Completed
Push — develop ( 721cd8...61b686 )
by John
03:09
created

Alpha/Util/Extension/TCPDFFacade.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace Alpha\Util\Extension;
4
5
use Alpha\Util\Config\ConfigProvider;
6
use Alpha\Util\Logging\Logger;
7
use Alpha\Util\Extension\Markdown;
8
use Alpha\View\Widget\Image;
9
use Alpha\Exception\AlphaException;
10
11
/**
12
 * A facade class for the TCPDF library which is used to convert some HTML content provided by the
13
 * Markdown library to a PDF file using FPDF.
14
 *
15
 * @since 1.0
16
 *
17
 * @author John Collins <[email protected]>
18
 * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
19
 * @copyright Copyright (c) 2017, John Collins (founder of Alpha Framework).
20
 * All rights reserved.
21
 *
22
 * <pre>
23
 * Redistribution and use in source and binary forms, with or
24
 * without modification, are permitted provided that the
25
 * following conditions are met:
26
 *
27
 * * Redistributions of source code must retain the above
28
 *   copyright notice, this list of conditions and the
29
 *   following disclaimer.
30
 * * Redistributions in binary form must reproduce the above
31
 *   copyright notice, this list of conditions and the
32
 *   following disclaimer in the documentation and/or other
33
 *   materials provided with the distribution.
34
 * * Neither the name of the Alpha Framework nor the names
35
 *   of its contributors may be used to endorse or promote
36
 *   products derived from this software without specific
37
 *   prior written permission.
38
 *
39
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
40
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
41
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
42
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
43
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
44
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
45
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
46
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
47
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
48
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
49
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
50
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
51
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
52
 * </pre>
53
 */
