Issues (195)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

Alpha/Model/Article.php (3 issues)

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\Model;
4
5
use Alpha\Model\Type\SmallText;
6
use Alpha\Model\Type\DEnum;
7
use Alpha\Model\Type\Text;
8
use Alpha\Model\Type\LargeText;
9
use Alpha\Model\Type\Boolean;
10
use Alpha\Model\Type\Relation;
11
use Alpha\Util\Config\Configprovider;
12
use Alpha\Util\Logging\Logger;
13
use Alpha\Util\Service\ServiceFactory;
14
use Alpha\Exception\ValidationException;
15
use Alpha\Exception\FileNotFoundException;
16
use Alpha\Exception\AlphaException;
17
use Alpha\Controller\Front\FrontController;
18
19
/**
20
 * An article class for the CMS.
21
 *
22
 * @since 1.0
23
 *
24
 * @author John Collins <[email protected]>
25
 * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
26
 * @copyright Copyright (c) 2018, John Collins (founder of Alpha Framework).
27
 * All rights reserved.
28
 *
29
 * <pre>
30
 * Redistribution and use in source and binary forms, with or
31
 * without modification, are permitted provided that the
32
 * following conditions are met:
33
 *
34
 * * Redistributions of source code must retain the above
35
 *   copyright notice, this list of conditions and the
36
 *   following disclaimer.
37
 * * Redistributions in binary form must reproduce the above
38
 *   copyright notice, this list of conditions and the
39
 *   following disclaimer in the documentation and/or other
40
 *   materials provided with the distribution.
41
 * * Neither the name of the Alpha Framework nor the names
42
 *   of its contributors may be used to endorse or promote
43
 *   products derived from this software without specific
44
 *   prior written permission.
45
 *
46
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
47
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
48
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
49
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
50
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
51
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
52
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
53
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
54
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
55
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
56
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
57
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
58
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
59
 * </pre>
60
 */
