1: <?php
2:
3: AlphaAutoLoader::loadLib('eng.php');
4:
5: /**
6: *
7: * A facade class for the TCPDF library which is used to convert some HTML content provided by the
8: * Markdown library to a PDF file using FPDF
9: *
10: * @package alpha::util
11: * @since 1.0
12: * @author John Collins <dev@alphaframework.org>
13: * @version $Id: TCPDFFacade.inc 1550 2012-07-29 21:15:40Z alphadevx $
14: * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
15: * @copyright Copyright (c) 2012, John Collins (founder of Alpha Framework).
16: * All rights reserved.
17: *
18: * <pre>
19: * Redistribution and use in source and binary forms, with or
20: * without modification, are permitted provided that the
21: * following conditions are met:
22: *
23: * * Redistributions of source code must retain the above
24: * copyright notice, this list of conditions and the
25: * following disclaimer.
26: * * Redistributions in binary form must reproduce the above
27: * copyright notice, this list of conditions and the
28: * following disclaimer in the documentation and/or other
29: * materials provided with the distribution.
30: * * Neither the name of the Alpha Framework nor the names
31: * of its contributors may be used to endorse or promote
32: * products derived from this software without specific
33: * prior written permission.
34: *
35: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
36: * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
37: * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
38: * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39: * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
40: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
42: * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
43: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
44: * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
45: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
46: * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
47: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
48: * </pre>
49: *
50: */
51: class TCPDFFacade {
52: /**
53: * The HTML-format content that we will render as a PDF
54: *
55: * @var string
56: * @since 1.0
57: */
58: private $content;
59:
60: /**
61: * The PDF object that will be generated from the Markdown HTML content
62: *
63: * @var AlphaTCPDF
64: * @since 1.0
65: */
66: private $pdf;
67:
68: /**
69: * The business object that stores the content will be rendered to Markdown
70: *
71: * @var AlphaDAO
72: * @since 1.0
73: */
74: private $BO = null;
75:
76: /**
77: * The auto-generated name of the PDF cache file for the BO
78: *
79: * @var string
80: * @since 1.0
81: */
82: private $PDFFilename;
83:
84: /**
85: * The auto-generated name of the HTML cache file for the BO generated by Markdown
86: *
87: * @var string
88: * @since 1.0
89: */
90: private $HTMLFilename;
91:
92: /**
93: * The constructor
94: *
95: * @param object $BO the business object that stores the content will be rendered to Markdown
96: * @since 1.0
97: */
98: public function __construct($BO) {
99: global $config;
100: global $l;
101:
102: $this->BO = $BO;
103: $this->PDFFilename = $config->get('app.file.store.dir').'cache/pdf/'.get_class($this->BO).'_'.$this->BO->getID().'_'.$this->BO->getVersion().'.pdf';
104: $PDFDownloadName = str_replace(' ', '_', $this->BO->get('title').'.pdf');
105: $this->HTMLFilename = $config->get('app.file.store.dir').'cache/html/'.get_class($this->BO).'_'.$this->BO->getID().'_'.$this->BO->getVersion().'.html';
106:
107: // first check the PDF cache, and if its there then re-direct to the file
108: if($this->checkPDFCache())
109: $this->serveCachedPDF($PDFDownloadName);
110:
111: if(method_exists($this->BO, 'getAttachmentsURL'))
112: $attachURL = $this->BO->getAttachmentsURL();
113: else
114: $attachURL = '';
115:
116: if ($this->checkHTMLCache()) {
117: $this->loadHTMLCache();
118: }else{
119: $this->content = $this->markdown($this->BO->get('content', true), $attachURL);
120: $this->HTMLCache();
121: }
122:
123: // Replace all instances of $attachURL in link tags to links to the ViewAttachment controller
124: $attachments = array();
125: preg_match_all('/href\=\"\$attachURL\/.*\"/', $this->content, $attachments);
126:
127: foreach($attachments[0] as $attachmentURL) {
128: $start = strpos($attachmentURL, '/');
129: $end = strrpos($attachmentURL, '"');
130: $fileName = substr($attachmentURL, $start+1, $end-($start+1));
131:
132: if(method_exists($this->BO, 'getAttachmentSecureURL')) {
133: $this->content = str_replace($attachmentURL, 'href='.$this->BO->getAttachmentSecureURL($fileName), $this->content);
134: }
135: }
136:
137: // Handle image attachments
138: $attachments = array();
139: preg_match_all('/\<img\ src\=\"\$attachURL\/.*\".*\>/', $this->content, $attachments);
140:
141: foreach($attachments[0] as $attachmentURL) {
142: $start = strpos($attachmentURL, '/');
143: $end = strrpos($attachmentURL, '" alt');
144: $fileName = substr($attachmentURL, $start+1, $end-($start+1));
145:
146: if($config->get('cms.images.widget')) {
147: // get the details of the source image
148: $path = $this->BO->getAttachmentsLocation().'/'.$fileName;
149: $image_details = getimagesize($path);
150: $imgType = $image_details[2];
151: if($imgType == 1)
152: $type = 'gif';
153: elseif($imgType == 2)
154: $type = 'jpg';
155: elseif($imgType == 3)
156: $type = 'png';
157:
158: $img = new Image($path, $image_details[0], $image_details[1], $type, 0.95, false, (boolean)$config->get('cms.images.widget.secure'));
159: $this->content = str_replace($attachmentURL, $img->renderHTMLLink(), $this->content);
160: }else{
161: // render a normal image link to the ViewAttachment controller
162: if(method_exists($this->BO, 'getAttachmentSecureURL')) {
163: $this->content = str_replace($attachmentURL, '<img src="'.$this->BO->getAttachmentSecureURL($fileName).'">', $this->content);
164: }
165: }
166: }
167:
168: $this->pdf = new AlphaTCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false);
169: $this->pdf->SetCreator(PDF_CREATOR);
170: $this->pdf->SetAuthor($this->BO->get('author'));
171: $this->pdf->SetTitle($this->BO->get('title'));
172: $this->pdf->SetSubject($this->BO->get('description'));
173: // TODO inject tags here?
174: //$this->pdf->SetKeywords($this->BO->get('keywords'));
175:
176: //set margins
177: $this->pdf->SetMargins(PDF_MARGIN_LEFT, PDF_MARGIN_TOP, PDF_MARGIN_RIGHT);
178: $this->pdf->SetHeaderMargin(PDF_MARGIN_HEADER);
179: $this->pdf->SetFooterMargin(PDF_MARGIN_FOOTER);
180:
181: //set auto page breaks
182: $this->pdf->SetAutoPageBreak(TRUE, PDF_MARGIN_BOTTOM);
183:
184: //set image scale factor
185: $this->pdf->setImageScale(2.5);
186:
187: //set some language-dependent strings
188: $this->pdf->setLanguageArray($l);
189:
190: // add a page
191: $this->pdf->AddPage();
192:
193: // add the title
194: $title = '<h1>'.$this->BO->get('title').'</h1>';
195: // add some custom footer info about the article
196: $footer = '<br><p>Article URL: <a href="'.$this->BO->get('URL').'">'.$this->BO->get('URL').'</a><br>Title: '.$this->BO->get('title').'<br>Author: '.$this->BO->get('author').'</p>';
197:
198: // write the title
199: $this->pdf->writeHTML($title, true, 0, true, 0);
200: // output the HTML content
201: $this->pdf->writeHTML($this->content, true, 0, true, 0);
202: // write the article footer
203: $this->pdf->writeHTML($footer, true, 0, true, 0);
204:
205: // save this PDF to the cache
206: $this->pdf->Output($this->PDFFilename, 'F');
207: $this->serveCachedPDF($PDFDownloadName);
208: }
209:
210: /**
211: * Facade method which will invoke our custom markdown class rather than the standard one
212: *
213: * @since 1.0
214: */
215: private function markdown($text, $attachURL='') {
216: global $config;
217:
218: /*
219: * Initialize the parser and return the result of its transform method.
220: *
221: */
222: static $parser;
223: if (!isset($parser)) {
224: $parser_class = 'AlphaMarkdown';
225: $parser = new $parser_class;
226: }
227:
228: /*
229: * Replace all instances of $sysURL in the text with the app.url setting from config
230: *
231: */
232: $text = str_replace('$sysURL', $config->get('app.url'), $text);
233:
234: // transform text using parser.
235: return $parser->transform($text);
236: }
237:
238: /**
239: * Fetter for the content
240: *
241: * @return string HTML rendered the content
242: * @since 1.0
243: */
244: public function getContent() {
245: return $this->content;
246: }
247:
248: /**
249: * Saves the HTML generated by Markdown to the cache directory
250: *
251: * @since 1.0
252: */
253: private function HTMLCache() {
254: // check to ensure that the article is not transient before caching it
255: if ($this->BO->getID() != '00000000000') {
256: $fp=fopen($this->HTMLFilename,"w");
257: if (!$fp) {
258: throw new AlphaException('Failed to open the cache file for writing, directory permissions my not be set correctly!');
259: }else{
260: flock($fp,2); // locks the file for writting
261: fwrite($fp,$this->content);
262: flock($fp,3); // unlocks the file
263: fclose($fp); //closes the file
264: }
265: }
266: }
267:
268: /**
269: * Used to check the HTML cache for the BO cache file
270: *
271: * @return bool true if the file exists, false otherwise
272: * @since 1.0
273: */
274: private function checkHTMLCache() {
275: global $config;
276:
277: return file_exists($this->HTMLFilename);
278: }
279:
280: /**
281: * Method to load the content of the cache file to the $content attribute of this object
282: *
283: * @since 1.0
284: */
285: private function loadHTMLCache() {
286: $fp = fopen($this->HTMLFilename,"r");
287:
288: if (!$fp) {
289: throw new AlphaException('Failed to open the cache file for reading, directory permissions my not be set correctly!' ,'loadHTMLCache()');
290: }else{
291: $this->content = fread($fp, filesize($this->HTMLFilename));
292: fclose($fp); //closes the file
293: }
294: }
295:
296: /**
297: * Used to check the PDF cache for the BO cache file
298: *
299: * @return bool true if the file exists, false otherwise
300: * @since 1.0
301: */
302: private function checkPDFCache() {
303: return file_exists($this->PDFFilename);
304: }
305:
306: /**
307: * Used to serve the cached PDF file as a download
308: *
309: * @since 1.0
310: */
311: private function serveCachedPDF($name) {
312: // first load the file
313: $handle = fopen ($this->PDFFilename, 'r');
314: $data = fread($handle, filesize($this->PDFFilename));
315: fclose($handle);
316:
317: $filesize = strlen($data);
318: $mimetype = 'application/octet-stream';
319:
320: // Start sending headers
321: header("Pragma: public"); // required
322: header("Expires: 0");
323: header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
324: header("Cache-Control: private",false); // required for certain browsers
325: header("Content-Transfer-Encoding: binary");
326: header("Content-Type: " . $mimetype);
327: header("Content-Length: " . $filesize);
328: header("Content-Disposition: attachment; filename=\"" . $name . "\";" );
329:
330: // Send data
331: echo $data;
332: die();
333: }
334: }
335: ?>