dotnetic Posted October 7, 2018 Share Posted October 7, 2018 I want to create PDFs of certain pages in the admin. I do not want to recreate the admin template on the frontend and write code to show every field that I have in my template. Instead I would like to get just the HTML of the admin page in a variable so I can use it for generating my PDFs. How would I do that? Can it be done via API? Or use ob_start to capture the output? Link to comment Share on other sites More sharing options...
adrian Posted October 7, 2018 Share Posted October 7, 2018 If you hook after Page::render you can assign $event->return to a variable and use that. 2 Link to comment Share on other sites More sharing options...
bernhard Posted October 8, 2018 Share Posted October 8, 2018 What exactly are you trying to do? I guess you want to create some sort of reports? https://parall.ax/products/jspdf could also be an option. But generating a PDF from the markup of the adminpage will definitely need some additional work. I'm curious what you come up with. Could also need a good solution ? 1 Link to comment Share on other sites More sharing options...
dotnetic Posted October 8, 2018 Author Share Posted October 8, 2018 @bernhard I try to generate PDFs of one one or more admin pages (forms). I built an application with PW where people can enter details (for example a job application or a request form for office material) and there are many fields (150+) in the admin. Also in the admin I ordered and positioned the fields like they should appear on the resulting PDF. So I do not want to make a new template on the frontend level, just to recreate what I already have in the admin. If fields are added I have to change nothing. So I hope to come up with a good solution. I already seen your RockPDF module, and also Pages2PDF and PDFcomposer. Thanks for the additional hint. Link to comment Share on other sites More sharing options...
bernhard Posted October 8, 2018 Share Posted October 8, 2018 I guess it would be the easiest to setup a function that loops over all fields of the edit form, creates a simple HTML table (or floating divs could also be an option) and populates all the values with outputformatting turned on. Rendering the original admin HTML into pdf will bring you a lot of bloat and I guess it will be hard to make that look pretty (actually I think it will be hard to even make it work at all, at least with RockPDF (mpdf), because it does not support all kinds of css and you also have some JS magic in the pw admin...). 1 Link to comment Share on other sites More sharing options...
szabesz Posted October 8, 2018 Share Posted October 8, 2018 31 minutes ago, bernhard said: Rendering the original admin HTML into pdf will bring you a lot of bloat @jmartsch Based on my limited amount of experience in converting HTML into PDF I can also confirm that keeping it vector based and simply converting the HTML/CSS/JS code of the admin into PDF will possibly not work out well. However, you might want to try turning it into a JPEG (or JPEGs), and afterwards embedding them into a multipage PDF. I have no idea if it is doable or not, but there are libraries out there, such as the one pupped up in my google seach: https://github.com/tsayen/dom-to-image Link to comment Share on other sites More sharing options...
dotnetic Posted October 8, 2018 Author Share Posted October 8, 2018 There are different libs out there which convert existing HTML and CSS to a PDF. If I create a Bitmap graphic out of the website, the PDF would not be searchable. So converting to a bitmap is not a solution for me. Looping over the fields and generating own markup would be the exact same process as if I did it on the frontend. I have to take every single fieldtype into account and render it accordingly. This is one thing ProcessWire's admin already does. Although this solution would be a lot cleaner with simpler HTML. Then I would create an own stylesheet and generate the PDF out of it. As I said before, I would not like to do this. But it might be an option. 1 Link to comment Share on other sites More sharing options...
dotnetic Posted October 8, 2018 Author Share Posted October 8, 2018 But my main question remains: How do I get the HTML of the admin page? Most important is, how do I get it when I am not actually on the page. Because I want to get the HTML of multiple admin pages. Link to comment Share on other sites More sharing options...
bernhard Posted October 8, 2018 Share Posted October 8, 2018 37 minutes ago, jmartsch said: But my main question remains: How do I get the HTML of the page. Most important is, how do I get it when I am not actually on the page. Because I want to get the HTML of multiple pages. 13 hours ago, adrian said: If you hook after Page::render you can assign $event->return to a variable and use that. As adrian already told you. Or like this: 1 Link to comment Share on other sites More sharing options...
dotnetic Posted October 8, 2018 Author Share Posted October 8, 2018 I have seen Adrian's answer but to hook into the rendering, I need to render the page, as you did in your example. The problem with your example is, that it does not get the HTML of the admin page, but instead of a template file that resides in site/templates. But I need the HTML from the admin page. Link to comment Share on other sites More sharing options...
bernhard Posted October 8, 2018 Share Posted October 8, 2018 haha, sorry, totally missed that ? 1 Link to comment Share on other sites More sharing options...
BitPoet Posted October 8, 2018 Share Posted October 8, 2018 @jmartsch: are these admin pages regular pages open in ProcessPageEdit, custom Process modules or pages added through AdminCustomFiles? In each of these cases, the answers might be a bit different. 1 Link to comment Share on other sites More sharing options...
dotnetic Posted October 8, 2018 Author Share Posted October 8, 2018 2 minutes ago, BitPoet said: @jmartsch: are these admin pages regular pages open in ProcessPageEdit, custom Process modules or pages added through AdminCustomFiles? In each of these cases, the answers might be a bit different. They are regular admin pages. Then in a module I would like to add a button that gathers the HTML of the admin pages and generates one or multiple PDFs out of it. Link to comment Share on other sites More sharing options...
Robin S Posted October 9, 2018 Share Posted October 9, 2018 15 hours ago, jmartsch said: I have to take every single fieldtype into account and render it accordingly. I'd say this is the way to go for sure. There aren't that many fieldtypes that require special treatment, and this way you get exactly the markup you want. You can get the inputfield widths used in Page Edit too so the layout is similar. A starting point... $out = ''; $p = $pages(1234); // get the page however you like $fieldgroup = $p->fields; foreach($fieldgroup as $field) { /* @var Field $field */ $field = $fieldgroup->getFieldContext($field); $inputfield = $field->getInputfield($p); $width = $inputfield->columnWidth ?: 100; // getFieldValue() is a function/method you create that returns a string (could be markup) value for $field on $p, depending on the type of $field $value = getFieldValue($p, $field); $out .= "<div class='field-value' style='width: {$width}%'>$value</div>"; } I think it's unlikely that the admin markup is going to render nicely when you convert it to PDF. In my experience the HTML-to-PDF conversion tools don't handle complex layouts well. Plus there is going to be all kinds of markup in Page Edit that won't be relevant to what you want to show in your PDF (tabs, buttons, "Add New" links for Repeaters, etc). If you do want to try HTML-to-PDF conversion for PW's default markup value for fields, or using the Page Edit markup, then here are some avenues to explore. Your mileage may vary as I only tested these quickly. You'll need to include the admin CSS. // Use PW's default markup value for different field types $out = ''; $p = $pages(1234); // get the page however you like foreach($p->fields as $field) { $out .= $p->getMarkup($field->name); } // Get markup for the whole Page Edit form $input->post->id = 1234; // the ID of the page you want $ppe = $modules->ProcessPageEdit; $out = $ppe->execute(); // Get markup for just the Content tab (you can't easily show more than one tab in the PDF anyway) $input->post->id = 1234; // the ID of the page you want $ppe = $modules->ProcessPageEdit; $form = $ppe->buildFormContent(); $out = $form->render(); 5 1 Link to comment Share on other sites More sharing options...
dotnetic Posted October 9, 2018 Author Share Posted October 9, 2018 Thank you all for your insights and information. Most people think, that it would not be a smart thing to render the default admin markup. However I am still evaluating options and trying out different things. Thanks @Robin S for his code. At a later time I would like to get an export of many PDFs (100 or 1000, I don't know yet, how many my customer would handle). I tried to find the perfect tool for generating the PDF. I tried WeasyPrint (written in Python) which has very good rendering capabilities. The problem is, that I have to call it from PHP via exec(), and it gives no feedback or errors. Additionally WeasyPrint requires an URL or a file to render to PDF, but does not know anything about my login status in ProcessWire. As my PDFs and pages are protected and should only be visible to logged in users, this is not a real solution ? Another problem is performance. The generation of 100 PDFs from pages would take a long time. First the page with over 150 fields has to be rendered and then the PDF has to be created. Multiply the time with 100 and you are waiting a looooong time. So right now, I think I use the Pages2PDF module which generates a PDF for a page on save and saves it to disk. As a template for the PDF I will try Robin's code to iterate through the Inputfields and get a clean output. When a user then wants to get an export of all PDFs, I just have to gather the files and zip them or combine them into one PDF. This would be really fast. What do you guys think about this approach? Link to comment Share on other sites More sharing options...
bernhard Posted October 9, 2018 Share Posted October 9, 2018 Do you know about RockPdf? Yeah, performance is a problem with all those PHP PDF libraries. Generating PDFs on page save would definitely help, if possible. mPDF also has a feature to replace text in pdf files, so you could maybe copy a base PDF and replace blocks individually to make it more performant (never tried, though). The problem with this feature (for me) was that it does only support strings and not any other markup (like images). Link to comment Share on other sites More sharing options...
OLSA Posted October 9, 2018 Share Posted October 9, 2018 Hello, there is also one "alternate" solution, using Node.js and Puppeteer with headless browser. In this case, export to PDF is only one segment what can be done with that tools (remote login, automated processing, deep testing, etc...). If you have Node.js on your machine, here is example (Windows) where project directory "printer" is in C partition (C:\printer). C:\> mkdir printer cd printer npm i puppeteer easy-pdf-merge After this, inside project directory are all required node modules (Puppeteer, Chromium browser, Easy PDF). Last step is to create index.js file and place it inside project directory ( C:\printer ) // index.js const puppeteer = require('puppeteer'); const merge = require('easy-pdf-merge'); // configuration // *** EDIT THIS: var admin_url = "http://my_site.com/admin_url"; var user = '*****'; var pasw = '*****'; // desired pages // *** EDIT THIS: var pdfUrls = [ "/page/edit/?id=1054&modal=1", "/page/edit/?id=1016&modal=1", "/page/edit/?id=1019&modal=1", "/setup/field/edit?id=1#inputfieldConfig", "/setup/field/edit?id=1&modal=1#inputfieldConfig" ]; var pdfFiles = []; // START async function main(){ const browser = await puppeteer.launch({headless: true, args:['--start-maximized']}); const page = await browser.newPage(); await page.setViewport({width: 1366, height: 768}); // login await page.goto(admin_url, { waitUntil: 'networkidle0' }); await page.type('#login_name', user); await page.type('#login_pass', pasw); // login submit await Promise.all([ page.click('#Inputfield_login_submit'), page.waitForNavigation({ waitUntil: 'networkidle0' }) ]); for(var i = 0; i < pdfUrls.length; i++){ await page.goto(admin_url + pdfUrls[i], {waitUntil: 'networkidle0'}); var pdfFileName = 'page' + (i + 1) + '.pdf'; pdfFiles.push(pdfFileName); await page.pdf({ path: pdfFileName, format: 'A4', printBackground: true,margin: {top: 0, right: 0, bottom: 0, left: 0}}); } await browser.close(); await mergeMultiplePDF(pdfFiles); }; const mergeMultiplePDF = (pdfFiles) => { return new Promise((resolve, reject) => { merge(pdfFiles,'processwire.pdf',function(err){ if(err){ console.log(err); reject(err) } console.log('Success'); resolve() }); }); }; // run all this and exit main().then(process.exit); *** Note: edit this script and write your login parameters and desired urls. After all, run script (inside C:\printer>) node index.js After a while (for this example, ~10 sec.) you will find PDF files in project folder (partials and 1 merged with all). As example here is attachment. Regards. processwire.pdf 5 1 Link to comment Share on other sites More sharing options...
bernhard Posted October 9, 2018 Share Posted October 9, 2018 Wow @OLSA thx for that great example! Creating PDFs via PHP (mpdf) definitely has some drawbacks (formatting is sometimes tedious since not all css commands are supported, for example you can't use block elements inside table cells). Your method would also work with charts or other complex elements. Not sure how multipage would work, but I guess support for that via the @page directives should be possible. @jmartsch For creating 1000s of reports you could use RockGrid's batcher. You could create a grid containing all elements to create a PDF from and batcher would create the reports one-by-one with a progressbar and user feedback, similar to this: 3 Link to comment Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now