Overview

Namespaces

  • Alpha
    • Controller
      • Front
    • Exception
    • Model
      • Type
    • Task
    • Util
      • Backup
      • Cache
      • Code
        • Highlight
        • Metric
      • Config
      • Convertor
      • Email
      • Extension
      • Feed
      • File
      • Graph
      • Helper
      • Http
        • Filter
        • Session
      • Image
      • Logging
      • Search
      • Security
    • View
      • Renderer
        • Html
        • Json
      • Widget

Classes

  • ActionLog
  • ActiveRecord
  • ActiveRecordProviderFactory
  • ActiveRecordProviderMySQL
  • ActiveRecordProviderSQLite
  • Article
  • ArticleComment
  • ArticleVote
  • BadRequest
  • BlacklistedClient
  • BlacklistedIP
  • Person
  • Rights
  • Tag

Interfaces

  • ActiveRecordProviderInterface
  • Overview
  • Namespace
  • Class
  • Tree
  1: <?php
  2: 
  3: namespace Alpha\Model;
  4: 
  5: use Alpha\Model\Type\String;
  6: use Alpha\Model\Type\DEnum;
  7: use Alpha\Model\Type\Text;
  8: use Alpha\Model\Type\Boolean;
  9: use Alpha\Model\Type\Relation;
 10: use Alpha\Util\Logging\Logger;
 11: use Alpha\Util\Config\Configprovider;
 12: use Alpha\Util\Http\Session\SessionProviderFactory;
 13: use Alpha\Exception\ValidationException;
 14: use Alpha\Exception\FileNotFoundException;
 15: use Alpha\Exception\AlphaException;
 16: use Alpha\Controller\Front\FrontController;
 17: 
 18: /**
 19:  * An article class for the CMS.
 20:  *
 21:  * @since 1.0
 22:  *
 23:  * @author John Collins <dev@alphaframework.org>
 24:  * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
 25:  * @copyright Copyright (c) 2015, John Collins (founder of Alpha Framework).
 26:  * All rights reserved.
 27:  *
 28:  * <pre>
 29:  * Redistribution and use in source and binary forms, with or
 30:  * without modification, are permitted provided that the
 31:  * following conditions are met:
 32:  *
 33:  * * Redistributions of source code must retain the above
 34:  *   copyright notice, this list of conditions and the
 35:  *   following disclaimer.
 36:  * * Redistributions in binary form must reproduce the above
 37:  *   copyright notice, this list of conditions and the
 38:  *   following disclaimer in the documentation and/or other
 39:  *   materials provided with the distribution.
 40:  * * Neither the name of the Alpha Framework nor the names
 41:  *   of its contributors may be used to endorse or promote
 42:  *   products derived from this software without specific
 43:  *   prior written permission.
 44:  *
 45:  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 46:  * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 47:  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 48:  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 49:  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 50:  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 51:  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 52:  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 53:  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 54:  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 55:  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 56:  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 57:  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 58:  * </pre>
 59:  */
 60: class Article extends ActiveRecord
 61: {
 62:     /**
 63:      * The article title.
 64:      *
 65:      * @var Alpha\Model\Type\String
 66:      *
 67:      * @since 1.0
 68:      */
 69:     protected $title;
 70: 
 71:     /**
 72:      * The article site section.
 73:      *
 74:      * @var Alpha\Model\Type\DEnum
 75:      *
 76:      * @since 1.0
 77:      */
 78:     protected $section;
 79: 
 80:     /**
 81:      * The description of the article.
 82:      *
 83:      * @var Alpha\Model\Type\String
 84:      *
 85:      * @since 1.0
 86:      */
 87:     protected $description;
 88: 
 89:     /**
 90:      * Optional custom body onload Javascript.
 91:      *
 92:      * @var Alpha\Model\Type\String
 93:      *
 94:      * @since 1.0
 95:      */
 96:     protected $bodyOnload;
 97: 
 98:     /**
 99:      * Any custom HTML header content (e.g. Javascript) for the article.
100:      *
101:      * @var Alpha\Model\Type\Text
102:      *
103:      * @since 1.0
104:      */
105:     protected $headerContent;
106: 
107:     /**
108:      * The article content.
109:      *
110:      * @var Alpha\Model\Type\Text
111:      *
112:      * @since 1.0
113:      */
114:     protected $content;
115: 
116:     /**
117:      * The author of the article.
118:      *
119:      * @var Alpha\Model\Type\String
120:      *
121:      * @since 1.0
122:      */
123:     protected $author;
124: 
125:     /**
126:      * A boolean to control whether the artcile is publically accessible or not.
127:      *
128:      * @var Alpha\Model\Type\Boolean
129:      *
130:      * @since 1.0
131:      */
132:     protected $published;
133: 
134:     /**
135:      * A Relation containing all of the comments on this article.
136:      *
137:      * @var Alpha\Model\Type\Relation
138:      *
139:      * @since 1.0
140:      */
141:     protected $comments;
142: 
143:     /**
144:      * A Relation containing all of the votes on this article.
145:      *
146:      * @var Alpha\Model\Type\Relation
147:      *
148:      * @since 1.0
149:      */
150:     protected $votes;
151: 
152:     /**
153:      * A Relation containing all of the tags on this article.
154:      *
155:      * @var Alpha\Model\Type\Relation
156:      *
157:      * @since 1.0
158:      */
159:     protected $tags;
160: 
161:     /**
162:      * An array of all of the attributes on this BO which are tagged.
163:      *
164:      * @var array
165:      *
166:      * @since 1.0
167:      */
168:     protected $taggedAttributes = array('title', 'description', 'content');
169: 
170:     /**
171:      * Path to a .text file where the content of this article is stored (optional).
172:      *
173:      * @var string
174:      *
175:      * @since 1.0
176:      */
177:     private $filePath;
178: 
179:     /**
180:      * An array of data display labels for the class properties.
181:      *
182:      * @var array
183:      *
184:      * @since 1.0
185:      */
186:     protected $dataLabels = array('OID' => 'Article ID#', 'title' => 'Title', 'section' => 'Site Section', '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');
187: 
188:     /**
189:      * The name of the database table for the class.
190:      *
191:      * @var string
192:      *
193:      * @since 1.0
194:      */
195:     const TABLE_NAME = 'Article';
196: 
197:     /**
198:      * The URL for this article (transient).
199:      *
200:      * @var string
201:      *
202:      * @since 1.0
203:      */
204:     protected $URL;
205: 
206:     /**
207:      * The print URL for this article (transient).
208:      *
209:      * @var string
210:      *
211:      * @since 1.0
212:      */
213:     protected $printURL;
214: 
215:     /**
216:      * Trace logger.
217:      *
218:      * @var Alpha\Util\Logging\Logger
219:      *
220:      * @since 1.0
221:      */
222:     private static $logger = null;
223: 
224:     /**
225:      * The constructor which sets up some housekeeping attributes.
226:      *
227:      * @since 1.0
228:      */
229:     public function __construct()
230:     {
231:         self::$logger = new Logger('Article');
232: 
233:         // ensure to call the parent constructor
234:         parent::__construct();
235: 
236:         $this->title = new String();
237:         $this->title->setHelper('Please provide a title for the article.');
238:         $this->title->setSize(100);
239:         $this->title->setRule("/\w+/");
240: 
241:         $this->section = new DEnum('Alpha\Model\Article::section');
242: 
243:         $this->description = new String();
244:         $this->description->setHelper('Please provide a brief description of the article.');
245:         $this->description->setSize(200);
246:         $this->description->setRule("/\w+/");
247:         $this->bodyOnload = new String();
248:         $this->content = new Text();
249:         $this->headerContent = new Text();
250:         $this->author = new String();
251:         $this->author->setHelper('Please state the name of the author of this article');
252:         $this->author->setSize(70);
253:         $this->author->setRule("/\w+/");
254:         $this->published = new Boolean(0);
255: 
256:         $this->comments = new Relation();
257:         $this->markTransient('comments');
258: 
259:         $this->votes = new Relation();
260:         $this->markTransient('votes');
261: 
262:         $this->tags = new Relation();
263:         $this->markTransient('tags');
264: 
265:         $this->URL = '';
266:         $this->printURL = '';
267:         // mark the URL attributes as transient
268:         $this->markTransient('URL');
269:         $this->markTransient('printURL');
270: 
271:         // mark title as unique
272:         $this->markUnique('title');
273: 
274:         $this->markTransient('filePath');
275:         $this->markTransient('taggedAttributes');
276: 
277:         $this->setupRels();
278:     }
279: 
280:     /**
281:      * After creating a new Article, tokenize the description field to form a set
282:      * of automated tags and save them.
283:      *
284:      * @since 1.0
285:      */
286:     protected function after_save_callback()
287:     {
288:         if ($this->getVersion() == 1 && $this->tags instanceof \Alpha\Model\Type\Relation) {
289:             // update the empty tags values to reference this OID
290:             $this->tags->setValue($this->OID);
291: 
292:             foreach ($this->taggedAttributes as $tagged) {
293:                 $tags = Tag::tokenize($this->get($tagged), 'Alpha\Model\Article', $this->getOID());
294:                 foreach ($tags as $tag) {
295:                     try {
296:                         $tag->save();
297:                     } catch (ValidationException $e) {
298:                         /*
299:                          * The unique key has most-likely been violated because this BO is already tagged with this
300:                          * value, so we can ignore in this case.
301:                          */
302:                     }
303:                 }
304:             }
305:         }
306: 
307:         $this->setupRels();
308:     }
309: 
310:     /**
311:      * Set up the transient URL attributes for the artcile after it has loaded.
312:      *
313:      * @since 1.0
314:      */
315:     protected function after_loadByAttribute_callback()
316:     {
317:         $this->after_load_callback();
318:     }
319: 
320:     /**
321:      * Set up the transient URL attributes for the article after it has loaded.
322:      *
323:      * @since 1.0
324:      */
325:     protected function after_load_callback()
326:     {
327:         $config = ConfigProvider::getInstance();
328: 
329:         $this->URL = $config->get('app.url').'/a/'.str_replace(' ', $config->get('cms.url.title.separator'), $this->title->getValue());
330: 
331:         $this->printURL = $config->get('app.url').'/a/'.str_replace(' ', $config->get('cms.url.title.separator'), $this->title->getValue()).'/print';
332: 
333:         $this->setupRels();
334:     }
335: 
336:     /**
337:      * Gets an array of the OIDs of the most recent articles added to the system (by date), from the newest
338:      * article to the amount specified by the $limit.
339:      *
340:      * @param int    $limit
341:      * @param string $excludeID
342:      *
343:      * @return array
344:      *
345:      * @since 1.0
346:      *
347:      * @throws Alpha\Exception\AlphaException
348:      */
349:     public function loadRecentWithLimit($limit, $excludeID = '')
350:     {
351:         if ($excludeID != '') {
352:             $denum = new DEnum('Alpha\Model\Article::section');
353:             $excludeID = $denum->getOptionID($excludeID);
354:         }
355: 
356:         $sqlQuery = 'SELECT OID FROM '.$this->getTableName()." WHERE published='1' AND section!='$excludeID' ORDER BY created_ts DESC LIMIT 0, $limit;";
357: 
358:         $result = $this->query($sqlQuery);
359: 
360:         $OIDs = array();
361: 
362:         foreach ($result as $row) {
363:             array_push($OIDs, $row['OID']);
364:         }
365: 
366:         return $OIDs;
367:     }
368: 
369:     /**
370:      * Generates the location of the attachments folder for this article.
371:      *
372:      * @return string
373:      *
374:      * @since 1.0
375:      */
376:     public function getAttachmentsLocation()
377:     {
378:         $config = ConfigProvider::getInstance();
379: 
380:         return $config->get('app.file.store.dir').'attachments/article_'.$this->getID();
381:     }
382: 
383:     /**
384:      * Generates the URL of the attachments folder for this article.
385:      *
386:      * @return string
387:      *
388:      * @since 1.0
389:      */
390:     public function getAttachmentsURL()
391:     {
392:         $config = ConfigProvider::getInstance();
393: 
394:         return $config->get('app.url').'/attachments/article_'.$this->getID();
395:     }
396: 
397:     /**
398:      * Generates a secure URL for downloading an attachment file via the ViewAttachment controller.
399:      *
400:      * @param string $filename
401:      *
402:      * @since 1.0
403:      */
404:     public function getAttachmentSecureURL($filename)
405:     {
406:         $config = ConfigProvider::getInstance();
407: 
408:         return FrontController::generateSecureURL('act=Alpha\\Controller\\AttachmentController&articleOID='.$this->getOID().'&filename='.$filename);
409:     }
410: 
411:     /**
412:      * Creates the attachment folder for the article on the server.
413:      *
414:      * @since 1.0
415:      *
416:      * @throws Alpha\Exception\AlphaException
417:      */
418:     public function createAttachmentsFolder()
419:     {
420:         // create the attachment directory for the article
421:         try {
422:             mkdir($this->getAttachmentsLocation());
423:         } catch (\Exception $e) {
424:             throw new AlphaException('Unable to create the folder ['.$this->getAttachmentsLocation().'] for the article.');
425:         }
426: 
427:         // ...and set write permissions on the folder
428:         try {
429:             chmod($this->getAttachmentsLocation(), 0777);
430:         } catch (\Exception $e) {
431:             throw new AlphaException('Unable to set write permissions on the folder ['.$this->getAttachmentsLocation().'].');
432:         }
433:     }
434: 
435:     /**
436:      * Method for returning the calculated score for this article.
437:      *
438:      * @return float
439:      *
440:      * @since 1.0
441:      */
442:     public function getArticleScore()
443:     {
444:         $votes = $this->getArticleVotes();
445: 
446:         $score = 0;
447:         $total_score = 0;
448:         $vote_count = count($votes);
449: 
450:         for ($i = 0; $i < $vote_count; ++$i) {
451:             $total_score += $votes[$i]->get('score');
452:         }
453: 
454:         if ($vote_count > 0) {
455:             $score = $total_score / $vote_count;
456:         }
457: 
458:         return sprintf('%01.2f', $score);
459:     }
460: 
461:     /**
462:      * Method for fetching all of the votes for this article.
463:      *
464:      * @return array An array of ArticleVote objects
465:      *
466:      * @since 1.0
467:      */
468:     public function getArticleVotes()
469:     {
470:         $votes = $this->votes->getRelatedObjects();
471: 
472:         return $votes;
473:     }
474: 
475:     /**
476:      * Method to determine if the logged-in user has already voted for this article.
477:      *
478:      * @return bool True if they have voted already, false otherwise
479:      *
480:      * @since 1.0
481:      *
482:      * @throws Alpha\Exception\AlphaException
483:      */
484:     public function checkUserVoted()
485:     {
486:         $config = ConfigProvider::getInstance();
487:         $sessionProvider = $config->get('session.provider.name');
488:         $session = SessionProviderFactory::getInstance($sessionProvider);
489:         // just going to return true if nobody is logged in
490:         if ($session->get('currentUser') == null) {
491:             return true;
492:         }
493: 
494:         $userID = $session->get('currentUser')->getID();
495: 
496:         $vote = new ArticleVote();
497: 
498:         $sqlQuery = 'SELECT COUNT(*) AS usersVote FROM '.$vote->getTableName()." WHERE articleOID='".$this->OID."' AND personOID='".$userID."';";
499: 
500:         $result = $this->query($sqlQuery);
501: 
502:         if (!isset($result[0])) {
503:             throw new AlphaException('Failed to check if the current user voted for the article ['.$this->OID.'], query ['.$sqlQuery.']');
504: 
505:             return false;
506:         }
507: 
508:         $row = $result[0];
509: 
510:         if ($row['usersVote'] == '0') {
511:             return false;
512:         } else {
513:             return true;
514:         }
515:     }
516: 
517:     /**
518:      * Method for fetching all of the comments for this article.
519:      *
520:      * @return array An array of ArticleComment objects
521:      *
522:      * @since 1.0
523:      */
524:     public function getArticleComments()
525:     {
526:         $comments = $this->comments->getRelatedObjects();
527: 
528:         return $comments;
529:     }
530: 
531:     /**
532:      * Loads the content of the ArticleObject from the specified file path.
533:      *
534:      * @param $filePath
535:      *
536:      * @since 1.0
537:      *
538:      * @throws Alpha\Exception\FileNotFoundException
539:      */
540:     public function loadContentFromFile($filePath)
541:     {
542:         try {
543:             $this->content->setValue(file_get_contents($filePath));
544:             $this->filePath = $filePath;
545:         } catch (\Exception $e) {
546:             throw new FileNotFoundException($e->getMessage());
547:         }
548:     }
549: 
550:     /**
551:      * Returns true if the article content was loaded from a .text file, false otherwise.
552:      *
553:      * @return bool
554:      *
555:      * @since 1.0
556:      */
557:     public function isLoadedFromFile()
558:     {
559:         return $this->filePath == '' ? false : true;
560:     }
561: 
562:     /**
563:      * Returns the timestamp of when the content .text file for this article was last
564:      * modified.
565:      *
566:      * @return string
567:      *
568:      * @since 1.0
569:      *
570:      * @throws Alpha\Exception\FileNotFoundException
571:      */
572:     public function getContentFileDate()
573:     {
574:         if ($this->filePath != '') {
575:             try {
576:                 return date('Y-m-d H:i:s', filemtime($this->filePath));
577:             } catch (\Exception $e) {
578:                 throw new FileNotFoundException($e->getMessage());
579:             }
580:         } else {
581:             throw new FileNotFoundException('Error trying to access an article content file when none is set!');
582:         }
583:     }
584: 
585:     /**
586:      * Sets up the Relation definitions on this record object.
587:      *
588:      * @since 2.0
589:      */
590:     protected function setupRels()
591:     {
592:         $this->comments->setValue($this->OID);
593:         $this->comments->setRelatedClass('Alpha\Model\ArticleComment');
594:         $this->comments->setRelatedClassField('articleOID');
595:         $this->comments->setRelatedClassDisplayField('content');
596:         $this->comments->setRelationType('ONE-TO-MANY');
597: 
598:         $this->votes->setValue($this->OID);
599:         $this->votes->setRelatedClass('Alpha\Model\ArticleVote');
600:         $this->votes->setRelatedClassField('articleOID');
601:         $this->votes->setRelatedClassDisplayField('score');
602:         $this->votes->setRelationType('ONE-TO-MANY');
603: 
604:         $this->tags->setRelatedClass('Alpha\Model\Tag');
605:         $this->tags->setRelatedClassField('taggedOID');
606:         $this->tags->setRelatedClassDisplayField('content');
607:         $this->tags->setRelationType('ONE-TO-MANY');
608:         $this->tags->setTaggedClass(get_class($this));
609:         $this->tags->setValue($this->OID);
610:     }
611: }
612: 
Alpha Framework 2.0.4 API Documentation API documentation generated by ApiGen 2.8.0