Completed
Push — develop ( 1aa55d...6ff6f9 )
by John
02:50
created

ArticleController::renderComments()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 34
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 34
rs 8.439
cc 6
eloc 20
nc 4
nop 0
1
<?php
2
3
namespace Alpha\Controller;
4
5
use Alpha\Util\Logging\Logger;
6
use Alpha\Util\Logging\KPI;
7
use Alpha\Util\Config\ConfigProvider;
8
use Alpha\Util\Security\SecurityUtils;
9
use Alpha\Util\Extension\TCPDFFacade;
10
use Alpha\Util\Http\Request;
11
use Alpha\Util\Http\Response;
12
use Alpha\Util\Http\Session\SessionProviderFactory;
13
use Alpha\Util\File\FileUtils;
14
use Alpha\Model\Article;
15
use Alpha\Model\ArticleComment;
16
use Alpha\View\View;
17
use Alpha\View\ViewState;
18
use Alpha\View\Widget\Button;
19
use Alpha\Exception\SecurityException;
20
use Alpha\Exception\AlphaException;
21
use Alpha\Exception\RecordNotFoundException;
22
use Alpha\Exception\IllegalArguementException;
23
use Alpha\Exception\ResourceNotFoundException;
24
use Alpha\Exception\FileNotFoundException;
25
use Alpha\Model\ActiveRecord;
26
use Alpha\Controller\Front\FrontController;
27
28
/**
29
 * Controller used handle Article objects.
30
 *
31
 * @since 1.0
32
 *
33
 * @author John Collins <[email protected]>
34
 * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
35
 * @copyright Copyright (c) 2017, John Collins (founder of Alpha Framework).
36
 * All rights reserved.
37
 *
38
 * <pre>
39
 * Redistribution and use in source and binary forms, with or
40
 * without modification, are permitted provided that the
41
 * following conditions are met:
42
 *
43
 * * Redistributions of source code must retain the above
44
 *   copyright notice, this list of conditions and the
45
 *   following disclaimer.
46
 * * Redistributions in binary form must reproduce the above
47
 *   copyright notice, this list of conditions and the
48
 *   following disclaimer in the documentation and/or other
49
 *   materials provided with the distribution.
50
 * * Neither the name of the Alpha Framework nor the names
51
 *   of its contributors may be used to endorse or promote
52
 *   products derived from this software without specific
53
 *   prior written permission.
54
 *
55
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
56
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
57
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
58
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
59
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
60
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
61
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
62
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
63
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
64
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
65
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
66
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
67
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
68
 * </pre>
69
 */
