Skip to content

Commit

Permalink
XWIKI-21763: Disable the server-side image resizing while exporting t…
Browse files Browse the repository at this point in the history
…o PDF

* Using a workaround until we add support for temporarily overwrite properties from a configuration source.
* Small refactoring of PDFExportJob to reduce class-fan-out complexity
  • Loading branch information
mflorea committed Feb 9, 2024
1 parent 78c9707 commit 5e10fb9
Show file tree
Hide file tree
Showing 8 changed files with 154 additions and 61 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.xwiki.export.pdf.internal.job;

import java.util.Arrays;

import javax.inject.Inject;

import org.xwiki.export.pdf.job.PDFExportJobRequest;
import org.xwiki.export.pdf.job.PDFExportJobStatus;
import org.xwiki.job.AbstractJob;
import org.xwiki.job.GroupedJob;
import org.xwiki.job.JobGroupPath;
import org.xwiki.model.reference.EntityReference;
import org.xwiki.security.authorization.AuthorizationManager;
import org.xwiki.security.authorization.Right;

/**
* Base class for PDF export job.
*
* @version $Id$
*/
public abstract class AbstractPDFExportJob extends AbstractJob<PDFExportJobRequest, PDFExportJobStatus>
implements GroupedJob
{
/**
* The PDF export job type.
*/
public static final String JOB_TYPE = "export/pdf";

/**
* Used to check access permissions.
*
* @see #hasAccess(Right, EntityReference)
*/
@Inject
private AuthorizationManager authorization;

@Override
public String getType()
{
return JOB_TYPE;
}

@Override
public JobGroupPath getGroupPath()
{
return new JobGroupPath(Arrays.asList("export", "pdf"));
}

@Override
protected PDFExportJobStatus createNewStatus(PDFExportJobRequest request)
{
return new PDFExportJobStatus(getType(), request, this.observationManager, this.loggerManager);
}

/**
* Check access rights taking into account the job request.
*
* @param right the access right to check
* @param reference the target entity reference
* @return return {@code true} if the current user or the entity author have the specified access right on the
* specified entity, depending on the job request
*/
protected boolean hasAccess(Right right, EntityReference reference)
{
return ((!this.request.isCheckRights()
|| this.authorization.hasAccess(right, this.request.getUserReference(), reference))
&& (!this.request.isCheckAuthorRights()
|| this.authorization.hasAccess(right, this.request.getAuthorReference(), reference)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Arrays;
import java.util.List;

import javax.inject.Inject;
Expand All @@ -34,17 +33,11 @@
import org.xwiki.export.pdf.PDFPrinter;
import org.xwiki.export.pdf.internal.RequiredSkinExtensionsRecorder;
import org.xwiki.export.pdf.job.PDFExportJobRequest;
import org.xwiki.export.pdf.job.PDFExportJobStatus;
import org.xwiki.export.pdf.job.PDFExportJobStatus.DocumentRenderingResult;
import org.xwiki.job.AbstractJob;
import org.xwiki.job.GroupedJob;
import org.xwiki.job.JobGroupPath;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.reference.EntityReference;
import org.xwiki.model.reference.ObjectPropertyReference;
import org.xwiki.model.reference.ObjectReference;
import org.xwiki.resource.temporary.TemporaryResourceStore;
import org.xwiki.security.authorization.AuthorizationManager;
import org.xwiki.security.authorization.Right;

/**
Expand All @@ -56,21 +49,8 @@
*/
@Component
@Named(PDFExportJob.JOB_TYPE)
public class PDFExportJob extends AbstractJob<PDFExportJobRequest, PDFExportJobStatus> implements GroupedJob
public class PDFExportJob extends AbstractPDFExportJob
{
/**
* The PDF export job type.
*/
public static final String JOB_TYPE = "export/pdf";

/**
* Used to check access permissions.
*
* @see #hasAccess(Right, EntityReference)
*/
@Inject
private AuthorizationManager authorization;

@Inject
private DocumentRenderer documentRenderer;

Expand All @@ -94,24 +74,6 @@ public class PDFExportJob extends AbstractJob<PDFExportJobRequest, PDFExportJobS
@Inject
private PrintPreviewURLBuilder printPreviewURLBuilder;

@Override
public String getType()
{
return JOB_TYPE;
}

@Override
public JobGroupPath getGroupPath()
{
return new JobGroupPath(Arrays.asList("export", "pdf"));
}

@Override
protected PDFExportJobStatus createNewStatus(PDFExportJobRequest request)
{
return new PDFExportJobStatus(getType(), request, this.observationManager, this.loggerManager);
}

@Override
protected void runInternal() throws Exception
{
Expand Down Expand Up @@ -195,22 +157,6 @@ private int render(DocumentReference documentReference, DocumentRendererParamete
return renderingResult.getHTML().length();
}

/**
* Check access rights taking into account the job request.
*
* @param right the access right to check
* @param reference the target entity reference
* @return return {@code true} if the current user or the entity author have the specified access right on the
* specified entity, depending on the job request
*/
private boolean hasAccess(Right right, EntityReference reference)
{
return ((!this.request.isCheckRights()
|| this.authorization.hasAccess(right, this.request.getUserReference(), reference))
&& (!this.request.isCheckAuthorRights()
|| this.authorization.hasAccess(right, this.request.getAuthorReference(), reference)));
}

private void saveAsPDF() throws IOException
{
URL printPreviewURL = this.printPreviewURLBuilder.getPrintPreviewURL(this.request);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,15 @@

Content of first section.

image:attach:xwiki-logo.png</content>
[[image:attach:xwiki-logo.png||width="100"]]

[[image:attach:xwiki-logo.png||width="100" class="force-server-side-resize"]]

{{velocity}}
{{html}}
&lt;img src="$doc.getAttachmentURL('xwiki-logo.png', 'download', 'height=50')" alt="XWiki Logo" /&gt;
{{/html}}
{{/velocity}}</content>
<attachment>
<filename>xwiki-logo.png</filename>
<mimetype>image/png</mimetype>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
<!-- Needed to verify that image server-side resize is disabled while exporting to PDF. -->
<dependency>
<groupId>org.xwiki.platform</groupId>
<artifactId>xwiki-platform-image-processing-plugin</artifactId>
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
<!--Test only dependencies. -->
<dependency>
<groupId>org.xwiki.platform</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@
// Starting or stopping the Office server requires PR (for the current user, on the main wiki reference).
// Enabling debug logs also requires PR.
"xwikiPropertiesAdditionalProperties=test.prchecker.excludePattern="
+ ".*:(XWiki\\.OfficeImporterAdmin|PDFExportIT\\.EnableDebugLogs)"
+ ".*:(XWiki\\.OfficeImporterAdmin|PDFExportIT\\.EnableDebugLogs)",
"xwikiCfgPlugins=com.xpn.xwiki.plugin.image.ImagePlugin",
}
)
@ExtendWith(PDFExportExecutionCondition.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@
// Starting or stopping the Office server requires PR (for the current user, on the main wiki reference).
// Enabling debug logs also requires PR.
"xwikiPropertiesAdditionalProperties=test.prchecker.excludePattern="
+ ".*:(XWiki\\.OfficeImporterAdmin|PDFExportIT\\.EnableDebugLogs)"
+ ".*:(XWiki\\.OfficeImporterAdmin|PDFExportIT\\.EnableDebugLogs)",
"xwikiCfgPlugins=com.xpn.xwiki.plugin.image.ImagePlugin",
}
)
@ExtendWith(PDFExportExecutionCondition.class)
Expand Down Expand Up @@ -225,11 +226,20 @@ void exportAsPDF(TestUtils setup, TestConfiguration testConfiguration) throws Ex
assertTrue(contentPageText.contains("Child\nSection 1\nContent of first section.\n"),
"Child document content missing: " + contentPageText);

// The content of the child document shows an image.
// The content of the child document shows the same image multiple times.
List<PDFImage> contentPageImages = pdf.getImagesFromPage(3);
assertEquals(1, contentPageImages.size());
assertEquals(3, contentPageImages.size());

// Verify the images included in the PDF are not resized server-side (we know the image width is specified
// in the source wiki syntax and we enabled the server-side image resize by default).
assertEquals(512, contentPageImages.get(0).getRawWidth());
assertEquals(512, contentPageImages.get(0).getRawHeight());
assertEquals(512, contentPageImages.get(2).getRawWidth());
assertEquals(512, contentPageImages.get(2).getRawHeight());

// For the second image we force the server-side resize.
assertEquals(100, contentPageImages.get(1).getRawWidth());
assertEquals(100, contentPageImages.get(1).getRawHeight());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -822,6 +822,38 @@ require([
}).wrap("&lt;span&gt;&lt;/span&gt;");
};

/**
* Removes the image size (width and height) from the image URL query string in order to prevent the server-side
* resize of the image, thus allowing the full size image to be included in the generated PDF.
*/
async function disableServerSideImageResize() {
const imageLoadPromises = []
document.querySelectorAll('#xwikicontent img:not(.force-server-side-resize)').forEach(image =&gt; {
const imageURL = new URL(image.src);
const imageParams = imageURL.searchParams;
if (imageParams.has('width') || imageParams.has('height')) {
// Backup the original query string before we modify the image URL.
const oldImageQueryString = imageURL.search;
// The image size is specified in the URL which may trigger a server-side resize. We want the full image to be
// included in the PDF so let's remove these parameters.
imageParams.delete('width');
imageParams.delete('height');
const newImageQueryString = '?' + imageParams.toString();
const newImageSrc = image.getAttribute('src').replace(oldImageQueryString, newImageQueryString);
// Create a promise for when the full size image is loaded.
imageLoadPromises.push(new Promise((resolve, reject) =&gt; {
image.addEventListener('load', resolve);
image.addEventListener('error', reject);
image.addEventListener('abort', resolve);
image.setAttribute('src', newImageSrc);
}));
}
});
await Promise.allSettled(imageLoadPromises);
}

pageReady.delayPageReady(disableServerSideImageResize(), 'Disable server-side image resize.');

// Adjust the exported content before performing the print layout.
pageReady.afterPageReady(() =&gt; {
refactorAnchors();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,4 @@ XWiki.PDFExport.ConfigurationClass_maxContentSize.hint=단일 PDF 내보내기
XWiki.PDFExport.ConfigurationClass_replaceFOP=FOP 교체
XWiki.PDFExport.ConfigurationClass_replaceFOP.hint=Apache Formatting Objects Processor(FOP)를 기반으로 이전 PDF 내보내기를 대체합니다.
</content>
</xwikidoc>
</xwikidoc>

0 comments on commit 5e10fb9

Please sign in to comment.