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

TCPDFFacade::checkHTMLCache()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
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\Util\Service\ServiceFactory;
9
use Alpha\View\Widget\Image;
10
use Alpha\Exception\AlphaException;
11
12
/**
13
 * A facade class for the TCPDF library which is used to convert some HTML content provided by the
14
 * Markdown library to a PDF file using FPDF.
15
 *
16
 * @since 1.0
17
 *
18
 * @author John Collins <[email protected]>
19
 * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
20
 * @copyright Copyright (c) 2017, John Collins (founder of Alpha Framework).
21
 * All rights reserved.
22
 *
23
 * <pre>
24
 * Redistribution and use in source and binary forms, with or
25
 * without modification, are permitted provided that the
26
 * following conditions are met:
27
 *
28
 * * Redistributions of source code must retain the above
29
 *   copyright notice, this list of conditions and the
30
 *   following disclaimer.
31
 * * Redistributions in binary form must reproduce the above
32
 *   copyright notice, this list of conditions and the
33
 *   following disclaimer in the documentation and/or other
34
 *   materials provided with the distribution.
35
 * * Neither the name of the Alpha Framework nor the names
36
 *   of its contributors may be used to endorse or promote
37
 *   products derived from this software without specific
38
 *   prior written permission.
39
 *
40
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
41
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
42
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
43
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
44
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
45
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
46
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
47
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
48
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
49
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
50
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
51
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
52
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
53
 * </pre>
54
 */
55
class TCPDFFacade
56
{
57
    /**
58
     * The HTML-format content that we will render as a PDF.
59
     *
60
     * @var string
61
     *
62
     * @since 1.0
63
     */
64
    private $content;
65
66
    /**
67
     * The PDF object that will be generated from the Markdown HTML content.
68
     *
69
     * @var \Alpha\Util\Extension\TCPDF
70
     *
71
     * @since 1.0
72
     */
73
    private $pdf;
74
75
    /**
76
     * The business object that stores the content will be rendered to Markdown.
77
     *
78
     * @var \Alpha\Model\Article
79
     *
80
     * @since 1.0
81
     */
82
    private $article = null;
83
84
    /**
85
     * The auto-generated name of the PDF cache key for the article.
86
     *
87
     * @var string
88
     *
89
     * @since 1.0
90
     */
91
    private $PDFCacheKey;
92
93
    /**
94
     * The auto-generated name of the HTML cache key for the article generated by Markdown.
95
     *
96
     * @var string
97
     *
98
     * @since 1.0
99
     */
100
    private $HTMLCacheKey;
101
102
    /**
103
     * Trace logger.
104
     *
105
     * @var \Alpha\Util\Logging\Logger
106
     *
107
     * @since 1.0
108
     */
109
    private static $logger = null;
110
111
    /**
112
     * The constructor.
113
     *
114
     * @param \Alpha\Model\ActiveRecord $article the business object that stores the content will be rendered to Markdown
115
     *
116
     * @since 1.0
117
     */
118
    public function __construct($article)
119
    {
120
        self::$logger = new Logger('TCPDFFacade');
121
        self::$logger->debug('>>__construct()');
122
123
        $config = ConfigProvider::getInstance();
124
        $cache = ServiceFactory::getInstance($config->get('cache.provider.name'), 'Alpha\Util\Cache\CacheProviderInterface');
125
126
        $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...
127
128
        $reflect = new \ReflectionClass($this->article);
129
        $classname = $reflect->getShortName();
130
131
        $this->PDFCacheKey = 'pdf_'.$classname.'_'.$this->article->getID().'_'.$this->article->getVersion();
132
        $this->HTMLCacheKey = 'html_'.$classname.'_'.$this->article->getID().'_'.$this->article->getVersion();
133
134
        // first check the PDF cache
135
        if ($cache->get($this->PDFCacheKey) !== false) {
136
            return;
137
        }
138
139
        if ($cache->get($this->HTMLCacheKey) !== false) {
140
            $this->content = $cache->get($this->HTMLCacheKey);
141
        } else {
142
            $this->content = $this->markdown($this->article->get('content', true));
143
            $cache->set($this->HTMLCacheKey, $this->content);
144
        }
145
146
        // Replace all instances of $attachURL in link tags to links to the ViewAttachment controller
147
        $attachments = array();
148
        preg_match_all('/href\=\"\$attachURL\/.*\"/', $this->content, $attachments);
149
150
        foreach ($attachments[0] as $attachmentURL) {
151
            $start = mb_strpos($attachmentURL, '/');
152
            $end = mb_strrpos($attachmentURL, '"');
153
            $fileName = mb_substr($attachmentURL, $start+1, $end-($start+1));
154
155
            if (method_exists($this->article, 'getAttachmentSecureURL')) {
156
                $this->content = str_replace($attachmentURL, 'href='.$this->article->getAttachmentSecureURL($fileName), $this->content);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Alpha\Model\ActiveRecord as the method getAttachmentSecureURL() does only exist in the following sub-classes of Alpha\Model\ActiveRecord: Alpha\Model\Article. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
157
            }
158
        }
159
160
        // Handle image attachments
161
        $attachments = array();
162
        preg_match_all('/\<img\ src\=\"\$attachURL\/.*\".*\>/', $this->content, $attachments);
163
164
        foreach ($attachments[0] as $attachmentURL) {
165
            $start = mb_strpos($attachmentURL, '/');
166
            $end = mb_strrpos($attachmentURL, '" alt');
167
            $fileName = mb_substr($attachmentURL, $start+1, $end-($start+1));
168
169
            if ($config->get('cms.images.widget')) {
170
                // get the details of the source image
171
                $path = $this->article->getAttachmentsLocation().'/'.$fileName;
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Alpha\Model\ActiveRecord as the method getAttachmentsLocation() does only exist in the following sub-classes of Alpha\Model\ActiveRecord: Alpha\Model\Article. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
172
                $image_details = getimagesize($path);
173
                $imgType = $image_details[2];
174
                if ($imgType == 1) {
175
                    $type = 'gif';
176
                } elseif ($imgType == 2) {
177
                    $type = 'jpg';
178
                } else {
179
                    $type = 'png';
180
                }
181
182
                $img = new Image($path, $image_details[0], $image_details[1], $type, 0.95, false, (boolean)$config->get('cms.images.widget.secure'));
183
                $this->content = str_replace($attachmentURL, $img->renderHTMLLink(), $this->content);
184
            } else {
185
                // render a normal image link to the ViewAttachment controller
186
                if (method_exists($this->article, 'getAttachmentSecureURL')) {
187
                    $this->content = str_replace($attachmentURL, '<img src="'.$this->article->getAttachmentSecureURL($fileName).'">', $this->content);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Alpha\Model\ActiveRecord as the method getAttachmentSecureURL() does only exist in the following sub-classes of Alpha\Model\ActiveRecord: Alpha\Model\Article. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
188
                }
189
            }
190
        }
191
192
        $this->pdf = new TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false);
193
        $this->pdf->SetCreator(PDF_CREATOR);
194
        $this->pdf->SetAuthor($this->article->get('author'));
195
        $this->pdf->SetTitle($this->article->get('title'));
196
        $this->pdf->SetSubject($this->article->get('description'));
197
198
        //set margins
199
        $this->pdf->SetMargins(PDF_MARGIN_LEFT, PDF_MARGIN_TOP, PDF_MARGIN_RIGHT);
200
        $this->pdf->SetHeaderMargin(PDF_MARGIN_HEADER);
201
        $this->pdf->SetFooterMargin(PDF_MARGIN_FOOTER);
202
203
        //set auto page breaks
204
        $this->pdf->SetAutoPageBreak(true, PDF_MARGIN_BOTTOM);
205
206
        //set image scale factor
207
        $this->pdf->setImageScale(2.5);
208
209
        // add a page
210
        $this->pdf->AddPage();
211
212
        // add the title
213
        $title = '<h1>'.$this->article->get('title').'</h1>';
214
        // add some custom footer info about the article
215
        $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>';
216
217
        // write the title
218
        self::$logger->debug('Writing the title ['.$title.'] to the PDF');
219
        $this->pdf->writeHTML(utf8_encode($title), true, false, true, false, '');
220
        // output the HTML content
221
        self::$logger->debug('Writing the content ['.$this->content.'] to the PDF');
222
        $this->pdf->writeHTML(utf8_encode($this->content), true, false, true, false, '');
223
        // write the article footer
224
        $this->pdf->writeHTML(utf8_encode($footer), true, false, true, false, '');
225
        self::$logger->debug('Writing the footer ['.$footer.'] to the PDF');
226
227
        // save this PDF to the cache
228
        $config->set($this->PDFCacheKey, $this->pdf->Output($this->PDFCacheKey, 'S'));
229
230
        self::$logger->debug('<<__construct()');
231
    }