70
class ArticleController extends ActiveRecordController implements ControllerInterface
71
{
72
    /**
73
     * Trace logger.
74
     *
75
     * @var \Alpha\Util\Logging\Logger
76
     *
77
     * @since 1.0
78
     */
79
    private static $logger = null;
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
80
81
    /**
82
     * constructor to set up the object.
83
     *
84
     * @since 1.0
85
     */
86
    public function __construct()
87
    {
88
        self::$logger = new Logger('ArticleController');
89
        self::$logger->debug('>>__construct()');
90
91
        // ensure that the super class constructor is called, indicating the rights group
92
        parent::__construct('Public');
93
94
        self::$logger->debug('<<__construct');
95
    }
96
97
    /**
98
     * Handle GET requests.
99
     *
100
     * @param \Alpha\Util\Http\Request
101
     *
102
     * @return \Alpha\Util\Http\Response
103
     *
104
     * @throws \Alpha\Exception\ResourceNotFoundException
105
     *
106
     * @since 1.0
107
     */
108
    public function doGET($request)
109
    {
110
        self::$logger->debug('>>doGET($request=['.var_export($request, true).'])');
111
112
        $config = ConfigProvider::getInstance();
113
114
        $params = $request->getParams();
115
116
        $body = '';
117
118
        // handle requests for PDFs
119
        if (isset($params['title']) && (isset($params['pdf']) || $request->getHeader('Accept') == 'application/pdf')) {
120
            try {
121
                $title = str_replace($config->get('cms.url.title.separator'), ' ', $params['title']);
122
123
                if (isset($params['ActiveRecordType']) && class_exists($params['ActiveRecordType'])) {
124
                    $record = new $params['ActiveRecordType'];
125
                } else {
126
                    $record = new Article();
127
                }
128
                $record->loadByAttribute('title', $title);
129
                $this->record = $record;
130
131
                ActiveRecord::disconnect();
132
133
                $pdf = new TCPDFFacade($record);
134
                $pdfData = $pdf->getPDFData();
135
                $pdfDownloadName = str_replace(' ', '-', $record->get('title').'.pdf');
136
137
                $headers = array(
138
                    'Pragma' => 'public',
139
                    'Expires' => 0,
140
                    'Cache-Control' => 'must-revalidate, post-check=0, pre-check=0',
141
                    'Content-Transfer-Encoding' => 'binary',
142
                    'Content-Type' => 'application/pdf',
143
                    'Content-Length' => strlen($pdfData),
144
                    'Content-Disposition' => 'attachment; filename="'.$pdfDownloadName.'";',
145
                );
146
147
                return new Response(200, $pdfData, $headers);
148
            } catch (IllegalArguementException $e) {
149
                self::$logger->error($e->getMessage());
150
                throw new ResourceNotFoundException($e->getMessage());
151
            } catch (RecordNotFoundException $e) {
152
                self::$logger->error($e->getMessage());
153
                throw new ResourceNotFoundException($e->getMessage());
154
            }
155
        }
156
157
        // view edit article requests
158
        if ((isset($params['view']) && $params['view'] == 'edit') && (isset($params['title']) || isset($params['ActiveRecordID']))) {
159
            if (isset($params['ActiveRecordType']) && class_exists($params['ActiveRecordType'])) {
160
                $record = new $params['ActiveRecordType'];
161
            } else {
162
                $record = new Article();
163
            }
164
165
            try {
166
                if (isset($params['title'])) {
167
                    $title = str_replace($config->get('cms.url.title.separator'), ' ', $params['title']);
168
                    $record->loadByAttribute('title', $title);
169
                } else {
170
                    $record->load($params['ActiveRecordID']);
171
                }
172
            } catch (RecordNotFoundException $e) {
173
                self::$logger->warn($e->getMessage());
174
                $body .= View::renderErrorPage(404, 'Failed to find the requested article!');
175
176
                return new Response(404, $body, array('Content-Type' => 'text/html'));
177
            }
178
179
            ActiveRecord::disconnect();
180
181
            $this->record = $record;
182
            $view = View::getInstance($record);
183
184
            // set up the title and meta details
185
            $this->setTitle($record->get('title').' (editing)');
186
            $this->setDescription('Page to edit '.$record->get('title').'.');
187
            $this->setKeywords('edit,article');
188
189
            $body .= View::displayPageHead($this);
190
191
            $message = $this->getStatusMessage();
192
            if (!empty($message)) {
193
                $body .= $message;
194
            }
195
196
            $body .= $view->editView(array('URI' => $request->getURI()));
197
            $body .= View::renderDeleteForm($request->getURI());
198
199
            $body .= View::displayPageFoot($this);
200
            self::$logger->debug('<<doGET');
201
202
            return new Response(200, $body, array('Content-Type' => 'text/html'));
203
        }
204
205
        // handle requests for viewing articles
206
        if (isset($params['title']) || isset($params['ActiveRecordID'])) {
207
            $KDP = new KPI('viewarticle');
208
            if (isset($params['ActiveRecordType']) && class_exists($params['ActiveRecordType'])) {
209
                $record = new $params['ActiveRecordType'];
210
            } else {
211
                $record = new Article();
212
            }
213
214
            try {
215
                if (isset($params['title'])) {
216
                    $title = str_replace($config->get('cms.url.title.separator'), ' ', $params['title']);
217
218
                    $record->loadByAttribute('title', $title, false, array('ID', 'version_num', 'created_ts', 'updated_ts', 'title', 'author', 'published', 'content', 'headerContent'));
219
                } else {
220
                    $record->load($params['ActiveRecordID']);
221
                }
222
223
                if (!$record->get('published')) {
224
                    throw new RecordNotFoundException('Attempted to load an article which is not published yet');
225
                }
226
227
                $record->set('tags', $record->getID());
228
            } catch (IllegalArguementException $e) {
229
                self::$logger->warn($e->getMessage());
230
                throw new ResourceNotFoundException('The file that you have requested cannot be found!');
231
            } catch (RecordNotFoundException $e) {
232
                self::$logger->warn($e->getMessage());
233
                throw new ResourceNotFoundException('The article that you have requested cannot be found!');
234
            }
235
236
            $this->record = $record;
237
            $this->setTitle($record->get('title'));
238
            $this->setDescription($record->get('description'));
239
240
            $recordView = View::getInstance($record);
241
242
            $body .= View::displayPageHead($this);
243
244
            $message = $this->getStatusMessage();
245
            if (!empty($message)) {
246
                $body .= $message;
247
            }
248
249
            $body .= $recordView->markdownView();
250
251
            $body .= View::displayPageFoot($this);
252
253
            $KDP->log();
254
255
            return new Response(200, $body, array('Content-Type' => 'text/html'));
256
        }
257
258
        // handle requests to view an article stored in a file
259
        if (isset($params['file'])) {
260
            try {
261
                $record = new Article();
262
263
                // just checking to see if the file path is absolute or not
264
                if (mb_substr($params['file'], 0, 1) == '/') {
265
                    $record->loadContentFromFile($params['file']);
266
                } else {
267
                    $record->loadContentFromFile($config->get('app.root').'docs/'.$params['file']);
268
                }
269
            } catch (IllegalArguementException $e) {
270
                self::$logger->error($e->getMessage());
271
                throw new ResourceNotFoundException($e->getMessage());
272
            } catch (FileNotFoundException $e) {
273
                self::$logger->warn($e->getMessage().' File path is ['.$params['file'].']');
274
                throw new ResourceNotFoundException('Failed to load the requested article from the file system!');
275
            }
276
277
            $this->record = $record;
278
            $this->setTitle($record->get('title'));
279
280
            $recordView = View::getInstance($record);
281
282
            $body .= View::displayPageHead($this, false);
0 ignored issues
show
Unused Code introduced by
The call to View::displayPageHead() has too many arguments starting with false.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
283
284
            $body .= $recordView->markdownView();
285
286
            $body .= View::displayPageFoot($this);
287
288
            return new Response(200, $body, array('Content-Type' => 'text/html'));
289
        }
290
291
        // handle requests to view a list of articles
292
        if (isset($params['start'])) {
293
            return parent::doGET($request);
294
        }
295
296
        // create a new article requests
297
        $record = new Article();
298
        $view = View::getInstance($record);
299
300
        // set up the title and meta details
301
        $this->setTitle('Creating article');
302
        $this->setDescription('Page to create a new article.');
303
        $this->setKeywords('create,article');
304
305
        $body .= View::displayPageHead($this);
306
307
        $message = $this->getStatusMessage();
308
        if (!empty($message)) {
309
            $body .= $message;
310
        }
311
312
        $fields = array('formAction' => $this->request->getURI());
313
        $body .= $view->createView($fields);
314
315
        $body .= View::displayPageFoot($this);
316
        self::$logger->debug('<<doGET');
317
318
        return new Response(200, $body, array('Content-Type' => 'text/html'));
319
    }
320
321
    /**
322
     * Method to handle PUT requests.
323
     *
324
     * @param \Alpha\Util\Http\Request
325
     *
326
     * @return \Alpha\Util\Http\Response
327
     *
328
     * @since 1.0
329
     */
330
    public function doPUT($request)
331
    {
332
        self::$logger->debug('>>doPUT($request=['.var_export($request, true).'])');
333
334
        $config = ConfigProvider::getInstance();
335
336
        $params = $request->getParams();
337
338
        $record = null;
339
340
        try {
341
            // check the hidden security fields before accepting the form POST data
342
            if (!$this->checkSecurityFields()) {
343
                self::$logger->debug('<<doPUT');
344
                throw new SecurityException('This page cannot accept post data from remote servers!');
345
            }
346
347
            if (isset($params['markdownTextBoxRows']) && $params['markdownTextBoxRows'] != '') {
348
                $viewState = ViewState::getInstance();
349
                $viewState->set('markdownTextBoxRows', $params['markdownTextBoxRows']);
350
            }
351
352
            if (isset($params['title']) || isset($params['ActiveRecordID'])) {
353
                if (isset($params['ActiveRecordType']) && class_exists($params['ActiveRecordType'])) {
354
                    $record = new $params['ActiveRecordType'];
355
                } else {
356
                    $record = new Article();
357
                }
358
359
                if (isset($params['title'])) {
360
                    $title = str_replace($config->get('cms.url.title.separator'), ' ', $params['title']);
361
362
                    $record->loadByAttribute('title', $title, false, array('ID', 'version_num', 'created_ts', 'updated_ts', 'title', 'author', 'published', 'content', 'headerContent'));
363
                } else {
364
                    $record->load($params['ActiveRecordID']);
365
                }
366
367
                // uploading an article attachment
368
                if (isset($params['uploadBut'])) {
369
                    $source = $request->getFile('userfile')['tmp_name'];
370
                    $dest = $record->getAttachmentsLocation().'/'.$request->getFile('userfile')['name'];
371
372
                    // upload the file to the attachments directory
373
                    FileUtils::copy($source, $dest);
374
375
                    if (!file_exists($dest)) {
376
                        throw new AlphaException('Could not move the uploaded file ['.$request->getFile('userfile')['name'].']');
377
                    }
378
379
                    // set read/write permissions on the file
380
                    $success = chmod($dest, 0666);
381
382
                    if (!$success) {
383
                        throw new AlphaException('Unable to set read/write permissions on the uploaded file ['.$dest.'].');
384
                    }
385
386
                    if ($success) {
387
                        self::$logger->action('File '.$source.' uploaded to '.$dest);
388
                        $this->setStatusMessage(View::displayUpdateMessage('File '.$source.' uploaded to '.$dest));
389
                    }
390
                } elseif (isset($params['deletefile']) && $params['deletefile'] != '') {
391
                    $success = unlink($record->getAttachmentsLocation().'/'.$params['deletefile']);
392
393
                    if (!$success) {
394
                        throw new AlphaException('Could not delete the file ['.$params['deletefile'].']');
395
                    }
396
397
                    if ($success) {
398
                        self::$logger->action('File '.$record->getAttachmentsLocation().'/'.$params['deletefile'].' deleted');
399
                        $this->setStatusMessage(View::displayUpdateMessage('File '.$record->getAttachmentsLocation().'/'.$params['deletefile'].' deleted'));
400
                    }
401
                } else {
402
                    self::$logger->debug('<<doPUT');
403
404
                    return parent::doPUT($request);
405
                }
406
            } else {
407
                throw new IllegalArguementException('No valid article ID provided!');
408
            }
409
        } catch (SecurityException $e) {
410
            $this->setStatusMessage(View::displayErrorMessage($e->getMessage()));
411
            self::$logger->warn($e->getMessage());
412
        } catch (IllegalArguementException $e) {
413
            $this->setStatusMessage(View::displayErrorMessage($e->getMessage()));
414
            self::$logger->error($e->getMessage());
415
        } catch (RecordNotFoundException $e) {
416
            self::$logger->warn($e->getMessage());
417
            $this->setStatusMessage(View::displayErrorMessage('Failed to load the requested article from the database!'));
418
        } catch (AlphaException $e) {
419
            $this->setStatusMessage(View::displayErrorMessage($e->getMessage()));
420
            self::$logger->error($e->getMessage());
421
        }
422
423
        $response = new Response(301);
424
425
        if ($this->getNextJob() != '') {
426
            $response->redirect($this->getNextJob());
427
        } else {
428
            if ($this->request->isSecureURI()) {
429
                $response->redirect(FrontController::generateSecureURL('act=Alpha\\Controller\\ActiveRecordController&ActiveRecordType=Alpha\Model\Article&ActiveRecordID='.$record->getID().'&view=edit'));
430
            } else {
431
                $title = str_replace(' ', $config->get('cms.url.title.separator'), $record->get('title'));
432
                $response->redirect($config->get('app.url').'/a/'.$title.'/edit');
433
            }
434
        }
435
436
        self::$logger->debug('<<doPUT');
437
438
        return $response;
439
    }
440
441
    /**
442
     * Method to handle DELETE requests.
443
     *
444
     * @param \Alpha\Util\Http\Request
445
     *
446
     * @return \Alpha\Util\Http\Response
447
     *
448
     * @since 2.0
449
     */
450
    public function doDELETE($request)
451
    {
452
        self::$logger->debug('>>doDELETE($request=['.var_export($request, true).'])');
453
454
        $this->setUnitOfWork(array());
455
456
        self::$logger->debug('<<doDELETE');
457
458
        return parent::doDELETE($request);
459
    }
460
461
    /**
462
     * Renders custom HTML header content.
463
     *
464
     * @return string
465
     *
466
     * @since 1.0
467
     */
468
    public function during_displayPageHead_callback()
469
    {
470
        $config = ConfigProvider::getInstance();
471
472
        $params = $this->request->getParams();
473
474
        $html = '';
475
476
        if ($config->get('cms.highlight.provider.name') == 'Alpha\Util\Code\Highlight\HighlightProviderLuminous') {
477
            $html .= '<link rel="StyleSheet" type="text/css" href="'.$config->get('app.url').'/css/luminous.css">';
478
            $html .= '<link rel="StyleSheet" type="text/css" href="'.$config->get('app.url').'/css/luminous_light.css">';
479
        }
480
481
        if ((isset($params['view']) && ($params['view'] == 'edit' || $params['view'] == 'create')) || (isset($params['ActiveRecordType']) && !isset($params['ActiveRecordID']))) {
482
            $fieldid = ($config->get('security.encrypt.http.fieldnames') ? 'text_field_'.base64_encode(SecurityUtils::encrypt('content')).'_0' : 'text_field_content_0');
483
484
            $html .= '
485
                <script type="text/javascript">
486
                $(document).ready(function() {
487
                    $(\'[id="'.$fieldid.'"]\').pagedownBootstrap({
488
                        \'sanatize\': false
489
                    });
490
                });
491
                </script>';
492
        } elseif (isset($params['view']) && $params['view'] == 'print') {
493
            $html .= '<link rel="StyleSheet" type="text/css" href="'.$config->get('app.url').'/css/print.css">';
494
        }
495
496
        if ($this->record instanceof Article) {
497
            $headerContent = $this->record->get('headerContent');
498
            if ($headerContent != '') {
499
                $html .= $headerContent;
500
            }
501
        }
502
503
        return $html;
504
    }