54
class TCPDFFacade
55
{
56
    /**
57
     * The HTML-format content that we will render as a PDF.
58
     *
59
     * @var string
60
     *
61
     * @since 1.0
62
     */
63
    private $content;
64
65
    /**
66
     * The PDF object that will be generated from the Markdown HTML content.
67
     *
68
     * @var \Alpha\Util\Extension\TCPDF
69
     *
70
     * @since 1.0
71
     */
72
    private $pdf;
73
74
    /**
75
     * The business object that stores the content will be rendered to Markdown.
76
     *
77
     * @var \Alpha\Model\Article
78
     *
79
     * @since 1.0
80
     */
81
    private $article = null;
82
83
    /**
84
     * The auto-generated name of the PDF cache file for the article.
85
     *
86
     * @var string
87
     *
88
     * @since 1.0
89
     */
90
    private $PDFFilename;
91
92
    /**
93
     * The auto-generated name of the HTML cache file for the article generated by Markdown.
94
     *
95
     * @var string
96
     *
97
     * @since 1.0
98
     */
99
    private $HTMLFilename;
100
101
    /**
102
     * Trace logger.
103
     *
104
     * @var \Alpha\Util\Logging\Logger
105
     *
106
     * @since 1.0
107
     */
108
    private static $logger = null;
109
110
    /**
111
     * The constructor.
112
     *
113
     * @param \Alpha\Model\ActiveRecord $article the business object that stores the content will be rendered to Markdown
114
     *
115
     * @since 1.0
116
     */
117
    public function __construct($article)
118
    {
119
        self::$logger = new Logger('TCPDFFacade');
120
        self::$logger->debug('>>__construct()');
121
122
        $config = ConfigProvider::getInstance();
123
124
        $this->article = $article;
0 ignored issues
show
Documentation Bug introduced by
$article is of type object<Alpha\Model\ActiveRecord>, but the property $article was declared to be of type object<Alpha\Model\Article>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
125
126
        $reflect = new \ReflectionClass($this->article);
127
        $classname = $reflect->getShortName();
128
129
        $this->PDFFilename = $config->get('app.file.store.dir').'cache/pdf/'.$classname.'_'.$this->article->getID().'_'.$this->article->getVersion().'.pdf';
130
        $this->HTMLFilename = $config->get('app.file.store.dir').'cache/html/'.$classname.'_'.$this->article->getID().'_'.$this->article->getVersion().'.html';
131
132
        // first check the PDF cache
133
        if ($this->checkPDFCache()) {
134
            return;
135
        }
136
137
        if ($this->checkHTMLCache()) {
138
            $this->loadHTMLCache();
139
        } else {
140
            $this->content = $this->markdown($this->article->get('content', true));
141
            $this->HTMLCache();
142
        }
143
144
        // Replace all instances of $attachURL in link tags to links to the ViewAttachment controller
145
        $attachments = array();
146
        preg_match_all('/href\=\"\$attachURL\/.*\"/', $this->content, $attachments);
147
148
        foreach ($attachments[0] as $attachmentURL) {
149
            $start = mb_strpos($attachmentURL, '/');
150
            $end = mb_strrpos($attachmentURL, '"');
151
            $fileName = mb_substr($attachmentURL, $start+1, $end-($start+1));
152
153
            if (method_exists($this->article, 'getAttachmentSecureURL')) {
154
                $this->content = str_replace($attachmentURL, 'href='.$this->article->getAttachmentSecureURL($fileName), $this->content);
155
            }
156
        }
157
158
        // Handle image attachments
159
        $attachments = array();
160
        preg_match_all('/\<img\ src\=\"\$attachURL\/.*\".*\>/', $this->content, $attachments);
161
162
        foreach ($attachments[0] as $attachmentURL) {
163
            $start = mb_strpos($attachmentURL, '/');
164
            $end = mb_strrpos($attachmentURL, '" alt');
165
            $fileName = mb_substr($attachmentURL, $start+1, $end-($start+1));
166
167
            if ($config->get('cms.images.widget')) {
168
                // get the details of the source image
169
                $path = $this->article->getAttachmentsLocation().'/'.$fileName;
170
                $image_details = getimagesize($path);
171
                $imgType = $image_details[2];
172
                if ($imgType == 1) {
173
                    $type = 'gif';
174
                } elseif ($imgType == 2) {
175
                    $type = 'jpg';
176
                } else {
177
                    $type = 'png';
178
                }
179
180
                $img = new Image($path, $image_details[0], $image_details[1], $type, 0.95, false, (boolean)$config->get('cms.images.widget.secure'));
181
                $this->content = str_replace($attachmentURL, $img->renderHTMLLink(), $this->content);
182
            } else {
183
                // render a normal image link to the ViewAttachment controller
184
                if (method_exists($this->article, 'getAttachmentSecureURL')) {
185
                    $this->content = str_replace($attachmentURL, '<img src="'.$this->article->getAttachmentSecureURL($fileName).'">', $this->content);
186
                }
187
            }
188
        }
189
190
        $this->pdf = new TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false);
191
        $this->pdf->SetCreator(PDF_CREATOR);
192
        $this->pdf->SetAuthor($this->article->get('author'));
193
        $this->pdf->SetTitle($this->article->get('title'));
194
        $this->pdf->SetSubject($this->article->get('description'));
195
196
        //set margins
197
        $this->pdf->SetMargins(PDF_MARGIN_LEFT, PDF_MARGIN_TOP, PDF_MARGIN_RIGHT);
198
        $this->pdf->SetHeaderMargin(PDF_MARGIN_HEADER);
199
        $this->pdf->SetFooterMargin(PDF_MARGIN_FOOTER);
200
201
        //set auto page breaks
202
        $this->pdf->SetAutoPageBreak(true, PDF_MARGIN_BOTTOM);
203
204
        //set image scale factor
205
        $this->pdf->setImageScale(2.5);
206
207
        // add a page
208
        $this->pdf->AddPage();
209
210
        // add the title
211
        $title = '<h1>'.$this->article->get('title').'</h1>';
212
        // add some custom footer info about the article
213
        $footer = '<br><p>Article URL: <a href="'.$this->article->get('URL').'">'.$this->article->get('URL').'</a><br>Title: '.$this->article->get('title').'<br>Author: '.$this->article->get('author').'</p>';
214
215
        // write the title
216
        self::$logger->debug('Writing the title ['.$title.'] to the PDF');
217
        $this->pdf->writeHTML(utf8_encode($title), true, false, true, false, '');
218
        // output the HTML content
219
        self::$logger->debug('Writing the content ['.$this->content.'] to the PDF');
220
        $this->pdf->writeHTML(utf8_encode($this->content), true, false, true, false, '');
221
        // write the article footer
222
        $this->pdf->writeHTML(utf8_encode($footer), true, false, true, false, '');
223
        self::$logger->debug('Writing the footer ['.$footer.'] to the PDF');
224
225
        // save this PDF to the cache
226
        $this->pdf->Output($this->PDFFilename, 'F');
227
228
        self::$logger->debug('<<__construct()');
229
    }