232
233
    /**
234
     * Facade method which will invoke our custom markdown class rather than the standard one.
235
     *
236
     * @param $text The markdown content to parse
237
     *
238
     * @since 1.0
239
     */
240
    private function markdown($text)
241
    {
242
        $config = ConfigProvider::getInstance();
243
244
        /*
245
         * Initialize the parser and return the result of its transform method.
246
         *
247
         */
248
        static $parser;
249
250
        if (!isset($parser)) {
251
            $parser = new Markdown();
252
        }
253
254
        /*
255
         * Replace all instances of $sysURL in the text with the app.url setting from config
256
         *
257
         */
258
        $text = str_replace('$sysURL', $config->get('app.url'), $text);
259
260
        // transform text using parser.
261
        return $parser->transform($text);
262
    }
263
264
    /**
265
     * Fetter for the content.
266
     *
267
     * @return string HTML rendered the content
268
     *
269
     * @since 1.0
270
     */
271
    public function getContent()
272
    {
273
        return $this->content;
274
    }
275
276
    /**
277
     * Returns the raw PDF data stream from the cache (should be warm first before calling this method).
278
     *
279
     * @return string
280
     *
281
     * @since 2.0
282
     */
283
    public function getPDFData()
284
    {
285
        $config = ConfigProvider::getInstance();
286
        $cache = ServiceFactory::getInstance($config->get('cache.provider.name'), 'Alpha\Util\Cache\CacheProviderInterface');
287
288
        return $cache->get($this->PDFCacheKey);
289
    }
290
}
291