505
506
    /**
507
     * Callback that inserts the CMS level header.
508
     *
509
     * @return string
510
     *
511
     * @since 1.0
512
     */
513
    public function insert_CMSDisplayStandardHeader_callback()
514
    {
515
        if ($this->request->getParam('token') != null) {
516
            return '';
517
        }
518
519
        if (!$this->record instanceof Article) {
520
            return '';
521
        }
522
523
        $config = ConfigProvider::getInstance();
524
525
        $html = '';
526
527
        if ($config->get('cms.display.standard.header')) {
528
            $html .= '<p><a href="'.$config->get('app.url').'">'.$config->get('app.title').'</a> &nbsp; &nbsp;';
529
            $html .= 'Date Added: <em>'.$this->record->getCreateTS()->getDate().'</em> &nbsp; &nbsp;';
530
            $html .= 'Last Updated: <em>'.$this->record->getUpdateTS()->getDate().'</em> &nbsp; &nbsp;';
531
            $html .= 'Revision: <em>'.$this->record->getVersion().'</em></p>';
532
        }
533
534
        $html .= $config->get('cms.header');
535
536
        return $html;
537
    }
538
539
    /**
540
     * Callback used to render footer content, including comments, votes and print/PDF buttons when
541
     * enabled to do so.
542
     *
543
     * @return string
544
     *
545
     * @since 1.0
546
     */
