* @link http://www.netcommons.org NetCommons Project * @license http://www.netcommons.org/license.txt NetCommons License * @copyright Copyright 2014, NetCommons Project */ App::uses('RssReadersAppModel', 'RssReaders.Model'); /** * RssReaderItem Model * * @author Shohei Nakajima * @package NetCommons\RssReaders\Model */ class RssReaderItem extends RssReadersAppModel { /** * バリデーションルール * __d()を使うため、[self::beforeValidate()](#method_beforeValidate)でセットする * * @var array */ public $validate = array(); /** * belongsTo associations * * @var array */ public $belongsTo = array( 'RssReader' => array( 'className' => 'RssReader', 'foreignKey' => 'rss_reader_id', 'conditions' => '', 'fields' => '', 'order' => '' ) ); /** * Called during validation operations, before validation. Please note that custom * validation rules can be defined in $validate. * * @param array $options Options passed from Model::save(). * @return bool True if validate operation should continue, false to abort * @link http://book.cakephp.org/2.0/en/models/callback-methods.html#beforevalidate * @see Model::save() */ public function beforeValidate($options = array()) { $this->validate = ValidateMerge::merge($this->validate, array( 'rss_reader_id' => array( 'numeric' => array( 'rule' => array('numeric'), 'message' => __d('net_commons', 'Invalid request.'), 'allowEmpty' => false, //'required' => true, ), ), 'title' => array( 'notBlank' => array( 'rule' => array('notBlank'), 'message' => sprintf( __d('net_commons', 'Please input %s.'), __d('rss_readers', 'Title') ), 'required' => true, ), ), 'link' => array( 'notBlank' => array( 'rule' => array('notBlank'), 'message' => sprintf( __d('net_commons', 'Please input %s.'), __d('rss_readers', 'Url') ), 'required' => true, ), 'url' => array( 'rule' => array('url'), 'message' => sprintf( __d('net_commons', 'Unauthorized pattern for %s. Please input the data in %s format.'), __d('rss_readers', 'Url'), __d('net_commons', 'URL') ), 'required' => true, ) ), )); return parent::beforeValidate($options); } /** * RssReaderItemデータの取得 * ※最終取得日時が古い場合、RSSを取得し直す * * @param array $rssReader RssReaderItemデータ * @param int $limit 取得Limit * @return array $rssReaderItems */ public function getRssReaderItems($rssReader, $limit = null) { if (! Hash::get($rssReader, 'RssReader.id')) { return array(); } $this->updateItems($rssReader); $conditions = array( 'rss_reader_id' => $rssReader['RssReader']['id'], ); $rssReaderItems = $this->find('all', array( 'recursive' => -1, 'conditions' => $conditions, 'order' => array('RssReaderItem.last_updated' => 'desc', 'RssReaderItem.id' => 'asc'), 'limit' => $limit )); return $rssReaderItems; } /** * RssReader最終更新日時がキャッシュ時間より超えていた場合、RSSを取得し直す * * @param array $rssReader RssReaderデータ * @return void */ public function updateItems($rssReader) { if (! isset($rssReader['RssReader']['id'])) { return; } $date = new DateTime(); $now = $date->format('Y-m-d H:i:s'); $date = new DateTime($rssReader['RssReader']['modified']); $date->add(new DateInterval(RssReader::CACHE_TIME)); $modified = $date->format('Y-m-d H:i:s'); if ($now < $modified) { return; } $rssReaderItem = $this->serializeXmlToArray($rssReader['RssReader']['url']); if (! $rssReaderItem) { // Xmlが取得できない場合、validationのエラーにする $this->RssReader->invalidate('url', __d('rss_readers', 'Feed Not Found.')); return; } $rssReaderItem = Hash::insert( $rssReaderItem, '{n}.rss_reader_id', $rssReader['RssReader']['id'] ); $this->updateRssReaderItems( array('RssReader' => $rssReader['RssReader'], 'RssReaderItem' => $rssReaderItem) ); } /** * XMLからArrayにシリアライズする * * @param array $url URL * @return array XMLの配列データ * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ public function serializeXmlToArray($url) { try { $xmlData = Xml::toArray(Xml::build($url)); } catch (XmlException $ex) { $xmlData = false; } if (! $xmlData) { return $xmlData; } // rssの種類によってタグ名が異なる if (isset($xmlData['feed'])) { $items = Hash::get($xmlData, 'feed.entry'); $dateKey = 'updated'; $linkKey = 'link.@href'; $summaryKey = 'summary'; } elseif (Hash::get($xmlData, 'rss.@version') === '2.0') { $items = Hash::get($xmlData, 'rss.channel.item'); $dateKey = 'pubDate'; $linkKey = 'link'; $summaryKey = 'description'; } elseif (Hash::get($xmlData, 'RDF')) { $items = Hash::get($xmlData, 'RDF.item'); $dateKey = 'dc:date'; $linkKey = 'link'; $summaryKey = 'description'; } else { return false; } if (! isset($items[0]) && is_array($items)) { $items = array($items); } if (is_null($items)) { $items = array(); } $data = array(); foreach ($items as $item) { $date = new DateTime(Hash::get($item, $dateKey, 'now')); $summary = Hash::get($item, $summaryKey, ''); if (is_array($summary) && isset($summary['@'])) { $summary = $summary['@']; } $link = Hash::get($item, $linkKey); if (is_null($link)) { $link = Hash::get($item, 'link.0.@href'); } $data[] = array( 'title' => $item['title'], 'link' => $link, 'summary' => strip_tags($summary), 'last_updated' => $date->format('Y-m-d H:i:s'), 'serialize_value' => serialize($item) ); } return $data; } /** * updateRssReaderItems * * @param array $data received post data * @return bool true on success, exception on error * @throws InternalErrorException */ public function updateRssReaderItems($data) { //トランザクションBegin $this->begin(); //バリデーション if (! $this->validateMany($data[$this->alias])) { return false; } try { //既存データの削除 $conditions = array($this->alias . '.rss_reader_id' => $data[$this->RssReader->alias]['id']); if (! $this->deleteAll($conditions, true, false)) { throw new InternalErrorException(__d('net_commons', 'Internal Server Error')); } if (! $this->saveMany($data[$this->alias], ['validate' => false])) { throw new InternalErrorException(__d('net_commons', 'Internal Server Error')); } //不要なビヘイビアを停止する if ($this->RssReader->Behaviors->enabled('Mails.MailQueue')) { $this->RssReader->Behaviors->disable('Mails.MailQueue'); } if ($this->RssReader->Behaviors->enabled('Topics.Topics')) { $this->RssReader->Behaviors->disable('Topics.Topics'); } //RssReaderの登録 $this->RssReader->id = $data[$this->RssReader->alias]['id']; $date = new DateTime(); $now = $date->format('Y-m-d H:i:s'); if (! $this->RssReader->saveField('modified', $now, ['callbacks' => false])) { throw new InternalErrorException(__d('net_commons', 'Internal Server Error')); } //停止したビヘイビアを開始する $this->RssReader->Behaviors->enable('Mails.MailQueue'); $this->RssReader->Behaviors->enable('Topics.Topics'); //トランザクションCommit $this->commit(); } catch (Exception $ex) { //トランザクションRollback $this->rollback($ex); } $this->setSlaveDataSource(); return true; } }