      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: ?>