547
    public function before_displayPageFoot_callback()
548
    {
549
        $config = ConfigProvider::getInstance();
550
        $sessionProvider = $config->get('session.provider.name');
551
        $session = SessionProviderFactory::getInstance($sessionProvider);
552
553
        $html = '';
554
        $params = $this->request->getParams();
555
556
        // this will ensure that direct requests to ActiveRecordController will be re-directed here.
557
        if (isset($this->record) && !$this->record->isTransient()) {
558
            $this->setName($config->get('app.url').$this->request->getURI());
559
            $this->setUnitOfWork(array($config->get('app.url').$this->request->getURI(), $config->get('app.url').$this->request->getURI()));
560
        } else {
561
            $this->setUnitOfWork(array());
562
        }
563
564
        if ($this->record != null) {
565
            if (isset($params['view']) && $params['view'] == 'detailed') {
566
                if ($config->get('cms.display.comments')) {
567
                    $html .= $this->renderComments();
568
                }
569
570
                if ($config->get('cms.display.tags')) {
571
                    $tags = $this->record->getPropObject('tags')->getRelatedObjects();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Alpha\Model\Type\Type as the method getRelatedObjects() does only exist in the following sub-classes of Alpha\Model\Type\Type: Alpha\Model\Type\Relation. 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...
572
573
                    if (count($tags) > 0) {
574
                        $html .= '<p>Tags:';
575
576
                        foreach ($tags as $tag) {
577
                            $html .= ' <a href="'.$config->get('app.url').'/search/'.$tag->get('content').'">'.$tag->get('content').'</a>';
578
                        }
579
                        $html .= '</p>';
580
                    }
581
                }
582
583
                if ($config->get('cms.display.votes')) {
584
                    $rating = $this->record->getArticleScore();
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 getArticleScore() 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...
585
                    $votes = $this->record->getArticleVotes();
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 getArticleVotes() 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...
586
                    $html .= '<p>Average Article User Rating: <strong>'.$rating.'</strong> out of 10 (based on <strong>'.count($votes).'</strong> votes)</p>';
587
                }
588
589
                if (!$this->record->checkUserVoted() && $config->get('cms.voting.allowed')) {
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 checkUserVoted() 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...
590
                    $URL = FrontController::generateSecureURL('act=Alpha\Controller\ActiveRecordController&ActiveRecordType=Alpha\Model\ArticleVote');
591
                    $html .= '<form action="'.$URL.'" method="post" accept-charset="UTF-8">';
592
                    $fieldname = ($config->get('security.encrypt.http.fieldnames') ? base64_encode(SecurityUtils::encrypt('score')) : 'score');
593
                    $html .= '<p>Please rate this article from 1-10 (10 being the best):'.
594
                            '<select name="'.$fieldname.'">'.
595
                            '<option value="1">1'.
596
                            '<option value="2">2'.
597
                            '<option value="3">3'.
598
                            '<option value="4">4'.
599
                            '<option value="5">5'.
600
                            '<option value="6">6'.
601
                            '<option value="7">7'.
602
                            '<option value="8">8'.
603
                            '<option value="9">9'.
604
                            '<option value="10">10'.
605
                            '</select></p>&nbsp;&nbsp;';
606
607
                    $fieldname = ($config->get('security.encrypt.http.fieldnames') ? base64_encode(SecurityUtils::encrypt('articleID')) : 'articleID');
608
                    $html .= '<input type="hidden" name="'.$fieldname.'" value="'.$this->record->getID().'"/>';
609
610
                    $fieldname = ($config->get('security.encrypt.http.fieldnames') ? base64_encode(SecurityUtils::encrypt('personID')) : 'personID');
611
                    $html .= '<input type="hidden" name="'.$fieldname.'" value="'.$session->get('currentUser')->getID().'"/>';
612
613
                    $fieldname = ($config->get('security.encrypt.http.fieldnames') ? base64_encode(SecurityUtils::encrypt('statusMessage')) : 'statusMessage');
614
                    $html .= '<input type="hidden" name="'.$fieldname.'" value="Thank you for rating this article!"/>';
615
616
                    $temp = new Button('submit', 'Vote!', 'voteBut');
617
                    $html .= $temp->render();
618
619
                    $html .= View::renderSecurityFields();
620
                    $html .= '<form>';
621
                }
622
623
                ActiveRecord::disconnect();
624
625
                if ($config->get('cms.allow.print.versions')) {
626
                    $html .= '&nbsp;&nbsp;';
627
                    $temp = new Button("window.open('".$this->record->get('printURL')."')", 'Open Printer Version', 'printBut');
628
                    $html .= $temp->render();
629
                }
630
631
                $html .= '&nbsp;&nbsp;';
632
                if ($config->get('cms.allow.pdf.versions')) {
633
                    $html .= '&nbsp;&nbsp;';
634
                    $temp = new Button("document.location = '".FrontController::generateSecureURL("act=Alpha\Controller\ArticleController&mode=pdf&title=".$this->record->get('title'))."';", 'Open PDF Version', 'pdfBut');
635
                    $html .= $temp->render();
636
                }
637
638
                // render edit button for admins only
639
                if ($session->get('currentUser') instanceof \Alpha\Model\Person && $session->get('currentUser')->inGroup('Admin')) {
640
                    $html .= '&nbsp;&nbsp;';
641
                    $button = new Button("document.location = '".FrontController::generateSecureURL('act=Alpha\Controller\ArticleController&mode=edit&ActiveRecordID='.$this->record->getID())."'", 'Edit', 'editBut');
642
                    $html .= $button->render();
643
                }
644
            }
645
646
            if ($config->get('cms.display.standard.footer')) {
647
                $html .= '<p>Article URL: <a href="'.$this->record->get('URL').'">'.$this->record->get('URL').'</a><br>';
648
                $html .= 'Title: '.$this->record->get('title').'<br>';
649
                $html .= 'Author: '.$this->record->get('author').'</p>';
650
            }
651
        }
652
653
        $html .= $config->get('cms.footer');
654
655
        return $html;
656
    }