61
class Article extends ActiveRecord
62
{
63
    /**
64
     * The article title.
65
     *
66
     * @var \Alpha\Model\Type\SmallText
67
     *
68
     * @since 1.0
69
     */
70
    protected $title;
71
72
    /**
73
     * The description of the article.
74
     *
75
     * @var \Alpha\Model\Type\SmallText
76
     *
77
     * @since 1.0
78
     */
79
    protected $description;
80
81
    /**
82
     * Optional custom body onload Javascript.
83
     *
84
     * @var \Alpha\Model\Type\SmallText
85
     *
86
     * @since 1.0
87
     */
88
    protected $bodyOnload;
89
90
    /**
91
     * Any custom HTML header content (e.g. Javascript) for the article.
92
     *
93
     * @var \Alpha\Model\Type\Text
94
     *
95
     * @since 1.0
96
     */
97
    protected $headerContent;
98
99
    /**
100
     * The article content.
101
     *
102
     * @var \Alpha\Model\Type\LargeText
103
     *
104
     * @since 1.0
105
     */
106
    protected $content;
107
108
    /**
109
     * The author of the article.
110
     *
111
     * @var \Alpha\Model\Type\SmallText
112
     *
113
     * @since 1.0
114
     */
115
    protected $author;
116
117
    /**
118
     * A boolean to control whether the artcile is publically accessible or not.
119
     *
120
     * @var \Alpha\Model\Type\Boolean
121
     *
122
     * @since 1.0
123
     */
124
    protected $published;
125
126
    /**
127
     * A Relation containing all of the comments on this article.
128
     *
129
     * @var \Alpha\Model\Type\Relation
130
     *
131
     * @since 1.0
132
     */
133
    protected $comments;
134
135
    /**
136
     * A Relation containing all of the votes on this article.
137
     *
138
     * @var \Alpha\Model\Type\Relation
139
     *
140
     * @since 1.0
141
     */
142
    protected $votes;
143
144
    /**
145
     * A Relation containing all of the tags on this article.
146
     *
147
     * @var \Alpha\Model\Type\Relation
148
     *
149
     * @since 1.0
150
     */
151
    protected $tags;
152
153
    /**
154
     * An array of all of the attributes on this Record which are tagged.
155
     *
156
     * @var array
157
     *
158
     * @since 1.0
159
     */
160
    protected $taggedAttributes = array('title', 'description', 'content');
161
162
    /**
163
     * Path to a .text file where the content of this article is stored (optional).
164
     *
165
     * @var string
166
     *
167
     * @since 1.0
168
     */
169
    private $filePath;
170
171
    /**
172
     * An array of data display labels for the class properties.
173
     *
174
     * @var array
175
     *
176
     * @since 1.0
177
     */
178
    protected $dataLabels = array('ID' => 'Article ID#', 'title' => 'Title', 'description' => 'Description', 'bodyOnload' => 'Body onload Javascript', 'content' => 'Content', 'headerContent' => 'HTML Header Content', 'author' => 'Author', 'created_ts' => 'Date Added', 'updated_ts' => 'Date of last Update', 'published' => 'Published', 'URL' => 'URL', 'printURL' => 'Printer version URL', 'comments' => 'Comments', 'votes' => 'Votes', 'tags' => 'Tags');
179
180
    /**
181
     * The name of the database table for the class.
182
     *
183
     * @var string
184
     *
185
     * @since 1.0
186
     */
187
    const TABLE_NAME = 'Article';
188
189
    /**
190
     * The URL for this article (transient).
191
     *
192
     * @var string
193
     *
194
     * @since 1.0
195
     */
196
    protected $URL;
197
198
    /**
199
     * The print URL for this article (transient).
200
     *
201
     * @var string
202
     *
203
     * @since 1.0
204
     */
205
    protected $printURL;
206
207
    /**
208
     * Trace logger.
209
     *
210
     * @var \Alpha\Util\Logging\Logger
211
     *
212
     * @since 1.0
213
     */
214
    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...
215
216
    /**
217
     * The constructor which sets up some housekeeping attributes.
218
     *
219
     * @since 1.0
220
     */
221
    public function __construct()
222
    {
223
        self::$logger = new Logger('Article');
224
225
        $config = ConfigProvider::getInstance();
226
        $separator = $config->get('cms.url.title.separator');
227
228
        // ensure to call the parent constructor
229
        parent::__construct();
230
231
        $this->title = new SmallText();
232
        $this->title->setHelper('Please provide a title for the article. Note that the '.$separator.' character is not allowed!');
233
        $this->title->setSize(100);
234
        $this->title->setRule('/^[^'.$separator.']*$/');
235
236
        $this->description = new SmallText();
237
        $this->description->setHelper('Please provide a brief description of the article.');
238
        $this->description->setSize(200);
239
        $this->description->setRule("/\w+/");
240
        $this->bodyOnload = new SmallText();
241
        $this->content = new LargeText();
242
        $this->headerContent = new Text();
243
        $this->author = new SmallText();
244
        $this->author->setHelper('Please state the name of the author of this article');
245
        $this->author->setSize(70);
246
        $this->author->setRule("/\w+/");
247
        $this->published = new Boolean(0);
0 ignored issues
show
0 is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
248
249
        $this->comments = new Relation();
250
        $this->markTransient('comments');
251
252
        $this->votes = new Relation();
253
        $this->markTransient('votes');
254
255
        $this->tags = new Relation();
256
        $this->markTransient('tags');
257
258
        $this->URL = '';
259
        $this->printURL = '';
260
        // mark the URL attributes as transient
261
        $this->markTransient('URL');
262
        $this->markTransient('printURL');
263
264
        // mark title as unique
265
        $this->markUnique('title');
266
267
        $this->markTransient('filePath');
268
        $this->markTransient('taggedAttributes');
269
270
        $this->setupRels();
271
    }
272
273
    /**
274
     * After creating a new Article, tokenize the description field to form a set
275
     * of automated tags and save them.
276
     *
277
     * @since 1.0
278
     */
279
    protected function after_save_callback()
280
    {
281
        if ($this->getVersion() == 1 && $this->tags instanceof \Alpha\Model\Type\Relation) {
282
            // update the empty tags values to reference this ID
283
            $this->tags->setValue($this->ID);
284
285
            foreach ($this->taggedAttributes as $tagged) {
286
                $tags = Tag::tokenize($this->get($tagged), 'Alpha\Model\Article', $this->getID());
287
                foreach ($tags as $tag) {
288
                    try {
289
                        $tag->save();
290
                    } catch (ValidationException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
291
                        /*
292
                         * The unique key has most-likely been violated because this Record is already tagged with this
293
                         * value, so we can ignore in this case.
294
                         */
295
                    }
296
                }
297
            }
298
        }
299
300
        $this->setupRels();
301
    }
302
303
    /**
304
     * Set up the transient URL attributes for the artcile after it has loaded.
305
     *
306
     * @since 1.0
307
     */
308
    protected function after_loadByAttribute_callback()
309
    {
310
        $this->{'after_load_callback'}();
311
    }
312
313
    /**
314
     * Set up the transient URL attributes for the article after it has loaded.
315
     *
316
     * @since 1.0
317
     */
318
    protected function after_load_callback()
319
    {
320
        $config = ConfigProvider::getInstance();
321
322
        $this->URL = $config->get('app.url').'/a/'.str_replace(' ', $config->get('cms.url.title.separator'), $this->title->getValue());
323
324
        $this->printURL = $config->get('app.url').'/a/'.str_replace(' ', $config->get('cms.url.title.separator'), $this->title->getValue()).'/print';
325
326
        $this->setupRels();
327
    }
328
329
    /**
330
     * Gets an array of the IDs of the most recent articles added to the system (by date), from the newest
331
     * article to the amount specified by the $limit.
332
     *
333
     * @param int    $limit
334
     *
335
     * @return array
336
     *
337
     * @since 1.0
338
     *
339
     * @throws \Alpha\Exception\AlphaException
340
     */
341
    public function loadRecentWithLimit($limit)
342
    {
343
        $sqlQuery = 'SELECT ID FROM '.$this->getTableName()." WHERE published='1' ORDER BY created_ts DESC LIMIT 0, $limit;";
344
345
        $result = $this->query($sqlQuery);
346
347
        $IDs = array();
348
349
        foreach ($result as $row) {
350
            array_push($IDs, $row['ID']);
351
        }
352
353
        return $IDs;
354
    }
355
356
    /**
357
     * Generates the location of the attachments folder for this article.
358
     *
359
     * @return string
360
     *
361
     * @since 1.0
362
     */
363
    public function getAttachmentsLocation()
364
    {
365
        $config = ConfigProvider::getInstance();
366
367
        return $config->get('app.file.store.dir').'attachments/article_'.$this->getID();
368
    }
369
370
    /**
371
     * Generates the URL of the attachments folder for this article.
372
     *
373
     * @return string
374
     *
375
     * @since 1.0
376
     */
377
    public function getAttachmentsURL()
378
    {
379
        $config = ConfigProvider::getInstance();
380
381
        return $config->get('app.url').'/attachments/article_'.$this->getID();
382
    }
383
384
    /**
385
     * Generates a secure URL for downloading an attachment file via the ViewAttachment controller.
386
     *
387
     * @param string $filename
388
     *
389
     * @since 1.0
390
     */
391
    public function getAttachmentSecureURL($filename)
392
    {
393
        return FrontController::generateSecureURL('act=Alpha\\Controller\\AttachmentController&articleID='.$this->getID().'&filename='.$filename);
394
    }
395
396
    /**
397
     * Creates the attachment folder for the article on the server.
398
     *
399
     * @since 1.0
400
     *
401
     * @throws \Alpha\Exception\AlphaException
402
     */
403
    public function createAttachmentsFolder()
404
    {
405
        // create the attachment directory for the article
406
        try {
407
            mkdir($this->getAttachmentsLocation());
408
        } catch (\Exception $e) {
409
            throw new AlphaException('Unable to create the folder ['.$this->getAttachmentsLocation().'] for the article.');
410
        }
411
412
        // ...and set write permissions on the folder
413
        try {
414
            chmod($this->getAttachmentsLocation(), 0777);
415
        } catch (\Exception $e) {
416
            throw new AlphaException('Unable to set write permissions on the folder ['.$this->getAttachmentsLocation().'].');
417
        }
418
    }
419
420
    /**
421
     * Method for returning the calculated score for this article.
422
     *
423
     * @return string
424
     *
425
     * @since 1.0
426
     */
427
    public function getArticleScore()
428
    {
429
        $votes = $this->getArticleVotes();
430
431
        $score = 0;
432
        $total_score = 0;
433
        $vote_count = count($votes);
434
435
        for ($i = 0; $i < $vote_count; ++$i) {
436
            $total_score += $votes[$i]->get('score');
437
        }
438
439
        if ($vote_count > 0) {
440
            $score = $total_score/$vote_count;
441
        }
442
443
        return sprintf('%01.2f', $score);
444
    }
445
446
    /**
447
     * Method for fetching all of the votes for this article.
448
     *
449
     * @return array An array of ArticleVote objects
450
     *
451
     * @since 1.0
452
     */
453
    public function getArticleVotes()
454
    {
455
        $votes = $this->votes->getRelated();
456
457
        return $votes;
458
    }
459
460
    /**
461
     * Method to determine if the logged-in user has already voted for this article.
462
     *
463
     * @return bool True if they have voted already, false otherwise
464
     *
465
     * @since 1.0
466
     *
467
     * @throws \Alpha\Exception\AlphaException
468
     */
469
    public function checkUserVoted()
470
    {
471
        $config = ConfigProvider::getInstance();
472
        $sessionProvider = $config->get('session.provider.name');
473
        $session = ServiceFactory::getInstance($sessionProvider, 'Alpha\Util\Http\Session\SessionProviderInterface');
474
        // just going to return true if nobody is logged in
475
        if ($session->get('currentUser') == null) {
476
            return true;
477
        }
478
479
        $userID = $session->get('currentUser')->getID();
480
481
        $vote = new ArticleVote();
482
483
        $sqlQuery = 'SELECT COUNT(*) AS usersVote FROM '.$vote->getTableName()." WHERE articleID='".$this->ID."' AND personID='".$userID."';";
484
485
        $result = $this->query($sqlQuery);
486
487
        if (!isset($result[0])) {
488
            throw new AlphaException('Failed to check if the current user voted for the article ['.$this->ID.'], query ['.$sqlQuery.']');
489
        }
490
491
        $row = $result[0];
492
493
        if ($row['usersVote'] == '0') {
494
            return false;
495
        } else {
496
            return true;
497
        }
498
    }
499
500
    /**
501
     * Method for fetching all of the comments for this article.
502
     *
503
     * @return array An array of ArticleComment objects
504
     *
505
     * @since 1.0
506
     */
507
    public function getArticleComments()
508
    {
509
        $comments = $this->comments->getRelated();
510
511
        return $comments;
512
    }
513
514
    /**
515
     * Loads the content of the ArticleObject from the specified file path.
516
     *
517
     * @param string $filePath
518
     *
519
     * @since 1.0
520
     *
521
     * @throws \Alpha\Exception\FileNotFoundException
522
     */
523
    public function loadContentFromFile($filePath)
524
    {
525
        try {
526
            $this->content->setValue(file_get_contents($filePath));
527
            $this->filePath = $filePath;
528
        } catch (\Exception $e) {
529
            throw new FileNotFoundException($e->getMessage());
530
        }
531
    }
532
533
    /**
534
     * Returns true if the article content was loaded from a .text file, false otherwise.
535
     *
536
     * @return bool
537
     *
538
     * @since 1.0
539
     */
540
    public function isLoadedFromFile()
541
    {
542
        return $this->filePath == '' ? false : true;
543
    }
544
545
    /**
546
     * Returns the timestamp of when the content .text file for this article was last
547
     * modified.
548
     *
549
     * @return string
550
     *
551
     * @since 1.0
552
     *
553
     * @throws \Alpha\Exception\FileNotFoundException
554
     */
555
    public function getContentFileDate()
556
    {
557
        if ($this->filePath != '') {
558
            try {
559
                return date('Y-m-d H:i:s', filemtime($this->filePath));
560
            } catch (\Exception $e) {
561
                throw new FileNotFoundException($e->getMessage());
562
            }
563
        } else {
564
            throw new FileNotFoundException('Error trying to access an article content file when none is set!');
565
        }
566
    }
567
568
    /**
569
     * Sets up the Relation definitions on this record object.
570
     *
571
     * @since 2.0
572
     */
573
    protected function setupRels()
574
    {
575
        $this->comments->setValue($this->ID);
576
        $this->comments->setRelatedClass('Alpha\Model\ArticleComment');
577
        $this->comments->setRelatedClassField('articleID');
578
        $this->comments->setRelatedClassDisplayField('content');
579
        $this->comments->setRelationType('ONE-TO-MANY');
580
581
        $this->votes->setValue($this->ID);
582
        $this->votes->setRelatedClass('Alpha\Model\ArticleVote');
583
        $this->votes->setRelatedClassField('articleID');
584
        $this->votes->setRelatedClassDisplayField('score');
585
        $this->votes->setRelationType('ONE-TO-MANY');
586
587
        $this->tags->setRelatedClass('Alpha\Model\Tag');
588
        $this->tags->setRelatedClassField('taggedID');
589
        $this->tags->setRelatedClassDisplayField('content');
590
        $this->tags->setRelationType('ONE-TO-MANY');
591
        $this->tags->setTaggedClass(get_class($this));
592
        $this->tags->setValue($this->ID);
593
    }
594
}
595