230
231
        /**
232
         * Facade method which will invoke our custom markdown class rather than the standard one.
233
         *
234
         * @param $text The markdown content to parse
235
         *
236
         * @since 1.0
237
         */
238
        private function markdown($text)
239
        {
240
            $config = ConfigProvider::getInstance();
241
242
        /*
243
         * Initialize the parser and return the result of its transform method.
244
         *
245
         */
246
        static $parser;
247
248
            if (!isset($parser)) {
249
                $parser = new Markdown();
250
            }
251
252
        /*
253
         * Replace all instances of $sysURL in the text with the app.url setting from config
254
         *
255
         */
256
        $text = str_replace('$sysURL', $config->get('app.url'), $text);
257
258
        // transform text using parser.
259
        return $parser->transform($text);
260
        }
261
262
    /**
263
     * Fetter for the content.
264
     *
265
     * @return string HTML rendered the content
266
     *
267
     * @since 1.0
268
     */
269
    public function getContent()
270
    {
271
        return $this->content;
272
    }
273
274
    /**
275
     * Saves the HTML generated by Markdown to the cache directory.
276
     *
277
     * @throws \Alpha\Exception\AlphaException
278
     *
279
     * @since 1.0
280
     */
281
    private function HTMLCache()
282
    {
283
        // check to ensure that the article is not transient before caching it
284
        if ($this->article->getID() != '00000000000') {
285
            $fp = fopen($this->HTMLFilename, 'w');
286
            if (!$fp) {
287
                throw new AlphaException('Failed to open the cache file for writing, directory permissions my not be set correctly!');
288
            } else {
289
                flock($fp, 2); // locks the file for writting
290
                fwrite($fp, $this->content);
291
                flock($fp, 3); // unlocks the file
292
                fclose($fp); //closes the file
293
            }
294
        }
295
    }
296
297
    /**
298
     * Used to check the HTML cache for the article cache file.
299
     *
300
     * @return bool true if the file exists, false otherwise
301
     *
302
     * @since 1.0
303
     */
304
    private function checkHTMLCache()
305
    {
306
        return file_exists($this->HTMLFilename);
307
    }
308
309
    /**
310
     * Method to load the content of the cache file to the $content attribute of this object.
311
     *
312
     * @throws \Alpha\Exception\AlphaException
313
     *
314
     * @since 1.0
315
     */
316
    private function loadHTMLCache()
317
    {
318
        $fp = fopen($this->HTMLFilename, 'r');
319
320
        if (!$fp) {
321
            throw new AlphaException('Failed to open the cache file for reading, directory permissions my not be set correctly!', 'loadHTMLCache()');
322
        } else {
323
            $this->content = fread($fp, filesize($this->HTMLFilename));
324
            fclose($fp); //closes the file
325
        }
326
    }
327
328
    /**
329
     * Used to check the PDF cache for the article cache file.
330
     *
331
     * @return bool true if the file exists, false otherwise
332
     *
333
     * @since 1.0
334
     */
335
    private function checkPDFCache()
336
    {
337
        return file_exists($this->PDFFilename);
338
    }
339
340
    /**
341
     * Returns the raw PDF data stream.
342
     *
343
     * @return string
344
     *
345
     * @since 2.0
346
     */
347
    public function getPDFData()
348
    {
349
        // first load the file
350
        $handle = fopen($this->PDFFilename, 'r');
351
        $data = fread($handle, filesize($this->PDFFilename));
352
        fclose($handle);
353
354
        return $data;
355
    }
356
}
357