PageRenderTime 64ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/protected/modules/yupe/extensions/feed/EFeed.php

https://gitlab.com/RonLab1987/YupePlusClear
PHP | 510 lines | 258 code | 59 blank | 193 comment | 50 complexity | 6cecebf9b5b86290fd5359ec421232d2 MD5 | raw file
  1. <?php
  2. /**
  3. * EFeed Class file
  4. * @author Antonio Ramirez
  5. * @link http://www.ramirezcobos.com
  6. *
  7. *
  8. * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS "AS IS" AND
  9. * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  10. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  11. * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
  12. * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  13. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  14. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
  15. * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  16. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  17. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  18. */
  19. /**
  20. * EFeed is the RSS writer
  21. *
  22. * @author Antonio Ramirez <ramirez.cobos@gmail.com>
  23. * @package rss
  24. * @uses CComponent, CUrlValidator
  25. * @throws CException
  26. */
  27. class EFeed extends CComponent
  28. {
  29. /**
  30. *
  31. * supported Feed formats
  32. *
  33. * @var string RSS1
  34. * @var string RSS2
  35. * @var string ATOM
  36. */
  37. const RSS1 = 'RSS1';
  38. const RSS2 = 'RSS2';
  39. const ATOM = 'Atom';
  40. /**
  41. *
  42. * Holds all information and elements
  43. * to generate the feed
  44. * @var CMap $feedElements
  45. */
  46. private $feedElements;
  47. /**
  48. *
  49. * Holds stylesheet associated to the feed
  50. * http://www.w3.org/TR/xml-stylesheet/#dt-xml-stylesheet
  51. */
  52. private $stylesheets = [];
  53. /**
  54. *
  55. * Type of Feed Format
  56. * @var string $type
  57. */
  58. private $type;
  59. /**
  60. * Constructor
  61. *
  62. * @param constant the type constant (RSS1/RSS2/ATOM).
  63. */
  64. public function __construct($type = self::RSS2)
  65. {
  66. if ($type != self::RSS1 && $type != self::RSS2 && $type != self::ATOM) {
  67. throw new CException(Yii::t('EFeed', 'Feed version not supported'));
  68. }
  69. $this->type = $type;
  70. // Initiate Feed holder
  71. $this->feedElements = new CMap();
  72. // Setting default value for essential channel elements
  73. $this->addChannelTag('title', $this->type . ' Feed');
  74. $this->addChannelTag('link', 'http://www.ramirezcobos.com/');
  75. // Tag elements that we need to CDATA encode
  76. $this->feedElements->add('CDATAEncoded', ['description', 'content:encoded', 'summary']);
  77. }
  78. /**
  79. *
  80. * Adds stylesheet support
  81. * @param array $htmlOptions
  82. */
  83. public function addStylesheetTag($htmlOptions)
  84. {
  85. if (!is_array($htmlOptions)) {
  86. throw new CException(Yii::t('EFeed', __FUNCTION__ . ' parameter must be an array.'));
  87. }
  88. $this->stylesheets[] = '<?xml-stylesheet ' . CHtml::renderAttributes($htmlOptions) . ' ?>';
  89. }
  90. /**
  91. *
  92. * Adds a channel element
  93. * @param string $tag name of the element
  94. * @param string $content of the element
  95. */
  96. public function addChannelTag($tag, $content)
  97. {
  98. if (null === $this->feedElements->itemAt('channels')) {
  99. $this->feedElements->add('channels', new CMap());
  100. }
  101. $this->feedElements->itemAt('channels')->add($tag, $content);
  102. }
  103. /**
  104. *
  105. * Adds an array of channel elements
  106. * They should be on the format:
  107. * <pre>
  108. * array('tagname'=>'tagcontent')
  109. * </pre>
  110. * @param unknown_type $tags
  111. * @throws CException
  112. */
  113. public function addChannelTagsArray($tags)
  114. {
  115. if (!is_array($tags)) {
  116. throw new CException(Yii::t('EFeed', __FUNCTION__ . ' parameter must be an array.'));
  117. }
  118. foreach ($tags as $tag => $content) {
  119. $this->addChannelTag($tag, $content);
  120. }
  121. }
  122. /**
  123. * RSS1, RSS2 or ATOM
  124. * @return EFeedItemAbstract Item
  125. */
  126. public function createNewItem()
  127. {
  128. // create EFeedItem based on selected version type
  129. $class = "EFeedItem" . $this->type;
  130. return new $class();
  131. }
  132. /**
  133. * Property setter the 'title' channel element
  134. *
  135. * @param string value of 'title' channel tag
  136. */
  137. public function setTitle($title)
  138. {
  139. $this->addChannelTag('title', $title);
  140. }
  141. /**
  142. *
  143. * Property getter 'title'
  144. *
  145. * @return value of title channel tag | null
  146. */
  147. public function getTitle()
  148. {
  149. if (null !== $this->feedElements->itemAt('channels')) {
  150. return $this->feedElements->itemAt('channels')->itemAt('title');
  151. }
  152. return null;
  153. }
  154. /**
  155. * Property setter 'description' channel element
  156. *
  157. * @param string value of 'description' channel tag
  158. */
  159. public function setDescription($description)
  160. {
  161. $this->addChannelTag('description', $description);
  162. }
  163. /**
  164. *
  165. * Property getter 'description'
  166. *
  167. * @return value of description channel tag | null
  168. */
  169. public function getDescription()
  170. {
  171. if (null !== $this->feedElements->itemAt('channels')) {
  172. return $this->feedElements->itemAt('channels')->itemAt('description');
  173. }
  174. return null;
  175. }
  176. /**
  177. * Property setter 'link' channel element
  178. *
  179. * @param string value of 'link' channel tag
  180. * @throws CException
  181. */
  182. public function setLink($link)
  183. {
  184. $validator = new CUrlValidator();
  185. if (!$validator->validateValue($link)) {
  186. throw new CException(Yii::t('EFeed', $link . ' does not seem to be a valid URL'));
  187. }
  188. $this->addChannelTag('link', $link);
  189. }
  190. /**
  191. *
  192. * Property getter 'link'
  193. *
  194. * @return value of link channel tag | null
  195. */
  196. public function getLink()
  197. {
  198. if (null !== $this->feedElements->itemAt('channels')) {
  199. return $this->feedElements->itemAt('channels')->itemAt('link');
  200. }
  201. return null;
  202. }
  203. /**
  204. * Set the 'image' channel element
  205. *
  206. * Cannot be used as property setter
  207. *
  208. * @param string title of image
  209. * @param string link url of the image
  210. * @param string path url of the image
  211. */
  212. public function setImage($title, $link, $url)
  213. {
  214. $validator = new CUrlValidator();
  215. if (!$validator->validateValue($link)) {
  216. throw new CException(Yii::t('EFeed', $link . ' does not seem to be a valid URL'));
  217. }
  218. $this->addChannelTag('image', ['title' => $title, 'link' => $link, 'url' => $url]);
  219. }
  220. /**
  221. *
  222. * Property getter image
  223. * @return value of the image channel tag | null
  224. */
  225. public function getImage()
  226. {
  227. if (null !== $this->feedElements->itemAt('channels')) {
  228. return $this->feedElements->itemAt('channels')->itemAt('image');
  229. }
  230. return null;
  231. }
  232. /**
  233. *
  234. * Property setter the 'about' RSS 1.0 channel element
  235. *
  236. * @param string value of 'about' channel tag
  237. */
  238. public function setRSS1ChannelAbout($url)
  239. {
  240. $validator = new CUrlValidator();
  241. if (!$validator->validateValue($url)) {
  242. throw new CException(Yii::t('EFeed', $url . ' does not seem to be a valid URL'));
  243. }
  244. $this->addChannelTag('ChannelAbout', $url);
  245. }
  246. /**
  247. *
  248. * Property getter the 'about' RSS 1.0 channel
  249. * @return value of 'about' channel tag | null
  250. */
  251. public function getRSS1ChannelAbout()
  252. {
  253. if (null !== $this->feedElements->itemAt('channels')) {
  254. return $this->feedElements->itemAt('channels')->itemAt('ChannelAbout');
  255. }
  256. return null;
  257. }
  258. /**
  259. *
  260. * Add a FeedItem to the main class
  261. *
  262. * @param object instance of EFeedItemAbstract class
  263. */
  264. public function addItem(EFeedItemAbstract $item)
  265. {
  266. if (null === $this->feedElements->itemAt('items')) {
  267. $this->feedElements->add('items', new CTypedList('EFeedItemAbstract'));
  268. }
  269. $this->feedElements->itemAt('items')->add($item);
  270. }
  271. /**
  272. * Generates an UUID
  273. *
  274. * @author Anis uddin Ahmad <admin@ajaxray.com>
  275. * @param string an optional prefix
  276. * @return string the formated uuid
  277. */
  278. public static function uuid($key = null, $prefix = '')
  279. {
  280. $key = ($key == null) ? uniqid(rand()) : $key;
  281. $chars = md5($key);
  282. $uuid = substr($chars, 0, 8) . '-';
  283. $uuid .= substr($chars, 8, 4) . '-';
  284. $uuid .= substr($chars, 12, 4) . '-';
  285. $uuid .= substr($chars, 16, 4) . '-';
  286. $uuid .= substr($chars, 20, 12);
  287. return $prefix . $uuid;
  288. }
  289. /**
  290. *
  291. * Generates the Feed
  292. */
  293. public function generateFeed()
  294. {
  295. header("Content-type: text/xml");
  296. $this->renderHead();
  297. $this->renderChannels();
  298. $this->renderItems();
  299. $this->renderBottom();
  300. }
  301. /**
  302. *
  303. * Prints the xml and rss namespace
  304. *
  305. */
  306. private function renderHead()
  307. {
  308. $head = '<?xml version="1.0" encoding="utf-8"?>' . PHP_EOL;
  309. if (!empty($this->stylesheets)) {
  310. $head .= implode(PHP_EOL, $this->stylesheets);
  311. }
  312. if ($this->type == self::RSS2) {
  313. $head .= CHtml::openTag(
  314. 'rss',
  315. [
  316. "version" => "2.0",
  317. "xmlns:content" => "http://purl.org/rss/1.0/modules/content/",
  318. "xmlns:wfw" => "http://wellformedweb.org/CommentAPI/"
  319. ]
  320. ) . PHP_EOL;
  321. } elseif ($this->type == self::RSS1) {
  322. $head .= CHtml::openTag(
  323. 'rdf:RDF',
  324. [
  325. "xmlns:rdf" => "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
  326. "xmlns" => "http://purl.org/rss/1.0/",
  327. "xmlns:dc" => "http://purl.org/dc/elements/1.1/"
  328. ]
  329. ) . PHP_EOL;
  330. } elseif ($this->type == self::ATOM) {
  331. $head .= CHtml::openTag('feed', ["xmlns" => "http://www.w3.org/2005/Atom"]) . PHP_EOL;
  332. }
  333. echo $head;
  334. }
  335. /**
  336. *
  337. * Prints the xml closing tags
  338. *
  339. */
  340. private function renderBottom()
  341. {
  342. if ($this->type == self::RSS2) {
  343. echo CHtml::closeTag('channel');
  344. echo CHtml::closeTag('rss');
  345. } elseif ($this->type == self::RSS1) {
  346. echo CHtml::closeTag('rdf:RDF');
  347. } elseif ($this->type == self::ATOM) {
  348. echo CHtml::closeTag('feed');
  349. }
  350. }
  351. /**
  352. *
  353. * Prints the channels of the xml document
  354. * @throws CException
  355. */
  356. private function renderChannels()
  357. {
  358. switch ($this->type) {
  359. case self::RSS2 :
  360. echo '<channel>' . PHP_EOL;
  361. break;
  362. case self::RSS1:
  363. if (null !== $this->RSS1ChannelAbout) {
  364. echo CHtml::tag('channel', ['rdf:about' => $this->RSS1ChannelAbout]);
  365. } else {
  366. echo CHtml::tag('channel', ['rdf:about' => $this->link]);
  367. }
  368. break;
  369. }
  370. // Printing channel items
  371. foreach ($this->feedElements->itemAt('channels') as $key => $value) {
  372. if ($this->type == self::ATOM && $key == 'link') {
  373. // ATOM prints link element as href attribute
  374. echo $this->makeNode($key, '', ['href' => $value]);
  375. // And add the id for ATOM
  376. echo $this->makeNode('id', $this->uuid($value, 'urn:uuid:'));
  377. } else {
  378. echo $this->makeNode($key, $value);
  379. }
  380. }
  381. // RSS 1.0 have special tag <rdf:Seq> with channel
  382. if ($this->type == self::RSS1) {
  383. if (null === $this->feedElements->itemAt('items')) {
  384. throw new CException(Yii::t('EFeed', 'No items have been set'));
  385. }
  386. echo "<items>" . PHP_EOL . "<rdf:Seq>" . PHP_EOL;
  387. foreach ($this->feedElements->itemAt('items') as $item) {
  388. $tag = $item->link;
  389. if (null === $tag) {
  390. throw new CException(Yii::t(
  391. 'EFeed',
  392. 'For RSS 1.0 specifications link element should be add per item'
  393. ));
  394. }
  395. echo CHtml::tag('rdf:li', ['resource' => $tag->content], true) . PHP_EOL;
  396. }
  397. echo "</rdf:Seq>" . PHP_EOL . "</items>" . PHP_EOL;
  398. }
  399. }
  400. /**
  401. *
  402. * Prints feed items
  403. * @throws CException
  404. */
  405. private function renderItems()
  406. {
  407. if (null === $this->feedElements->itemAt('items')) {
  408. throw new CException(Yii::t('EFeed', 'No feed items configured'));
  409. }
  410. foreach ($this->feedElements->itemAt('items') as $item) {
  411. echo $item->getNode();
  412. }
  413. }
  414. /**
  415. *
  416. * Creates a single node as xml format
  417. *
  418. * @access private
  419. * @param string name of the tag
  420. * @param mixed tag value as string or array of nested tags in 'tagName' => 'tagValue' format
  421. * @param array Attributes(if any) in 'attrName' => 'attrValue' format
  422. * @return string formatted xml tag
  423. */
  424. private function makeNode($tagName, $tagContent, $attributes = [])
  425. {
  426. $node = '';
  427. if (is_array($tagContent) && $this->type == self::RSS1) {
  428. $attributes['rdf:parseType'] = "Resource";
  429. }
  430. if (in_array($tagName, $this->feedElements->itemAt('CDATAEncoded'))) {
  431. if ($this->type == self::ATOM) {
  432. $attributes['type'] = "html";
  433. }
  434. $node .= CHtml::openTag($tagName, $attributes) . '<![CDATA[';
  435. } else {
  436. $node .= CHtml::openTag($tagName, $attributes);
  437. }
  438. if (is_array($tagContent)) {
  439. foreach ($tagContent as $tag => $content) {
  440. $node .= $this->makeNode($tag, $content);
  441. }
  442. } else {
  443. $node .= in_array($tagName, $this->feedElements->itemAt('CDATAEncoded')) ? $tagContent : CHtml::encode(
  444. $tagContent
  445. );
  446. }
  447. $node .= in_array($tagName, $this->feedElements->itemAt('CDATAEncoded')) ? PHP_EOL . ']]>' : '';
  448. $node .= CHtml::closeTag($tagName);
  449. return $node . PHP_EOL;
  450. }
  451. }