657
658
    /**
659
     * Method for displaying the user comments for the article.
660
     *
661
     * @return string
662
     *
663
     * @since 1.0
664
     */
665
    private function renderComments()
666
    {
667
        $config = ConfigProvider::getInstance();
668
        $sessionProvider = $config->get('session.provider.name');
669
        $session = SessionProviderFactory::getInstance($sessionProvider);
670
671
        $html = '';
672
673
        $comments = $this->record->getArticleComments();
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 getArticleComments() 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...
674
        $commentsCount = count($comments);
675
676
        $URL = FrontController::generateSecureURL('act=Alpha\Controller\ActiveRecordController&ActiveRecordType=Alpha\Model\ArticleComment');
677
678
        $fields = array('formAction' => $URL);
679
680
        if ($config->get('cms.display.comments') && $commentsCount > 0) {
681
            $html .= '<h2>There are ['.$commentsCount.'] user comments for this article</h2>';
682
683
            for ($i = 0; $i < $commentsCount; ++$i) {
684
                $view = View::getInstance($comments[$i]);
685
                $html .= $view->markdownView($fields);
686
            }
687
        }
688
689
        if ($session->get('currentUser') != null && $config->get('cms.comments.allowed')) {
690
            $comment = new ArticleComment();
691
            $comment->set('articleID', $this->record->getID());
692
693
            $view = View::getInstance($comment);
694
            $html .= $view->createView($fields);
695
        }
696
697
        return $html;
698
    }
699
}
700