      1: <?php
      2:
      3: /**
      4:  * Base feed class for generating syndication feeds
      5:  *
      6:  * @package alpha::util::feeds
      7:  * @since 1.0
      8:  * @author John Collins <dev@alphaframework.org>
      9:  * @version $Id: AlphaFeed.inc 1496 2012-02-12 20:32:21Z alphadev $
     10:  * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
     11:  * @copyright Copyright (c) 2012, John Collins (founder of Alpha Framework).
     12:  * All rights reserved.
     13:  *
     14:  * <pre>
     15:  * Redistribution and use in source and binary forms, with or
     16:  * without modification, are permitted provided that the
     17:  * following conditions are met:
     18:  *
     19:  * * Redistributions of source code must retain the above
     20:  *   copyright notice, this list of conditions and the
     21:  *   following disclaimer.
     22:  * * Redistributions in binary form must reproduce the above
     23:  *   copyright notice, this list of conditions and the
     24:  *   following disclaimer in the documentation and/or other
     25:  *   materials provided with the distribution.
     26:  * * Neither the name of the Alpha Framework nor the names
     27:  *   of its contributors may be used to endorse or promote
     28:  *   products derived from this software without specific
     29:  *   prior written permission.
     30:  *
     31:  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
     32:  * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
     33:  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
     34:  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     35:  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
     36:  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     37:  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
     38:  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     39:  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     40:  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     41:  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
     42:  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     43:  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     44:  * </pre>
     45:  *
     46:  */
     47: abstract class AlphaFeed {
     48:     /**
     49:      * The DOMDocument object used to create the feed
     50:      *
     51:      * @var DOMDocument
     52:      * @since 1.0
     53:      */
     54:     protected $rssDoc;
     55:
     56:     /**
     57:      * The DOMElement object used to hold the item or entry elements
     58:      *
     59:      * @var DOMElement
     60:      * @since 1.0
     61:      */
     62:     protected $docElement;
     63:
     64:     /**
     65:      * Holds the DOMElement to which metadata is added for the feed
     66:      *
     67:      * @var DOMElement
     68:      * @since 1.0
     69:      */
     70:     protected $root;
     71:
     72:     /**
     73:      * The actual root tag used in each feed type
     74:      *
     75:      * @var string
     76:      * @since 1.0
     77:      */
     78:     protected $rootTag;
     79:
     80:     /**
     81:      * An array of feed items
     82:      *
     83:      * @var array
     84:      * @since 1.0
     85:      */
     86:     protected $items;
     87:
     88:     /**
     89:      * If the feed format has a channel or not
     90:      *
     91:      * @var boolean
     92:      * @since 1.0
     93:      */
     94:     protected $hasChannel = true;
     95:
     96:     /**
     97:      * Maps the tags to the feed-specific tags
     98:      *
     99:      * @var array
    100:      * @since 1.0
    101:      */
    102:     protected $tagMap = array('item'=>'item','feeddesc'=>'description','itemdesc'=>'description');
    103:
    104:     /**
    105:      * The BO which we will serve up in this feed
    106:      *
    107:      * @var AlphaDAO
    108:      * @since 1.0
    109:      */
    110:     private $BO;
    111:
    112:     /**
    113:      * An array containing BO field names -> RSS field name mappings
    114:      *
    115:      * @var array
    116:      * @since 1.0
    117:      */
    118:     protected $fieldNameMappings;
    119:
    120:     /**
    121:      * The XML namespace to use in the generated feed
    122:      *
    123:      * @var string
    124:      */
    125:     protected $nameSpace;
    126:
    127:     /**
    128:      * Trace logger
    129:      *
    130:      * @var Logger
    131:      * @since 1.0
    132:      */
    133:     private static $logger = null;
    134:
    135:     /**
    136:      * The constructor
    137:      *
    138:      * @param string $BOName The classname of the BO to render a feed for.
    139:      * @param string $title The title of the feed.
    140:      * @param string $url The base URL for the feed.
    141:      * @param string $description The description of the feed.
    142:      * @param string $pubDate The publish date, only used in Atom feeds.
    143:      * @param integer $id The feed id, only used in Atom feeds.
    144:      * @param integer $limit The amount of items to render in the feed.
    145:      * @throws IllegalArguementException
    146:      * @since 1.0
    147:      */
    148:     public function __construct($BOName, $title, $url, $description, $pubDate = null, $id = null, $limit = 10) {
    149:         self::$logger = new Logger('AlphaFeed');
    150:         self::$logger->debug('>>__construct(BOName=['.$BOName.'], title=['.$title.'], url=['.$url.'], description=['.$description.'], pubDate=['.$pubDate.'], id=['.$id.'], limit=['.$limit.'])');
    151:
    152:         $this->rssDoc = new DOMDocument();
    153:         $this->rssDoc->loadXML($this->rootTag);
    154:         $this->docElement = $this->rssDoc->documentElement;
    155:
    156:         try {
    157:             AlphaDAO::loadClassDef($BOName);
    158:             $this->BO = new $BOName;
    159:         }catch (IllegalArguementException $e) {
    160:             self::$logger->error('Unable to load the class definition for the class ['.$BOName.'] while trying to generate a feed!');
    161:             throw $e;
    162:         }
    163:
    164:         if ($this->hasChannel) {
    165:             $root = $this->createFeedElement('channel');
    166:             $this->root = $this->docElement->appendChild($root);
    167:         }else{
    168:             $this->root = $this->docElement;
    169:         }
    170:
    171:         $this->createRSSNode('feed', $this->root, $title, $url, $description, $pubDate, $id);
    172:
    173:         self::$logger->debug('<<__construct');
    174:     }
    175:
    176:     /**
    177:      * Method to load all of the BO items to the feed from the database, from the newest to the
    178:      * $limit provided
    179:      *
    180:      * @param integer $limit The amount of items to render in the feed.
    181:      * @param string $sortBy The name of the field to sort the feed by.
    182:      * @since 1.0
    183:      */
    184:     public function loadBOs($limit, $sortBy) {
    185:
    186:         $BOs = $this->BO->loadAll(0, $limit, $sortBy, 'DESC');
    187:
    188:         AlphaDAO::disconnect();
    189:
    190:         foreach($BOs as $BO) {
    191:             $this->addBO($BO);
    192:         }
    193:     }
    194:
    195:     /**
    196:      * Method for adding a BO to the current feed
    197:      *
    198:      * @param AlphaDAO $BO
    199:      */
    200:     public function addBO($BO) {
    201:         $title = $BO->get($this->fieldNameMappings['title']);
    202:         $url = $BO->get($this->fieldNameMappings['url']);
    203:
    204:         if(isset($this->fieldNameMappings['description'])) {
    205:             $description = $BO->get($this->fieldNameMappings['description']);
    206:         }else{
    207:             $description = '';
    208:         }
    209:
    210:         if(isset($this->fieldNameMappings['pubDate'])) {
    211:             $dateTS = strtotime($BO->get($this->fieldNameMappings['pubDate']));
    212:             $pubDate = date(DATE_ATOM, $dateTS);
    213:         }else{
    214:             $pubDate = '';
    215:         }
    216:
    217:         if(isset($this->fieldNameMappings['id']))
    218:             $id = $BO->get($this->fieldNameMappings['id']);
    219:         else
    220:             $id = '';
    221:
    222:         $this->addItem($title, $url, $description, $pubDate, $id);
    223:     }
    224:
    225:     /**
    226:      * Method for mapping BO fieldnames to feed field names
    227:      *
    228:      * @param string $title The title of the feed.
    229:      * @param string $url The base URL for the feed.
    230:      * @param string $description The description of the feed.
    231:      * @param string $pubDate The publish date, only used in Atom feeds.
    232:      * @param integer $id The feed id, only used in Atom feeds.
    233:      * @since 1.0
    234:      */
    235:     public function setFieldMappings($title, $url, $description=null, $pubDate=null, $id=null) {
    236:         $this->fieldNameMappings = array(
    237:             'title' => $title,
    238:             'url' => $url
    239:         );
    240:
    241:         if(isset($description))
    242:             $this->fieldNameMappings['description'] = $description;
    243:
    244:         if(isset($pubDate))
    245:             $this->fieldNameMappings['pubDate'] = $pubDate;
    246:
    247:         if(isset($id))
    248:             $this->fieldNameMappings['id'] = $id;
    249:     }
    250:
    251:     /**
    252:      * Method for creating a new feed element
    253:      *
    254:      * @param string $name The name of the element.
    255:      * @param string $value The value of the element.
    256:      * @return DOMElement
    257:      * @since 1.0
    258:      */
    259:     protected function createFeedElement($name, $value=null) {
    260:         $value = htmlspecialchars($value);
    261:
    262:         if($this->nameSpace == null) {
    263:             return $this->rssDoc->createElement($name, $value);
    264:         }else{
    265:             return $this->rssDoc->createElementNS($this->nameSpace, $name, $value);
    266:         }
    267:     }
    268:
    269:     /**
    270:      * Method for creating link elements (note that Atom has a different format)
    271:      *
    272:      * @param DOMElement $parent The parent element.
    273:      * @param string $url The URL for the link.
    274:      * @since 1.0
    275:      */
    276:     protected function createLink($parent, $url) {
    277:         $link = $this->createFeedElement('link', $url);
    278:         $parent->appendChild($link);
    279:     }
    280:
    281:     /**
    282:      * Method for creating an RSS node with a title, url and description
    283:      *
    284:      * @param integer $type Can be either (item|feed) to indicate the type of node we are creating.
    285:      * @param DOMElement $parent The parent element.
    286:      * @param string $title The title of the feed.
    287:      * @param string $url The base URL for the feed.
    288:      * @param string $description The description of the feed.
    289:      * @param string $pubDate The publish date, only used in Atom feeds.
    290:      * @param integer $id The feed id, only used in Atom feeds.
    291:      * @since 1.0
    292:      * @throws IllegalArguementException
    293:      */
    294:     protected function createRSSNode($type, $parent, $title, $url, $description, $pubDate=null, $id = null) {
    295:         $this->createLink($parent, $url);
    296:         $title = $this->createFeedElement('title', $title);
    297:         $parent->appendChild($title);
    298:
    299:         if ($type == 'item') {
    300:             $titletag = $this->tagMap['itemdesc'];
    301:         }else if ($type == 'feed') {
    302:             $titletag = $this->tagMap['feeddesc'];
    303:         }else{
    304:             throw new IllegalArguementException('The type paramater ['.$type.'] provided is invalid!');
    305:         }
    306:
    307:         $description = $this->createFeedElement($titletag, $description);
    308:         $parent->appendChild($description);
    309:
    310:         // id elements and updated elements are just for Atom!
    311:         if ($id != null) {
    312:             $idnode = $this->createFeedElement('id', $id);
    313:             $parent->appendChild($idnode);
    314:         }
    315:
    316:         if ($pubDate != null) {
    317:             $datenode = $this->createFeedElement('updated', $pubDate);
    318:             $parent->appendChild($datenode);
    319:         }
    320:     }
    321:
    322:     /**
    323:      * Method for adding an item to a feed
    324:      *
    325:      * @param string $title The title of the feed.
    326:      * @param string $url The base URL for the feed.
    327:      * @param string $description The description of the feed.
    328:      * @param string $pubDate The publish date, only used in Atom feeds.
    329:      * @param integer $id The feed id, only used in Atom feeds.
    330:      * @since 1.0
    331:      */
    332:     protected function addItem($title, $url, $description=null, $pubDate=null, $id=null) {
    333:         $item = $this->createFeedElement($this->tagMap['item']);
    334:
    335:         if ($this->docElement->appendChild($item)) {
    336:             $this->createRSSNode('item', $item, $title, $url, $description, $pubDate, $id);
    337:         }
    338:     }
    339:
    340:     /**
    341:      * Returns the formatted XML for the feed as a string
    342:      *
    343:      * @return string
    344:      * @since 1.0
    345:      */
    346:     public function render() {
    347:         if ($this->rssDoc) {
    348:             $this->rssDoc->formatOutput = true;
    349:             return $this->rssDoc->saveXML();
    350:         }else{
    351:             return '';
    352:         }
    353:     }
    354: }
    355:
    356: ?>