diff --git a/CHANGES.md b/CHANGES.md index b429ac70..749837e8 100755 --- a/CHANGES.md +++ b/CHANGES.md @@ -19,6 +19,8 @@ ## version 10.3.06 - Correction : - Edition de page avec module, le changement de mise en page désactive le bouton d'option du module. +- Modification : + - Modules News et Blog : ajout de l'option flux RSS. L'option est activée par défaut. ## version 10.3.05 - Correction : diff --git a/core/core.php b/core/core.php index d251f465..3b0a6312 100644 --- a/core/core.php +++ b/core/core.php @@ -17,9 +17,10 @@ class common { const DISPLAY_RAW = 0; const DISPLAY_JSON = 1; - const DISPLAY_LAYOUT_BLANK = 2; - const DISPLAY_LAYOUT_MAIN = 3; - const DISPLAY_LAYOUT_LIGHT = 4; + const DISPLAY_RSS = 2; + const DISPLAY_LAYOUT_BLANK = 3; + const DISPLAY_LAYOUT_MAIN = 4; + const DISPLAY_LAYOUT_LIGHT = 5; const GROUP_BANNED = -1; const GROUP_VISITOR = 0; const GROUP_MEMBER = 1; @@ -938,8 +939,9 @@ class common { public function setData($keys = []) { // Pas d'enregistrement lorsqu'une notice est présente ou tableau transmis vide - if (!empty(self::$inputNotices - OR empty($keys))) { + if (!empty(self::$inputNotices) + OR empty($keys) + OR in_array(NULL, $keys) ) { return false; } @@ -1427,6 +1429,39 @@ class common { } $this->setData(['core', 'dataVersion', 10304]); } + // Version 10.3.06 + if ($this->getData(['core', 'dataVersion']) < 10306) { + // Liste des pages + $pageList = array(); + foreach ($this->getHierarchy(null,null,null) as $parentKey=>$parentValue) { + $pageList [] = $parentKey; + foreach ($parentValue as $childKey) { + $pageList [] = $childKey; + } + } + // Mettre à jour les données des blogs les articles sont dans posts + foreach ($pageList as $parentKey => $parent) { + //La page a une galerie + if ($this->getData(['page',$parent,'moduleId']) === 'blog' ) { + foreach ( $this->getData(['module', $parent]) as $blogKey => $blogItem) { + $data = $this->getdata(['module',$parent,$blogKey]); + $this->deleteData(['module',$parent, $blogKey]); + $this->setData([ 'module', $parent, 'posts', $blogKey, $data ]); + } + } + } + foreach ($pageList as $parentKey => $parent) { + //La page a une galerie + if ($this->getData(['page',$parent,'moduleId']) === 'news' ) { + foreach ( $this->getData(['module', $parent]) as $newsKey => $newsItem) { + $data = $this->getdata(['module',$parent,$newsKey]); + $this->deleteData(['module',$parent, $newsKey]); + $this->setData([ 'module', $parent, 'posts', $newsKey, $data ]); + } + } + } + $this->setData(['core', 'dataVersion', 10306]); + } // Version 10.4.00 if ($this->getData(['core', 'dataVersion']) < 10400) { // Ajouter le prénom comme pseudo et le pseudo comme signature @@ -2130,7 +2165,12 @@ class core extends common { header('Content-Type: application/json'); echo json_encode($this->output['content']); break; - // Layout alléger + // RSS feed + case self::DISPLAY_RSS: + header('Content-type: application/rss+xml; charset=UTF-8'); + echo $this->output['content']; + break; + // Layout allégé case self::DISPLAY_LAYOUT_LIGHT: require 'core/layout/light.php'; break; diff --git a/core/module/install/ressource/defaultdata.php b/core/module/install/ressource/defaultdata.php index 28808690..986a68b1 100644 --- a/core/module/install/ressource/defaultdata.php +++ b/core/module/install/ressource/defaultdata.php @@ -648,65 +648,55 @@ class init extends common { ], 'module' => [ 'blog' => [ - 'mon-premier-article' => [ - 'comment' => [ - '58e11d09e5aff' => [ - 'author' => 'Rémi', - 'content' => 'Article bien rédigé et très pertinent, bravo !', - 'createdOn' => 1421748000, - 'userId' => '', - 'approval' => true - ] + 'config' => [ + 'feeds' => true, + 'feedsLabel' => "Syndication RSS" + ], + 'posts' => [ + 'mon-premier-article' => [ + 'closeComment' => false, + 'comment' => [ + '58e11d09e5aff' => [ + 'author' => 'Rémi', + 'content' => 'Article bien rédigé et très pertinent, bravo !', + 'createdOn' => 1421748000, + 'userId' => '' + ] + ], + 'content' => '

Et eodem impetu Domitianum praecipitem per scalas itidem funibus constrinxerunt, eosque coniunctos per ampla spatia civitatis acri raptavere discursu. iamque artuum et membrorum divulsa conpage superscandentes corpora mortuorum ad ultimam truncata deformitatem velut exsaturati mox abiecerunt in flumen.

Ex his quidam aeternitati se commendari posse per statuas aestimantes eas ardenter adfectant quasi plus praemii de figmentis aereis sensu carentibus adepturi, quam ex conscientia honeste recteque factorum, easque auro curant inbracteari, quod Acilio Glabrioni delatum est primo, cum consiliis armisque regem superasset Antiochum. quam autem sit pulchrum exigua haec spernentem et minima ad ascensus verae gloriae tendere longos et arduos, ut memorat vates Ascraeus, Censorius Cato monstravit. qui interrogatus quam ob rem inter multos... statuam non haberet malo inquit ambigere bonos quam ob rem id non meruerim, quam quod est gravius cur inpetraverim mussitare.

Latius iam disseminata licentia onerosus bonis omnibus Caesar nullum post haec adhibens modum orientis latera cuncta vexabat nec honoratis parcens nec urbium primatibus nec plebeiis.

', + 'picture' => 'galerie/landscape/meadow.jpg', + 'hidePicture' => false, + 'pictureSize' => 20, + 'publishedOn' => 1548790902, + 'state' => true, + 'title' => 'Mon premier article', + 'userId' => '' // Géré au moment de l'installation + ], + 'mon-deuxieme-article' => [ + 'closeComment' => false, + 'comment' => [], + 'content' => '

Et prima post Osdroenam quam, ut dictum est, ab hac descriptione discrevimus, Commagena, nunc Euphratensis, clementer adsurgit, Hierapoli, vetere Nino et Samosata civitatibus amplis inlustris.

Ob haec et huius modi multa, quae cernebantur in paucis, omnibus timeri sunt coepta. et ne tot malis dissimulatis paulatimque serpentibus acervi crescerent aerumnarum, nobilitatis decreto legati mittuntur: Praetextatus ex urbi praefecto et ex vicario Venustus et ex consulari Minervius oraturi, ne delictis supplicia sint grandiora, neve senator quisquam inusitato et inlicito more tormentis exponeretur.

Sed ut tum ad senem senex de senectute, sic hoc libro ad amicum amicissimus scripsi de amicitia. Tum est Cato locutus, quo erat nemo fere senior temporibus illis, nemo prudentior; nunc Laelius et sapiens (sic enim est habitus) et amicitiae gloria excellens de amicitia loquetur. Tu velim a me animum parumper avertas, Laelium loqui ipsum putes. C. Fannius et Q. Mucius ad socerum veniunt post mortem Africani; ab his sermo oritur, respondet Laelius, cuius tota disputatio est de amicitia, quam legens te ipse cognosces.

', + 'picture' => 'galerie/landscape/desert.jpg', + 'hidePicture' => false, + 'pictureSize' => 40, + 'publishedOn' => 1550432502, + 'state' => true, + 'title' => 'Mon deuxième article', + 'userId' => '' // Géré au moment de l'installation + ], + 'mon-troisieme-article' => [ + 'closeComment' => true, + 'comment' => [], + 'content' => '

Rogatus ad ultimum admissusque in consistorium ambage nulla praegressa inconsiderate et leviter proficiscere inquit ut praeceptum est, Caesar sciens quod si cessaveris, et tuas et palatii tui auferri iubebo prope diem annonas. hocque solo contumaciter dicto subiratus abscessit nec in conspectum eius postea venit saepius arcessitus.

Proinde concepta rabie saeviore, quam desperatio incendebat et fames, amplificatis viribus ardore incohibili in excidium urbium matris Seleuciae efferebantur, quam comes tuebatur Castricius tresque legiones bellicis sudoribus induratae.

Inter has ruinarum varietates a Nisibi quam tuebatur accitus Vrsicinus, cui nos obsecuturos iunxerat imperiale praeceptum, dispicere litis exitialis certamina cogebatur abnuens et reclamans, adulatorum oblatrantibus turmis, bellicosus sane milesque semper et militum ductor sed forensibus iurgiis longe discretus, qui metu sui discriminis anxius cum accusatores quaesitoresque subditivos sibi consociatos ex isdem foveis cerneret emergentes, quae clam palamve agitabantur, occultis Constantium litteris edocebat inplorans subsidia, quorum metu tumor notissimus Caesaris exhalaret.

', + 'picture' => 'galerie/landscape/iceberg.jpg', + 'hidePicture' => false, + 'pictureSize' => 100, + 'publishedOn' => 1550864502, + 'state' => true, + 'title' => 'Mon troisième article', + 'userId' => '' // Géré au moment de l'installation ], - 'content' => '

Et eodem impetu Domitianum praecipitem per scalas itidem funibus constrinxerunt, eosque coniunctos per ampla spatia civitatis acri raptavere discursu. iamque artuum et membrorum divulsa conpage superscandentes corpora mortuorum ad ultimam truncata deformitatem velut exsaturati mox abiecerunt in flumen.

Ex his quidam aeternitati se commendari posse per statuas aestimantes eas ardenter adfectant quasi plus praemii de figmentis aereis sensu carentibus adepturi, quam ex conscientia honeste recteque factorum, easque auro curant inbracteari, quod Acilio Glabrioni delatum est primo, cum consiliis armisque regem superasset Antiochum. quam autem sit pulchrum exigua haec spernentem et minima ad ascensus verae gloriae tendere longos et arduos, ut memorat vates Ascraeus, Censorius Cato monstravit. qui interrogatus quam ob rem inter multos... statuam non haberet malo inquit ambigere bonos quam ob rem id non meruerim, quam quod est gravius cur inpetraverim mussitare.

Latius iam disseminata licentia onerosus bonis omnibus Caesar nullum post haec adhibens modum orientis latera cuncta vexabat nec honoratis parcens nec urbium primatibus nec plebeiis.

', - 'picture' => 'galerie/landscape/meadow.jpg', - 'hidePicture' => false, - 'pictureSize' => 20, - 'publishedOn' => 1548790902, - 'state' => true, - 'title' => 'Mon premier article', - 'userId' => '', // Géré au moment de l'installation - 'editConsent' => 'all', - 'commentMaxlength' => '500', - 'commentApproved' => false, - 'commentClose' => false, - 'commentNotification' => false, - 'commentGroupNotification' => 3 ], - 'mon-deuxieme-article' => [ - 'comment' => [], - 'content' => '

Et prima post Osdroenam quam, ut dictum est, ab hac descriptione discrevimus, Commagena, nunc Euphratensis, clementer adsurgit, Hierapoli, vetere Nino et Samosata civitatibus amplis inlustris.

Ob haec et huius modi multa, quae cernebantur in paucis, omnibus timeri sunt coepta. et ne tot malis dissimulatis paulatimque serpentibus acervi crescerent aerumnarum, nobilitatis decreto legati mittuntur: Praetextatus ex urbi praefecto et ex vicario Venustus et ex consulari Minervius oraturi, ne delictis supplicia sint grandiora, neve senator quisquam inusitato et inlicito more tormentis exponeretur.

Sed ut tum ad senem senex de senectute, sic hoc libro ad amicum amicissimus scripsi de amicitia. Tum est Cato locutus, quo erat nemo fere senior temporibus illis, nemo prudentior; nunc Laelius et sapiens (sic enim est habitus) et amicitiae gloria excellens de amicitia loquetur. Tu velim a me animum parumper avertas, Laelium loqui ipsum putes. C. Fannius et Q. Mucius ad socerum veniunt post mortem Africani; ab his sermo oritur, respondet Laelius, cuius tota disputatio est de amicitia, quam legens te ipse cognosces.

', - 'picture' => 'galerie/landscape/desert.jpg', - 'hidePicture' => false, - 'pictureSize' => 40, - 'publishedOn' => 1550432502, - 'state' => true, - 'title' => 'Mon deuxième article', - 'userId' => '', // Géré au moment de l'installation - 'editConsent' => 'all', - 'commentMaxlength' => '500', - 'commentApproved' => false, - 'commentClose' => false, - 'commentNotification' => false, - 'commentGroupNotification' => 3 - ], - 'mon-troisieme-article' => [ - 'comment' => [], - 'content' => '

Rogatus ad ultimum admissusque in consistorium ambage nulla praegressa inconsiderate et leviter proficiscere inquit ut praeceptum est, Caesar sciens quod si cessaveris, et tuas et palatii tui auferri iubebo prope diem annonas. hocque solo contumaciter dicto subiratus abscessit nec in conspectum eius postea venit saepius arcessitus.

Proinde concepta rabie saeviore, quam desperatio incendebat et fames, amplificatis viribus ardore incohibili in excidium urbium matris Seleuciae efferebantur, quam comes tuebatur Castricius tresque legiones bellicis sudoribus induratae.

Inter has ruinarum varietates a Nisibi quam tuebatur accitus Vrsicinus, cui nos obsecuturos iunxerat imperiale praeceptum, dispicere litis exitialis certamina cogebatur abnuens et reclamans, adulatorum oblatrantibus turmis, bellicosus sane milesque semper et militum ductor sed forensibus iurgiis longe discretus, qui metu sui discriminis anxius cum accusatores quaesitoresque subditivos sibi consociatos ex isdem foveis cerneret emergentes, quae clam palamve agitabantur, occultis Constantium litteris edocebat inplorans subsidia, quorum metu tumor notissimus Caesaris exhalaret.

', - 'picture' => 'galerie/landscape/iceberg.jpg', - 'hidePicture' => false, - 'pictureSize' => 100, - 'publishedOn' => 1550864502, - 'state' => true, - 'title' => 'Mon troisième article', - 'userId' => '', // Géré au moment de l'installation - 'editConsent' => 'all', - 'commentMaxlength' => '500', - 'commentApproved' => false, - 'commentClose' => true, - 'commentNotification' => false, - 'commentGroupNotification' => 3 - ] ], 'galeries' => [ 'beaux-paysages' => [ diff --git a/module/blog/blog.php b/module/blog/blog.php index c679d769..c7a6d574 100644 --- a/module/blog/blog.php +++ b/module/blog/blog.php @@ -27,7 +27,8 @@ class blog extends common { 'config' => self::GROUP_MODERATOR, 'delete' => self::GROUP_MODERATOR, 'edit' => self::GROUP_MODERATOR, - 'index' => self::GROUP_VISITOR + 'index' => self::GROUP_VISITOR, + 'rss' => self::GROUP_VISITOR ]; public static $articles = []; @@ -86,7 +87,55 @@ class blog extends common { public static $users = []; - const BLOG_VERSION = '3.1'; + const BLOG_VERSION = '3.2'; + + /** + * Flux RSS + */ + public function rss() { + // Inclure les classes + include_once 'module/news/vendor/FeedWriter/Item.php'; + include_once 'module/news/vendor/FeedWriter/Feed.php'; + include_once 'module/news/vendor/FeedWriter/RSS2.php'; + include_once 'module/news/vendor/FeedWriter/InvalidOperationException.php'; + + date_default_timezone_set('UTC'); + $feeds = new \FeedWriter\RSS2(); + + // En-tête + $feeds->setTitle($this->getData (['page', $this->getUrl(0), 'posts','title'])); + $feeds->setLink(helper::baseUrl() . $this->getUrl(0)); + $feeds->setDescription(html_entity_decode(strip_tags($this->getData (['page', $this->getUrl(0), 'metaDescription'])))); + $feeds->setChannelElement('language', 'fr-FR'); + $feeds->setDate(time()); + $feeds->addGenerator(); + // Corps des articles + $articleIdsPublishedOns = helper::arrayCollumn($this->getData(['module', $this->getUrl(0), 'posts']), 'publishedOn', 'SORT_DESC'); + $articleIdsStates = helper::arrayCollumn($this->getData(['module', $this->getUrl(0),'posts']), 'state', 'SORT_DESC'); + foreach($articleIdsPublishedOns as $articleId => $articlePublishedOn) { + if($articlePublishedOn <= time() AND $articleIdsStates[$articleId]) { + // Miniature + $parts = explode('/',$this->getData(['module', $this->getUrl(0), 'posts', $articleId, 'picture'])); + $thumb = str_replace ($parts[(count($parts)-1)],'mini_' . $parts[(count($parts)-1)], $this->getData(['module', $this->getUrl(0), 'posts', $articleId, 'picture'])); + // Créer les articles du flux + $newsArticle = $feeds->createNewItem(); + $newsArticle->addElementArray([ + 'title' => strip_tags($this->getData(['module', $this->getUrl(0), 'posts', $articleId, 'title']) ), + 'link' => helper::baseUrl() .$this->getUrl(0) . '/' . $articleId, + 'description' => html_entity_decode(strip_tags($this->getData(['module', $this->getUrl(0), 'posts', $articleId, 'content']))), + 'addEnclosure' => helper::baseUrl() . self::FILE_DIR . $thumb + ]); + $feeds->addItem($newsArticle); + } + } + + // Valeurs en sortie + $this->addOutput([ + 'display' => self::DISPLAY_RSS, + 'content' => $feeds->generateFeed(), + 'view' => 'rss' + ]); + } /** * Édition @@ -108,8 +157,9 @@ class blog extends common { // Crée l'article $this->setData(['module', $this->getUrl(0), + 'posts', $articleId, [ - 'comment' => $this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'comment']), + 'comment' => $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'comment']), 'content' => $this->getInput('blogAddContent', null), 'picture' => $this->getInput('blogAddPicture', helper::FILTER_STRING_SHORT, true), 'hidePicture' => $this->getInput('blogAddHidePicture', helper::FILTER_BOOLEAN), @@ -156,7 +206,7 @@ class blog extends common { * Liste des commentaires */ public function comment() { - $comments = $this->getData(['module', $this->getUrl(0), $this->getUrl(2),'comment']); + $comments = $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2),'comment']); self::$commentsDelete = template::button('blogCommentDeleteAll', [ 'class' => 'blogCommentDeleteAll buttonRed', 'href' => helper::baseUrl() . $this->getUrl(0) . '/commentDeleteAll/' . $this->getUrl(2).'/' . $_SESSION['csrf'] , @@ -177,7 +227,7 @@ class blog extends common { $buttonApproval = ''; // Compatibilité avec les commentaires des versions précédentes, les valider $comment['approval'] = array_key_exists('approval', $comment) === false ? true : $comment['approval'] ; - if ( $this->getData(['module', $this->getUrl(0), $this->getUrl(2),'commentApproved']) === true) { + if ( $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2),'commentApproved']) === true) { $buttonApproval = template::button('blogCommentApproved' . $commentIds[$i], [ 'class' => $comment['approval'] === true ? 'blogCommentRejected buttonGreen' : 'blogCommentApproved buttonRed' , 'href' => helper::baseUrl() . $this->getUrl(0) . '/commentApprove/' . $this->getUrl(2) . '/' . $commentIds[$i] . '/' . $_SESSION['csrf'] , @@ -200,7 +250,7 @@ class blog extends common { } // Valeurs en sortie $this->addOutput([ - 'title' => 'Gestion des commentaires : '. $this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'title']), + 'title' => 'Gestion des commentaires : '. $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'title']), 'view' => 'comment' ]); } @@ -210,7 +260,7 @@ class blog extends common { */ public function commentDelete() { // Le commentaire n'existe pas - if($this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'comment', $this->getUrl(3)]) === null) { + if($this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'comment', $this->getUrl(3)]) === null) { // Valeurs en sortie $this->addOutput([ 'access' => false @@ -226,7 +276,7 @@ class blog extends common { } // Suppression else { - $this->deleteData(['module', $this->getUrl(0), $this->getUrl(2), 'comment', $this->getUrl(3)]); + $this->deleteData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'comment', $this->getUrl(3)]); // Valeurs en sortie $this->addOutput([ 'redirect' => helper::baseUrl() . $this->getUrl(0) . '/comment/'.$this->getUrl(2), @@ -250,7 +300,7 @@ class blog extends common { } // Suppression else { - $this->setData(['module', $this->getUrl(0), $this->getUrl(2), 'comment',[] ]); + $this->setData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'comment',[] ]); // Valeurs en sortie $this->addOutput([ 'redirect' => helper::baseUrl() . $this->getUrl(0) . '/comment', @@ -265,7 +315,7 @@ class blog extends common { */ public function commentApprove() { // Le commentaire n'existe pas - if($this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'comment', $this->getUrl(3)]) === null) { + if($this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'comment', $this->getUrl(3)]) === null) { // Valeurs en sortie $this->addOutput([ 'access' => false @@ -281,12 +331,12 @@ class blog extends common { } // Inversion du statut else { - $approved = !$this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'comment', $this->getUrl(3), 'approval']) ; - $this->setData(['module', $this->getUrl(0), $this->getUrl(2), 'comment', $this->getUrl(3), [ - 'author' => $this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'comment', $this->getUrl(3), 'author']), - 'content' => $this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'comment', $this->getUrl(3), 'content']), - 'createdOn' => $this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'comment', $this->getUrl(3), 'createdOn']), - 'userId' => $this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'comment', $this->getUrl(3), 'userId']), + $approved = !$this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'comment', $this->getUrl(3), 'approval']) ; + $this->setData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'comment', $this->getUrl(3), [ + 'author' => $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'comment', $this->getUrl(3), 'author']), + 'content' => $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'comment', $this->getUrl(3), 'content']), + 'createdOn' => $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'comment', $this->getUrl(3), 'createdOn']), + 'userId' => $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'comment', $this->getUrl(3), 'userId']), 'approval' => $approved ]]); @@ -310,19 +360,19 @@ class blog extends common { foreach ($articleIds as $key => $value) { if ( ( // Propriétaire - $this->getData(['module', $this->getUrl(0), $value,'editConsent']) === self::EDIT_OWNER - AND ( $this->getData(['module', $this->getUrl(0), $value,'userId']) === $this->getUser('id') + $this->getData(['module', $this->getUrl(0), 'posts', $value,'editConsent']) === self::EDIT_OWNER + AND ( $this->getData(['module', $this->getUrl(0), 'posts', $value,'userId']) === $this->getUser('id') OR $this->getUser('group') === self::GROUP_ADMIN ) ) OR ( // Groupe - $this->getData(['module', $this->getUrl(0), $value,'editConsent']) !== self::EDIT_OWNER - AND $this->getUser('group') >= $this->getData(['module',$this->getUrl(0), $value,'editConsent']) + $this->getData(['module', $this->getUrl(0), 'posts', $value,'editConsent']) !== self::EDIT_OWNER + AND $this->getUser('group') >= $this->getData(['module',$this->getUrl(0), 'posts', $value,'editConsent']) ) OR ( // Tout le monde - $this->getData(['module', $this->getUrl(0), $value,'editConsent']) === self::EDIT_ALL + $this->getData(['module', $this->getUrl(0), 'posts', $value,'editConsent']) === self::EDIT_ALL ) ) { $filterData[] = $value; @@ -336,28 +386,28 @@ class blog extends common { // Articles en fonction de la pagination for($i = $pagination['first']; $i < $pagination['last']; $i++) { // Nombre de commentaires à approuver et approuvés - $approvals = helper::arrayCollumn($this->getData(['module', $this->getUrl(0), $articleIds[$i], 'comment' ]),'approval', 'SORT_DESC'); + $approvals = helper::arrayCollumn($this->getData(['module', $this->getUrl(0), 'posts', $articleIds[$i], 'comment' ]),'approval', 'SORT_DESC'); if ( is_array($approvals) ) { $a = array_values($approvals); $toApprove = count(array_keys($a,false)); $approved = count(array_keys($a,true)); } else { $toApprove = 0; - $approved = count($this->getData(['module', $this->getUrl(0), $articleIds[$i],'comment'])); + $approved = count($this->getData(['module', $this->getUrl(0), 'posts', $articleIds[$i],'comment'])); } // Met en forme le tableau - $date = mb_detect_encoding(strftime('%d %B %Y', $this->getData(['module', $this->getUrl(0), $articleIds[$i], 'publishedOn'])), 'UTF-8', true) - ? strftime('%d %B %Y', $this->getData(['module', $this->getUrl(0), $articleIds[$i], 'publishedOn'])) - : utf8_encode(strftime('%d %B %Y', $this->getData(['module', $this->getUrl(0), $articleIds[$i], 'publishedOn']))); - $heure = mb_detect_encoding(strftime('%H:%M', $this->getData(['module', $this->getUrl(0), $articleIds[$i], 'publishedOn'])), 'UTF-8', true) - ? strftime('%H:%M', $this->getData(['module', $this->getUrl(0), $articleIds[$i], 'publishedOn'])) - : utf8_encode(strftime('%H:%M', $this->getData(['module', $this->getUrl(0), $articleIds[$i], 'publishedOn']))); + $date = mb_detect_encoding(strftime('%d %B %Y', $this->getData(['module', $this->getUrl(0), 'posts', $articleIds[$i], 'publishedOn'])), 'UTF-8', true) + ? strftime('%d %B %Y', $this->getData(['module', $this->getUrl(0), 'posts', $articleIds[$i], 'publishedOn'])) + : utf8_encode(strftime('%d %B %Y', $this->getData(['module', $this->getUrl(0), 'posts', $articleIds[$i], 'publishedOn']))); + $heure = mb_detect_encoding(strftime('%H:%M', $this->getData(['module', $this->getUrl(0), 'posts', $articleIds[$i], 'publishedOn'])), 'UTF-8', true) + ? strftime('%H:%M', $this->getData(['module', $this->getUrl(0), 'posts', $articleIds[$i], 'publishedOn'])) + : utf8_encode(strftime('%H:%M', $this->getData(['module', $this->getUrl(0), 'posts', $articleIds[$i], 'publishedOn']))); self::$articles[] = [ '' . - $this->getData(['module', $this->getUrl(0), $articleIds[$i], 'title']) . + $this->getData(['module', $this->getUrl(0), 'posts', $articleIds[$i], 'title']) . '', $date .' à '. $heure, - self::$states[$this->getData(['module', $this->getUrl(0), $articleIds[$i], 'state'])], + self::$states[$this->getData(['module', $this->getUrl(0), 'posts', $articleIds[$i], 'state'])], // Bouton pour afficher les commentaires de l'article template::button('blogConfigComment' . $articleIds[$i], [ 'class' => ($toApprove || $approved ) > 0 ? 'buttonBlue' : 'buttonGrey' , @@ -375,18 +425,13 @@ class blog extends common { ]) ]; } - // Valeurs en sortie - $this->addOutput([ - 'title' => 'Configuration du module', - 'view' => 'config' - ]); } /** * Suppression */ public function delete() { - if($this->getData(['module', $this->getUrl(0), $this->getUrl(2)]) === null) { + if($this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2)]) === null) { // Valeurs en sortie $this->addOutput([ 'access' => false @@ -402,7 +447,7 @@ class blog extends common { } // Suppression else { - $this->deleteData(['module', $this->getUrl(0), $this->getUrl(2)]); + $this->deleteData(['module', $this->getUrl(0), 'posts', $this->getUrl(2)]); // Valeurs en sortie $this->addOutput([ 'redirect' => helper::baseUrl() . $this->getUrl(0) . '/config', @@ -425,7 +470,7 @@ class blog extends common { ]); } // L'article n'existe pas - if($this->getData(['module', $this->getUrl(0), $this->getUrl(2)]) === null) { + if($this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2)]) === null) { // Valeurs en sortie $this->addOutput([ 'access' => false @@ -450,6 +495,7 @@ class blog extends common { } $this->setData(['module', $this->getUrl(0), + 'posts', $articleId, [ 'comment' => $this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'comment']), 'content' => $this->getInput('blogEditContent', null), @@ -471,7 +517,7 @@ class blog extends common { ]); // Supprime l'ancien article if($articleId !== $this->getUrl(2)) { - $this->deleteData(['module', $this->getUrl(0), $this->getUrl(2)]); + $this->deleteData(['module', $this->getUrl(0), 'posts', $this->getUrl(2)]); } // Valeurs en sortie $this->addOutput([ @@ -493,7 +539,7 @@ class blog extends common { unset($userFirstname); // Valeurs en sortie $this->addOutput([ - 'title' => $this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'title']), + 'title' => $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'title']), 'vendor' => [ 'flatpickr', 'tinymce' @@ -514,7 +560,7 @@ class blog extends common { AND intval($this->getUrl(1)) === 0 ) { // L'article n'existe pas - if($this->getData(['module', $this->getUrl(0), $this->getUrl(1)]) === null) { + if($this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1)]) === null) { // Valeurs en sortie $this->addOutput([ 'access' => false @@ -533,9 +579,9 @@ class blog extends common { self::$inputNotices['blogArticleCaptcha'] = 'Incorrect'; } // Crée le commentaire - $commentId = helper::increment(uniqid(), $this->getData(['module', $this->getUrl(0), $this->getUrl(1), 'comment'])); + $commentId = helper::increment(uniqid(), $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'comment'])); $content = $this->getInput('blogArticleContent', false); - $this->setData(['module', $this->getUrl(0), $this->getUrl(1), 'comment', $commentId, [ + $this->setData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'comment', $commentId, [ 'author' => $this->getInput('blogArticleAuthor', helper::FILTER_STRING_SHORT, empty($this->getInput('blogArticleUserId')) ? TRUE : FALSE), 'content' => $content, 'createdOn' => time(), @@ -547,13 +593,13 @@ class blog extends common { $to = []; // Liste des destinataires foreach($this->getData(['user']) as $userId => $user) { - if ($user['group'] >= $this->getData(['module', $this->getUrl(0), $this->getUrl(1), 'commentGroupNotification']) ) { + if ($user['group'] >= $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'commentGroupNotification']) ) { $to[] = $user['mail']; } } // Envoi du mail $sent code d'erreur ou de réussite - $notification = $this->getData(['module', $this->getUrl(0), $this->getUrl(1), 'commentApproved']) === true ? 'Commentaire déposé en attente d\'approbation': 'Commentaire déposé'; - if ($this->getData(['module', $this->getUrl(0), $this->getUrl(1), 'commentNotification']) === true) { + $notification = $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'commentApproved']) === true ? 'Commentaire déposé en attente d\'approbation': 'Commentaire déposé'; + if ($this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'commentNotification']) === true) { $sent = $this->sendMail( $to, 'Nouveau commentaire', @@ -580,7 +626,7 @@ class blog extends common { } // Ids des commentaires approuvés par ordre de publication - $commentsApproved = $this->getData(['module', $this->getUrl(0), $this->getUrl(1), 'comment']); + $commentsApproved = $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'comment']); if ($commentsApproved) { foreach( $commentsApproved as $key => $value){ if($value['approval']===false) unset($commentsApproved[$key]); @@ -661,7 +707,7 @@ class blog extends common { // Valeurs en sortie $this->addOutput([ 'showBarEditButton' => true, - 'title' => $this->getData(['module', $this->getUrl(0), $this->getUrl(1), 'title']), + 'title' => $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'title']), 'vendor' => [ 'tinymce' ], @@ -673,8 +719,8 @@ class blog extends common { // Liste des articles else { // Ids des articles par ordre de publication - $articleIdsPublishedOns = helper::arrayCollumn($this->getData(['module', $this->getUrl(0)]), 'publishedOn', 'SORT_DESC'); - $articleIdsStates = helper::arrayCollumn($this->getData(['module', $this->getUrl(0)]), 'state', 'SORT_DESC'); + $articleIdsPublishedOns = helper::arrayCollumn($this->getData(['module', $this->getUrl(0),'posts']), 'publishedOn', 'SORT_DESC'); + $articleIdsStates = helper::arrayCollumn($this->getData(['module', $this->getUrl(0), 'posts']), 'state', 'SORT_DESC'); $articleIds = []; foreach($articleIdsPublishedOns as $articleId => $articlePublishedOn) { if($articlePublishedOn <= time() AND $articleIdsStates[$articleId]) { @@ -687,7 +733,7 @@ class blog extends common { self::$pages = $pagination['pages']; // Articles en fonction de la pagination for($i = $pagination['first']; $i < $pagination['last']; $i++) { - self::$articles[$articleIds[$i]] = $this->getData(['module', $this->getUrl(0), $articleIds[$i]]); + self::$articles[$articleIds[$i]] = $this->getData(['module', $this->getUrl(0), 'posts', $articleIds[$i]]); } // Valeurs en sortie $this->addOutput([ diff --git a/module/blog/ressource/feed-icon-16.gif b/module/blog/ressource/feed-icon-16.gif new file mode 100644 index 00000000..b0e4adf1 Binary files /dev/null and b/module/blog/ressource/feed-icon-16.gif differ diff --git a/module/blog/vendor/FeedWriter/ATOM.php b/module/blog/vendor/FeedWriter/ATOM.php new file mode 100644 index 00000000..28494501 --- /dev/null +++ b/module/blog/vendor/FeedWriter/ATOM.php @@ -0,0 +1,38 @@ + + * + * This file is part of the "Universal Feed Writer" project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * Wrapper for creating ATOM feeds + * + * @package UniversalFeedWriter + */ +class ATOM extends Feed +{ + /** + * {@inheritdoc} + */ + public function __construct() + { + parent::__construct(Feed::ATOM); + } + +} diff --git a/module/blog/vendor/FeedWriter/Feed.php b/module/blog/vendor/FeedWriter/Feed.php new file mode 100644 index 00000000..9e0650a8 --- /dev/null +++ b/module/blog/vendor/FeedWriter/Feed.php @@ -0,0 +1,1017 @@ + + * Copyright (C) 2010-2016 Michael Bemmerl + * + * This file is part of the "Universal Feed Writer" project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * Universal Feed Writer class + * + * Generate RSS 1.0, RSS2.0 and ATOM Feeds + * + * @package UniversalFeedWriter + * @author Anis uddin Ahmad + * @link http://www.ajaxray.com/projects/rss + */ +abstract class Feed +{ + // RSS 0.90 Officially obsoleted by 1.0 + // RSS 0.91, 0.92, 0.93 and 0.94 Officially obsoleted by 2.0 + // So, define constants for RSS 1.0, RSS 2.0 and ATOM + + const RSS1 = 'RSS 1.0'; + const RSS2 = 'RSS 2.0'; + const ATOM = 'ATOM'; + + const VERSION = '1.1.0'; + + /** + * Collection of all channel elements + */ + private $channels = array(); + + /** + * Collection of items as object of \FeedWriter\Item class. + */ + private $items = array(); + + /** + * Collection of other version wise data. + * + * Currently used to store the 'rdf:about' attribute and image element of the channel (both RSS1 only). + */ + private $data = array(); + + /** + * The tag names which have to encoded as CDATA + */ + private $CDATAEncoding = array(); + + /** + * Collection of XML namespaces + */ + private $namespaces = array(); + + /** + * Contains the format of this feed. + */ + private $version = null; + + /** + * Constructor + * + * If no version is given, a feed in RSS 2.0 format will be generated. + * + * @param string $version the version constant (RSS1/RSS2/ATOM). + */ + protected function __construct($version = Feed::RSS2) + { + $this->version = $version; + + // Setting default encoding + $this->encoding = 'utf-8'; + + // Setting default value for essential channel element + $this->setTitle($version . ' Feed'); + + // Add some default XML namespaces + $this->namespaces['content'] = 'http://purl.org/rss/1.0/modules/content/'; + $this->namespaces['wfw'] = 'http://wellformedweb.org/CommentAPI/'; + $this->namespaces['atom'] = 'http://www.w3.org/2005/Atom'; + $this->namespaces['rdf'] = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'; + $this->namespaces['rss1'] = 'http://purl.org/rss/1.0/'; + $this->namespaces['dc'] = 'http://purl.org/dc/elements/1.1/'; + $this->namespaces['sy'] = 'http://purl.org/rss/1.0/modules/syndication/'; + + // Tag names to encode in CDATA + $this->addCDATAEncoding(array('description', 'content:encoded', 'summary')); + } + + // Start # public functions --------------------------------------------- + + /** + * Set the URLs for feed pagination. + * + * See RFC 5005, chapter 3. At least one page URL must be specified. + * + * @param string $nextURL The URL to the next page of this feed. Optional. + * @param string $previousURL The URL to the previous page of this feed. Optional. + * @param string $firstURL The URL to the first page of this feed. Optional. + * @param string $lastURL The URL to the last page of this feed. Optional. + * @link http://tools.ietf.org/html/rfc5005#section-3 + * @return self + * @throws \LogicException if none of the parameters are set. + */ + public function setPagination($nextURL = null, $previousURL = null, $firstURL = null, $lastURL = null) + { + if (empty($nextURL) && empty($previousURL) && empty($firstURL) && empty($lastURL)) + throw new \LogicException('At least one URL must be specified for pagination to work.'); + + if (!empty($nextURL)) + $this->setAtomLink($nextURL, 'next'); + + if (!empty($previousURL)) + $this->setAtomLink($previousURL, 'previous'); + + if (!empty($firstURL)) + $this->setAtomLink($firstURL, 'first'); + + if (!empty($lastURL)) + $this->setAtomLink($lastURL, 'last'); + + return $this; + } + + /** + * Add a channel element indicating the program used to generate the feed. + * + * @return self + * @throws InvalidOperationException if this method is called on an RSS1 feed. + */ + public function addGenerator() + { + if ($this->version == Feed::ATOM) + $this->setChannelElement('atom:generator', 'FeedWriter', array('uri' => 'https://github.com/mibe/FeedWriter')); + else if ($this->version == Feed::RSS2) + $this->setChannelElement('generator', 'FeedWriter'); + else + throw new InvalidOperationException('The generator element is not supported in RSS1 feeds.'); + + return $this; + } + + /** + * Add a XML namespace to the internal list of namespaces. After that, + * custom channel elements can be used properly to generate a valid feed. + * + * @access public + * @param string $prefix namespace prefix + * @param string $uri namespace name (URI) + * @return self + * @link http://www.w3.org/TR/REC-xml-names/ + * @throws \InvalidArgumentException if the prefix or uri is empty or NULL. + */ + public function addNamespace($prefix, $uri) + { + if (empty($prefix)) + throw new \InvalidArgumentException('The prefix may not be emtpy or NULL.'); + if (empty($uri)) + throw new \InvalidArgumentException('The uri may not be empty or NULL.'); + + $this->namespaces[$prefix] = $uri; + + return $this; + } + + /** + * Add a channel element to the feed. + * + * @access public + * @param string $elementName name of the channel tag + * @param string $content content of the channel tag + * @param array array of element attributes with attribute name as array key + * @param bool TRUE if this element can appear multiple times + * @return self + * @throws \InvalidArgumentException if the element name is not a string, empty or NULL. + */ + public function setChannelElement($elementName, $content, array $attributes = null, $multiple = false) + { + if (empty($elementName)) + throw new \InvalidArgumentException('The element name may not be empty or NULL.'); + if (!is_string($elementName)) + throw new \InvalidArgumentException('The element name must be a string.'); + + $entity['content'] = $content; + $entity['attributes'] = $attributes; + + if ($multiple === TRUE) + $this->channels[$elementName][] = $entity; + else + $this->channels[$elementName] = $entity; + + return $this; + } + + /** + * Set multiple channel elements from an array. Array elements + * should be 'channelName' => 'channelContent' format. + * + * @access public + * @param array array of channels + * @return self + */ + public function setChannelElementsFromArray(array $elementArray) + { + foreach ($elementArray as $elementName => $content) { + $this->setChannelElement($elementName, $content); + } + + return $this; + } + + /** + * Get the appropriate MIME type string for the current feed. + * + * @access public + * @return string The MIME type string. + */ + public function getMIMEType() + { + switch ($this->version) { + case Feed::RSS2 : $mimeType = "application/rss+xml"; + break; + case Feed::RSS1 : $mimeType = "application/rdf+xml"; + break; + case Feed::ATOM : $mimeType = "application/atom+xml"; + break; + default : $mimeType = "text/xml"; + } + + return $mimeType; + } + + /** + * Print the actual RSS/ATOM file + * + * Sets a Content-Type header and echoes the contents of the feed. + * Should only be used in situations where direct output is desired; + * if you need to pass a string around, use generateFeed() instead. + * + * @access public + * @param bool FALSE if the specific feed media type should be sent. + * @return void + * @throws \InvalidArgumentException if the useGenericContentType parameter is not boolean. + */ + public function printFeed($useGenericContentType = false) + { + if (!is_bool($useGenericContentType)) + throw new \InvalidArgumentException('The useGenericContentType parameter must be boolean.'); + + $contentType = "text/xml"; + + if (!$useGenericContentType) { + $contentType = $this->getMIMEType(); + } + + // Generate the feed before setting the header, so Exceptions will be nicely visible. + $feed = $this->generateFeed(); + header("Content-Type: " . $contentType . "; charset=" . $this->encoding); + echo $feed; + } + + /** + * Generate the feed. + * + * @access public + * @return string The complete feed XML. + * @throws InvalidOperationException if the link element of the feed is not set. + */ + public function generateFeed() + { + if ($this->version != Feed::ATOM && !array_key_exists('link', $this->channels)) + throw new InvalidOperationException('RSS1 & RSS2 feeds need a link element. Call the setLink method before this method.'); + + return $this->makeHeader() + . $this->makeChannels() + . $this->makeItems() + . $this->makeFooter(); + } + + /** + * Create a new Item. + * + * @access public + * @return Item instance of Item class + */ + public function createNewItem() + { + $Item = new Item($this->version); + + return $Item; + } + + /** + * Add one or more tags to the list of CDATA encoded tags + * + * @access public + * @param array $tags An array of tag names that are merged into the list of tags which should be encoded as CDATA + * @return self + */ + public function addCDATAEncoding(array $tags) + { + $this->CDATAEncoding = array_merge($this->CDATAEncoding, $tags); + + return $this; + } + + /** + * Get list of CDATA encoded properties + * + * @access public + * @return array Return an array of CDATA properties that are to be encoded as CDATA + */ + public function getCDATAEncoding() + { + return $this->CDATAEncoding; + } + + /** + * Remove tags from the list of CDATA encoded tags + * + * @access public + * @param array $tags An array of tag names that should be removed. + * @return void + */ + public function removeCDATAEncoding(array $tags) + { + // Call array_values to re-index the array. + $this->CDATAEncoding = array_values(array_diff($this->CDATAEncoding, $tags)); + } + + /** + * Add a FeedItem to the main class + * + * @access public + * @param Item $feedItem instance of Item class + * @return self + * @throws \InvalidArgumentException if the given item version mismatches. + */ + public function addItem(Item $feedItem) + { + if ($feedItem->getVersion() != $this->version) + { + $msg = sprintf('Feed type mismatch: This instance can handle %s feeds only, but item for %s feeds given.', $this->version, $feedItem->getVersion()); + throw new \InvalidArgumentException($msg); + } + + $this->items[] = $feedItem; + + return $this; + } + + // Wrapper functions ------------------------------------------------------------------- + + /** + * Set the 'encoding' attribute in the XML prolog. + * + * @access public + * @param string $encoding value of 'encoding' attribute + * @return self + * @throws \InvalidArgumentException if the encoding is not a string, empty or NULL. + */ + public function setEncoding($encoding) + { + if (empty($encoding)) + throw new \InvalidArgumentException('The encoding may not be empty or NULL.'); + if (!is_string($encoding)) + throw new \InvalidArgumentException('The encoding must be a string.'); + + $this->encoding = $encoding; + + return $this; + } + + /** + * Set the 'title' channel element + * + * @access public + * @param string $title value of 'title' channel tag + * @return self + */ + public function setTitle($title) + { + return $this->setChannelElement('title', $title); + } + + /** + * Set the date when the feed was lastly updated. + * + * This adds the 'updated' element to the feed. The value of the date parameter + * can be either an instance of the DateTime class, an integer containing a UNIX + * timestamp or a string which is parseable by PHP's 'strtotime' function. + * + * Not supported in RSS1 feeds. + * + * @access public + * @param DateTime|int|string Date which should be used. + * @return self + * @throws \InvalidArgumentException if the given date is not an instance of DateTime, a UNIX timestamp or a date string. + * @throws InvalidOperationException if this method is called on an RSS1 feed. + */ + public function setDate($date) + { + if ($this->version == Feed::RSS1) + throw new InvalidOperationException('The publication date is not supported in RSS1 feeds.'); + + // The feeds have different date formats. + $format = $this->version == Feed::ATOM ? \DATE_ATOM : \DATE_RSS; + + if ($date instanceof DateTime) + $date = $date->format($format); + else if(is_numeric($date) && $date >= 0) + $date = date($format, $date); + else if (is_string($date)) + { + $timestamp = strtotime($date); + if ($timestamp === FALSE) + throw new \InvalidArgumentException('The given date was not parseable.'); + + $date = date($format, $timestamp); + } + else + throw new \InvalidArgumentException('The given date is not an instance of DateTime, a UNIX timestamp or a date string.'); + + if ($this->version == Feed::ATOM) + $this->setChannelElement('updated', $date); + else + $this->setChannelElement('lastBuildDate', $date); + + return $this; + } + + /** + * Set a phrase or sentence describing the feed. + * + * @access public + * @param string $description Description of the feed. + * @return self + */ + public function setDescription($description) + { + if ($this->version != Feed::ATOM) + $this->setChannelElement('description', $description); + else + $this->setChannelElement('subtitle', $description); + + return $this; + } + + /** + * Set the 'link' channel element + * + * @access public + * @param string $link value of 'link' channel tag + * @return self + */ + public function setLink($link) + { + if ($this->version == Feed::ATOM) + $this->setAtomLink($link); + else + $this->setChannelElement('link', $link); + + return $this; + } + + /** + * Set custom 'link' channel elements. + * + * In ATOM feeds, only one link with alternate relation and the same combination of + * type and hreflang values. + * + * @access public + * @param string $href URI of this link + * @param string $rel relation type of the resource + * @param string $type MIME type of the target resource + * @param string $hreflang language of the resource + * @param string $title human-readable information about the resource + * @param int $length length of the resource in bytes + * @link https://www.iana.org/assignments/link-relations/link-relations.xml + * @link https://tools.ietf.org/html/rfc4287#section-4.2.7 + * @return self + * @throws \InvalidArgumentException on multiple occasions. + * @throws InvalidOperationException if the same link with the same attributes was already added to the feed. + */ + public function setAtomLink($href, $rel = null, $type = null, $hreflang = null, $title = null, $length = null) + { + $data = array('href' => $href); + + if ($rel != null) { + if (!is_string($rel) || empty($rel)) + throw new \InvalidArgumentException('rel parameter must be a string and a valid relation identifier.'); + + $data['rel'] = $rel; + } + if ($type != null) { + // Regex used from RFC 4287, page 41 + if (!is_string($type) || preg_match('/.+\/.+/', $type) != 1) + throw new \InvalidArgumentException('type parameter must be a string and a MIME type.'); + + $data['type'] = $type; + } + if ($hreflang != null) { + // Regex used from RFC 4287, page 41 + if (!is_string($hreflang) || preg_match('/[A-Za-z]{1,8}(-[A-Za-z0-9]{1,8})*/', $hreflang) != 1) + throw new \InvalidArgumentException('hreflang parameter must be a string and a valid language code.'); + + $data['hreflang'] = $hreflang; + } + if ($title != null) { + if (!is_string($title) || empty($title)) + throw new \InvalidArgumentException('title parameter must be a string and not empty.'); + + $data['title'] = $title; + } + if ($length != null) { + if (!is_int($length) || $length < 0) + throw new \InvalidArgumentException('length parameter must be a positive integer.'); + + $data['length'] = (string) $length; + } + + // ATOM spec. has some restrictions on atom:link usage + // See RFC 4287, page 12 (4.1.1) + if ($this->version == Feed::ATOM) { + foreach ($this->channels as $key => $value) { + if ($key != 'atom:link') + continue; + + // $value is an array , so check every element + foreach ($value as $linkItem) { + $attrib = $linkItem['attributes']; + // Only one link with relation alternate and same hreflang & type is allowed. + if (@$attrib['rel'] == 'alternate' && @$attrib['hreflang'] == $hreflang && @$attrib['type'] == $type) + throw new InvalidOperationException('The feed must not contain more than one link element with a' + . ' relation of "alternate" that has the same combination of type and hreflang attribute values.'); + } + } + } + + return $this->setChannelElement('atom:link', '', $data, true); + } + + /** + * Set an 'atom:link' channel element with relation=self attribute. + * Needs the full URL to this feed. + * + * @link http://www.rssboard.org/rss-profile#namespace-elements-atom-link + * @access public + * @param string $url URL to this feed + * @return self + */ + public function setSelfLink($url) + { + return $this->setAtomLink($url, 'self', $this->getMIMEType()); + } + + /** + * Set the 'image' channel element + * + * @access public + * @param string $url URL of the image + * @param string $title Title of the image. RSS only. + * @param string $link Link target URL of the image. RSS only. + * @return self + * @throws \InvalidArgumentException if the url is invalid. + * @throws \InvalidArgumentException if the title and link parameter are not a string or empty. + */ + public function setImage($url, $title = null, $link = null) + { + if (!is_string($url) || empty($url)) + throw new \InvalidArgumentException('url parameter must be a string and may not be empty or NULL.'); + + // RSS feeds have support for a title & link element. + if ($this->version != Feed::ATOM) + { + if (!is_string($title) || empty($title)) + throw new \InvalidArgumentException('title parameter must be a string and may not be empty or NULL.'); + if (!is_string($link) || empty($link)) + throw new \InvalidArgumentException('link parameter must be a string and may not be empty or NULL.'); + + $data = array('title'=>$title, 'link'=>$link, 'url'=>$url); + $name = 'image'; + } + else + { + $name = 'logo'; + $data = $url; + } + + // Special handling for RSS1 again (since RSS1 is a bit strange...) + if ($this->version == Feed::RSS1) + { + $this->data['Image'] = $data; + return $this->setChannelElement($name, '', array('rdf:resource' => $url), false); + } + else + return $this->setChannelElement($name, $data); + } + + /** + * Set the channel 'rdf:about' attribute, which is used in RSS1 feeds only. + * + * @access public + * @param string $url value of 'rdf:about' attribute of the channel element + * @return self + * @throws InvalidOperationException if this method is called and the feed is not of type RSS1. + * @throws \InvalidArgumentException if the given URL is invalid. + */ + public function setChannelAbout($url) + { + if ($this->version != Feed::RSS1) + throw new InvalidOperationException("This method is only supported in RSS1 feeds."); + if (empty($url)) + throw new \InvalidArgumentException('The about URL may not be empty or NULL.'); + if (!is_string($url)) + throw new \InvalidArgumentException('The about URL must be a string.'); + + $this->data['ChannelAbout'] = $url; + + return $this; + } + + /** + * Generate an UUID. + * + * The UUID is based on an MD5 hash. If no key is given, a unique ID as the input + * for the MD5 hash is generated. + * + * @author Anis uddin Ahmad + * @access public + * @param string $key optional key on which the UUID is generated + * @param string $prefix an optional prefix + * @return string the formatted UUID + */ + public static function uuid($key = null, $prefix = '') + { + $key = ($key == null) ? uniqid(rand()) : $key; + $chars = md5($key); + $uuid = substr($chars,0,8) . '-'; + $uuid .= substr($chars,8,4) . '-'; + $uuid .= substr($chars,12,4) . '-'; + $uuid .= substr($chars,16,4) . '-'; + $uuid .= substr($chars,20,12); + + return $prefix . $uuid; + } + + /** + * Replace invalid XML characters. + * + * @link http://www.phpwact.org/php/i18n/charsets#xml See utf8_for_xml() function + * @link http://www.w3.org/TR/REC-xml/#charsets + * @link https://github.com/mibe/FeedWriter/issues/30 + * + * @access public + * @param string $string string which should be filtered + * @param string $replacement replace invalid characters with this string + * @return string the filtered string + */ + public static function filterInvalidXMLChars($string, $replacement = '_') // default to '\x{FFFD}' ??? + { + $result = preg_replace('/[^\x{0009}\x{000a}\x{000d}\x{0020}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]+/u', $replacement, $string); + + // Did the PCRE replace failed because of bad UTF-8 data? + // If yes, try a non-multibyte regex and without the UTF-8 mode enabled. + if ($result == NULL && preg_last_error() == PREG_BAD_UTF8_ERROR) + $result = preg_replace('/[^\x09\x0a\x0d\x20-\xFF]+/', $replacement, $string); + + // In case the regex replacing failed completely, return the whole unfiltered string. + if ($result == NULL) + $result = $string; + + return $result; + } + // End # public functions ---------------------------------------------- + + // Start # private functions ---------------------------------------------- + + /** + * Returns all used XML namespace prefixes in this instance. + * This includes all channel elements and feed items. + * Unfortunately some namespace prefixes are not included, + * because they are hardcoded, e.g. rdf. + * + * @access private + * @return array Array with namespace prefix as value. + */ + private function getNamespacePrefixes() + { + $prefixes = array(); + + // Get all tag names from channel elements... + $tags = array_keys($this->channels); + + // ... and now all names from feed items + foreach ($this->items as $item) { + foreach (array_keys($item->getElements()) as $key) { + if (!in_array($key, $tags)) { + $tags[] = $key; + } + } + } + + // Look for prefixes in those tag names + foreach ($tags as $tag) { + $elements = explode(':', $tag); + + if (count($elements) != 2) + continue; + + $prefixes[] = $elements[0]; + } + + return array_unique($prefixes); + } + + /** + * Returns the XML header and root element, depending on the feed type. + * + * @access private + * @return string The XML header of the feed. + * @throws InvalidOperationException if an unknown XML namespace prefix is encountered. + */ + private function makeHeader() + { + $out = 'encoding.'" ?>' . PHP_EOL; + + $prefixes = $this->getNamespacePrefixes(); + $attributes = array(); + $tagName = ''; + $defaultNamespace = ''; + + if ($this->version == Feed::RSS2) { + $tagName = 'rss'; + $attributes['version'] = '2.0'; + } elseif ($this->version == Feed::RSS1) { + $tagName = 'rdf:RDF'; + $prefixes[] = 'rdf'; + $defaultNamespace = $this->namespaces['rss1']; + } elseif ($this->version == Feed::ATOM) { + $tagName = 'feed'; + $defaultNamespace = $this->namespaces['atom']; + + // Ugly hack to remove the 'atom' value from the prefixes array. + $prefixes = array_flip($prefixes); + unset($prefixes['atom']); + $prefixes = array_flip($prefixes); + } + + // Iterate through every namespace prefix and add it to the element attributes. + foreach ($prefixes as $prefix) { + if (!isset($this->namespaces[$prefix])) + throw new InvalidOperationException('Unknown XML namespace prefix: \'' . $prefix . '\'.' + . ' Use the addNamespace method to add support for this prefix.'); + else + $attributes['xmlns:' . $prefix] = $this->namespaces[$prefix]; + } + + // Include default namepsace, if required + if (!empty($defaultNamespace)) + $attributes['xmlns'] = $defaultNamespace; + + $out .= $this->makeNode($tagName, '', $attributes, true); + + return $out; + } + + /** + * Closes the open tags at the end of file + * + * @access private + * @return string The XML footer of the feed. + */ + private function makeFooter() + { + if ($this->version == Feed::RSS2) { + return '' . PHP_EOL . ''; + } elseif ($this->version == Feed::RSS1) { + return ''; + } elseif ($this->version == Feed::ATOM) { + return ''; + } + } + + /** + * Creates a single node in XML format + * + * @access private + * @param string $tagName name of the tag + * @param mixed $tagContent tag value as string or array of nested tags in 'tagName' => 'tagValue' format + * @param array $attributes Attributes (if any) in 'attrName' => 'attrValue' format + * @param bool $omitEndTag True if the end tag should be omitted. Defaults to false. + * @return string formatted xml tag + * @throws \InvalidArgumentException if the tagContent is not an array and not a string. + */ + private function makeNode($tagName, $tagContent, array $attributes = null, $omitEndTag = false) + { + $nodeText = ''; + $attrText = ''; + + if ($attributes != null) { + foreach ($attributes as $key => $value) { + $value = self::filterInvalidXMLChars($value); + $value = htmlspecialchars($value); + $attrText .= " $key=\"$value\""; + } + } + + $attrText .= (in_array($tagName, $this->CDATAEncoding) && $this->version == Feed::ATOM) ? ' type="html"' : ''; + $nodeText .= "<{$tagName}{$attrText}>"; + $nodeText .= (in_array($tagName, $this->CDATAEncoding)) ? ' $value) { + if (is_array($value)) { + $nodeText .= PHP_EOL; + foreach ($value as $subValue) { + $nodeText .= $this->makeNode($key, $subValue); + } + } else if (is_string($value)) { + $nodeText .= $this->makeNode($key, $value); + } else { + throw new \InvalidArgumentException("Unknown node-value type for $key"); + } + } + } else { + $tagContent = self::filterInvalidXMLChars($tagContent); + $nodeText .= (in_array($tagName, $this->CDATAEncoding)) ? $this->sanitizeCDATA($tagContent) : htmlspecialchars($tagContent); + } + + $nodeText .= (in_array($tagName, $this->CDATAEncoding)) ? ']]>' : ''; + + if (!$omitEndTag) + $nodeText .= ""; + + $nodeText .= PHP_EOL; + + return $nodeText; + } + + /** + * Make the channels. + * + * @access private + * @return string The feed header as XML containing all the feed metadata. + */ + private function makeChannels() + { + $out = ''; + + //Start channel tag + switch ($this->version) { + case Feed::RSS2: + $out .= '' . PHP_EOL; + break; + case Feed::RSS1: + $out .= (isset($this->data['ChannelAbout']))? "data['ChannelAbout']}\">" : "channels['link']['content']}\">"; + break; + } + + //Print Items of channel + foreach ($this->channels as $key => $value) { + // In ATOM feeds, strip all ATOM namespace prefixes from the tag name. They are not needed here, + // because the ATOM namespace name is set as default namespace. + if ($this->version == Feed::ATOM && strncmp($key, 'atom', 4) == 0) { + $key = substr($key, 5); + } + + // The channel element can occur multiple times, when the key 'content' is not in the array. + if (!array_key_exists('content', $value)) { + // If this is the case, iterate through the array with the multiple elements. + foreach ($value as $singleElement) { + $out .= $this->makeNode($key, $singleElement['content'], $singleElement['attributes']); + } + } else { + $out .= $this->makeNode($key, $value['content'], $value['attributes']); + } + } + + if ($this->version == Feed::RSS1) { + //RSS 1.0 have special tag with channel + $out .= "" . PHP_EOL . "" . PHP_EOL; + foreach ($this->items as $item) { + $thisItems = $item->getElements(); + $out .= "" . PHP_EOL; + } + $out .= "" . PHP_EOL . "" . PHP_EOL . "" . PHP_EOL; + + // An image has its own element after the channel elements. + if (array_key_exists('image', $this->data)) + $out .= $this->makeNode('image', $this->data['Image'], array('rdf:about' => $this->data['Image']['url'])); + } else if ($this->version == Feed::ATOM) { + // ATOM feeds have a unique feed ID. Use the title channel element as key. + $out .= $this->makeNode('id', Feed::uuid($this->channels['title']['content'], 'urn:uuid:')); + } + + return $out; + } + + /** + * Prints formatted feed items + * + * @access private + * @return string The XML of every feed item. + */ + private function makeItems() + { + $out = ''; + + foreach ($this->items as $item) { + $thisItems = $item->getElements(); + + // The argument is printed as rdf:about attribute of item in RSS 1.0 + // We're using the link set in the item (which is mandatory) as the about attribute. + if ($this->version == Feed::RSS1) + $out .= $this->startItem($thisItems['link']['content']); + else + $out .= $this->startItem(); + + foreach ($thisItems as $feedItem) { + $name = $feedItem['name']; + + // Strip all ATOM namespace prefixes from tags when feed is an ATOM feed. + // Not needed here, because the ATOM namespace name is used as default namespace. + if ($this->version == Feed::ATOM && strncmp($name, 'atom', 4) == 0) + $name = substr($name, 5); + + $out .= $this->makeNode($name, $feedItem['content'], $feedItem['attributes']); + } + $out .= $this->endItem(); + } + + return $out; + } + + /** + * Make the starting tag of channels + * + * @access private + * @param string $about The value of about attribute which is used for RSS 1.0 only. + * @return string The starting XML tag of an feed item. + * @throws InvalidOperationException if this object misses the data for the about attribute. + */ + private function startItem($about = false) + { + $out = ''; + + if ($this->version == Feed::RSS2) { + $out .= '' . PHP_EOL; + } elseif ($this->version == Feed::RSS1) { + if ($about) { + $out .= "" . PHP_EOL; + } else { + throw new InvalidOperationException("Missing data for about attribute. Call setChannelAbout method."); + } + } elseif ($this->version == Feed::ATOM) { + $out .= "" . PHP_EOL; + } + + return $out; + } + + /** + * Closes feed item tag + * + * @access private + * @return string The ending XML tag of an feed item. + */ + private function endItem() + { + if ($this->version == Feed::RSS2 || $this->version == Feed::RSS1) { + return '' . PHP_EOL; + } elseif ($this->version == Feed::ATOM) { + return '' . PHP_EOL; + } + } + + /** + * Sanitizes data which will be later on returned as CDATA in the feed. + * + * A "]]>" respectively "", "]]>", $text); + $text = str_replace(" + * + * This file is part of the "Universal Feed Writer" project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * The exception that is thrown when an invalid operation is performed on + * the object. + * + * @package UniversalFeedWriter + */ +class InvalidOperationException extends LogicException +{ +} diff --git a/module/blog/vendor/FeedWriter/Item.php b/module/blog/vendor/FeedWriter/Item.php new file mode 100644 index 00000000..695afe41 --- /dev/null +++ b/module/blog/vendor/FeedWriter/Item.php @@ -0,0 +1,413 @@ + + * Copyright (C) 2010-2013, 2015-2016 Michael Bemmerl + * + * This file is part of the "Universal Feed Writer" project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * Universal Feed Writer + * + * Item class - Used as feed element in Feed class + * + * @package UniversalFeedWriter + * @author Anis uddin Ahmad + * @link http://www.ajaxray.com/projects/rss + */ +class Item +{ + /** + * Collection of feed item elements + */ + private $elements = array(); + + /** + * Contains the format of this feed. + */ + private $version; + + /** + * Is used as a suffix when multiple elements have the same name. + **/ + private $_cpt = 0; + + /** + * Constructor + * + * @param string $version constant (RSS1/RSS2/ATOM) RSS2 is default. + */ + public function __construct($version = Feed::RSS2) + { + $this->version = $version; + } + + /** + * Return an unique number + * + * @access private + * @return int + **/ + private function cpt() + { + return $this->_cpt++; + } + + /** + * Add an element to elements array + * + * @access public + * @param string $elementName The tag name of an element + * @param string $content The content of tag + * @param array $attributes Attributes (if any) in 'attrName' => 'attrValue' format + * @param boolean $overwrite Specifies if an already existing element is overwritten. + * @param boolean $allowMultiple Specifies if multiple elements of the same name are allowed. + * @return self + * @throws \InvalidArgumentException if the element name is not a string, empty or NULL. + */ + public function addElement($elementName, $content, array $attributes = null, $overwrite = FALSE, $allowMultiple = FALSE) + { + if (empty($elementName)) + throw new \InvalidArgumentException('The element name may not be empty or NULL.'); + if (!is_string($elementName)) + throw new \InvalidArgumentException('The element name must be a string.'); + + $key = $elementName; + + // return if element already exists & if overwriting is disabled + // & if multiple elements are not allowed. + if (isset($this->elements[$elementName]) && !$overwrite) { + if (!$allowMultiple) + return $this; + + $key .= '-' . $this->cpt(); + } + + $this->elements[$key]['name'] = $elementName; + $this->elements[$key]['content'] = $content; + $this->elements[$key]['attributes'] = $attributes; + + return $this; + } + + /** + * Set multiple feed elements from an array. + * Elements which have attributes cannot be added by this method + * + * @access public + * @param array array of elements in 'tagName' => 'tagContent' format. + * @return self + */ + public function addElementArray(array $elementArray) + { + foreach ($elementArray as $elementName => $content) { + $this->addElement($elementName, $content); + } + + return $this; + } + + /** + * Return the collection of elements in this feed item + * + * @access public + * @return array All elements of this item. + * @throws InvalidOperationException on ATOM feeds if either a content or link element is missing. + * @throws InvalidOperationException on RSS1 feeds if a title or link element is missing. + */ + public function getElements() + { + // ATOM feeds have some specific requirements... + if ($this->version == Feed::ATOM) + { + // Add an 'id' element, if it was not added by calling the setLink method. + // Use the value of the title element as key, since no link element was specified. + if (!array_key_exists('id', $this->elements)) + $this->setId(Feed::uuid($this->elements['title']['content'], 'urn:uuid:')); + + // Either a 'link' or 'content' element is needed. + if (!array_key_exists('content', $this->elements) && !array_key_exists('link', $this->elements)) + throw new InvalidOperationException('ATOM feed entries need a link or a content element. Call the setLink or setContent method.'); + } + // ...same with RSS1 feeds. + else if ($this->version == Feed::RSS1) + { + if (!array_key_exists('title', $this->elements)) + throw new InvalidOperationException('RSS1 feed entries need a title element. Call the setTitle method.'); + if (!array_key_exists('link', $this->elements)) + throw new InvalidOperationException('RSS1 feed entries need a link element. Call the setLink method.'); + } + + return $this->elements; + } + + /** + * Return the type of this feed item + * + * @access public + * @return string The feed type, as defined in Feed.php + */ + public function getVersion() + { + return $this->version; + } + + // Wrapper functions ------------------------------------------------------ + + /** + * Set the 'description' element of feed item + * + * @access public + * @param string $description The content of the 'description' or 'summary' element + * @return self + */ + public function setDescription($description) + { + $tag = ($this->version == Feed::ATOM) ? 'summary' : 'description'; + + return $this->addElement($tag, $description); + } + + /** + * Set the 'content' element of the feed item + * For ATOM feeds only + * + * @access public + * @param string $content Content for the item (i.e., the body of a blog post). + * @return self + * @throws InvalidOperationException if this method is called on non-ATOM feeds. + */ + public function setContent($content) + { + if ($this->version != Feed::ATOM) + throw new InvalidOperationException('The content element is supported in ATOM feeds only.'); + + return $this->addElement('content', $content, array('type' => 'html')); + } + + /** + * Set the 'title' element of feed item + * + * @access public + * @param string $title The content of 'title' element + * @return self + */ + public function setTitle($title) + { + return $this->addElement('title', $title); + } + + /** + * Set the 'date' element of the feed item. + * + * The value of the date parameter can be either an instance of the + * DateTime class, an integer containing a UNIX timestamp or a string + * which is parseable by PHP's 'strtotime' function. + * + * @access public + * @param DateTime|int|string $date Date which should be used. + * @return self + * @throws \InvalidArgumentException if the given date was not parseable. + */ + public function setDate($date) + { + if (!is_numeric($date)) { + if ($date instanceof DateTime) + $date = $date->getTimestamp(); + else { + $date = strtotime($date); + + if ($date === FALSE) + throw new \InvalidArgumentException('The given date string was not parseable.'); + } + } elseif ($date < 0) + throw new \InvalidArgumentException('The given date is not an UNIX timestamp.'); + + if ($this->version == Feed::ATOM) { + $tag = 'updated'; + $value = date(\DATE_ATOM, $date); + } elseif ($this->version == Feed::RSS2) { + $tag = 'pubDate'; + $value = date(\DATE_RSS, $date); + } else { + $tag = 'dc:date'; + $value = date("Y-m-d", $date); + } + + return $this->addElement($tag, $value); + } + + /** + * Set the 'link' element of feed item + * + * @access public + * @param string $link The content of 'link' element + * @return self + */ + public function setLink($link) + { + if ($this->version == Feed::RSS2 || $this->version == Feed::RSS1) { + $this->addElement('link', $link); + } else { + $this->addElement('link','',array('href'=>$link)); + $this->setId(Feed::uuid($link,'urn:uuid:')); + } + + return $this; + } + + /** + * Attach a external media to the feed item. + * Not supported in RSS 1.0 feeds. + * + * See RFC 4288 for syntactical correct MIME types. + * + * Note that you should avoid the use of more than one enclosure in one item, + * since some RSS aggregators don't support it. + * + * @access public + * @param string $url The URL of the media. + * @param integer $length The length of the media. + * @param string $type The MIME type attribute of the media. + * @param boolean $multiple Specifies if multiple enclosures are allowed + * @return self + * @link https://tools.ietf.org/html/rfc4288 + * @throws \InvalidArgumentException if the length or type parameter is invalid. + * @throws InvalidOperationException if this method is called on RSS1 feeds. + */ + public function addEnclosure($url, $length, $type, $multiple = TRUE) + { + if ($this->version == Feed::RSS1) + throw new InvalidOperationException('Media attachment is not supported in RSS1 feeds.'); + + // the length parameter should be set to 0 if it can't be determined + // see http://www.rssboard.org/rss-profile#element-channel-item-enclosure + if (!is_numeric($length) || $length < 0) + throw new \InvalidArgumentException('The length parameter must be an integer and greater or equals to zero.'); + + // Regex used from RFC 4287, page 41 + if (!is_string($type) || preg_match('/.+\/.+/', $type) != 1) + throw new \InvalidArgumentException('type parameter must be a string and a MIME type.'); + + $attributes = array('length' => $length, 'type' => $type); + + if ($this->version == Feed::RSS2) { + $attributes['url'] = $url; + $this->addElement('enclosure', '', $attributes, FALSE, $multiple); + } else { + $attributes['href'] = $url; + $attributes['rel'] = 'enclosure'; + $this->addElement('atom:link', '', $attributes, FALSE, $multiple); + } + + return $this; + } + + /** + * Set the 'author' element of feed item. + * Not supported in RSS 1.0 feeds. + * + * @access public + * @param string $author The author of this item + * @param string|null $email Optional email address of the author + * @param string|null $uri Optional URI related to the author + * @return self + * @throws \InvalidArgumentException if the provided email address is syntactically incorrect. + * @throws InvalidOperationException if this method is called on RSS1 feeds. + */ + public function setAuthor($author, $email = null, $uri = null) + { + if ($this->version == Feed::RSS1) + throw new InvalidOperationException('The author element is not supported in RSS1 feeds.'); + + // Regex from RFC 4287 page 41 + if ($email != null && preg_match('/.+@.+/', $email) != 1) + throw new \InvalidArgumentException('The email address is syntactically incorrect.'); + + if ($this->version == Feed::RSS2) + { + if ($email != null) + $author = $email . ' (' . $author . ')'; + + $this->addElement('author', $author); + } + else + { + $elements = array('name' => $author); + + if ($email != null) + $elements['email'] = $email; + + if ($uri != null) + $elements['uri'] = $uri; + + $this->addElement('author', $elements); + } + + return $this; + } + + /** + * Set the unique identifier of the feed item + * + * On ATOM feeds, the identifier must begin with an valid URI scheme. + * + * @access public + * @param string $id The unique identifier of this item + * @param boolean $permaLink The value of the 'isPermaLink' attribute in RSS 2 feeds. + * @return self + * @throws \InvalidArgumentException if the permaLink parameter is not boolean. + * @throws InvalidOperationException if this method is called on RSS1 feeds. + */ + public function setId($id, $permaLink = false) + { + if ($this->version == Feed::RSS2) { + if (!is_bool($permaLink)) + throw new \InvalidArgumentException('The permaLink parameter must be boolean.'); + + $permaLink = $permaLink ? 'true' : 'false'; + + $this->addElement('guid', $id, array('isPermaLink' => $permaLink)); + } elseif ($this->version == Feed::ATOM) { + // Check if the given ID is an valid URI scheme (see RFC 4287 4.2.6) + // The list of valid schemes was generated from http://www.iana.org/assignments/uri-schemes + // by using only permanent or historical schemes. + $validSchemes = array('aaa', 'aaas', 'about', 'acap', 'acct', 'cap', 'cid', 'coap', 'coaps', 'crid', 'data', 'dav', 'dict', 'dns', 'example', 'fax', 'file', 'filesystem', 'ftp', 'geo', 'go', 'gopher', 'h323', 'http', 'https', 'iax', 'icap', 'im', 'imap', 'info', 'ipp', 'ipps', 'iris', 'iris.beep', 'iris.lwz', 'iris.xpc', 'iris.xpcs', 'jabber', 'ldap', 'mailserver', 'mailto', 'mid', 'modem', 'msrp', 'msrps', 'mtqp', 'mupdate', 'news', 'nfs', 'ni', 'nih', 'nntp', 'opaquelocktoken', 'pack', 'pkcs11', 'pop', 'pres', 'prospero', 'reload', 'rtsp', 'rtsps', 'rtspu', 'service', 'session', 'shttp', 'sieve', 'sip', 'sips', 'sms', 'snews', 'snmp', 'soap.beep', 'soap.beeps', 'stun', 'stuns', 'tag', 'tel', 'telnet', 'tftp', 'thismessage', 'tip', 'tn3270', 'turn', 'turns', 'tv', 'urn', 'vemmi', 'videotex', 'vnc', 'wais', 'ws', 'wss', 'xcon', 'xcon-userid', 'xmlrpc.beep', 'xmlrpc.beeps', 'xmpp', 'z39.50', 'z39.50r', 'z39.50s'); + $found = FALSE; + $checkId = strtolower($id); + + foreach($validSchemes as $scheme) + if (strrpos($checkId, $scheme . ':', -strlen($checkId)) !== FALSE) + { + $found = TRUE; + break; + } + + if (!$found) + throw new \InvalidArgumentException("The ID must begin with an IANA-registered URI scheme."); + + $this->addElement('id', $id, NULL, TRUE); + } else + throw new InvalidOperationException('A unique ID is not supported in RSS1 feeds.'); + + return $this; + } + + } // end of class Item diff --git a/module/blog/vendor/FeedWriter/README.md b/module/blog/vendor/FeedWriter/README.md new file mode 100644 index 00000000..f630af99 --- /dev/null +++ b/module/blog/vendor/FeedWriter/README.md @@ -0,0 +1,42 @@ +# Generate **RSS 1.0**, **RSS 2.0** or **ATOM** Formatted Feeds + +This package can be used to generate feeds in either **RSS 1.0**, **RSS 2.0** or **ATOM** format. + +Applications can create a feed object, several feed item objects, set several types of properties of either feed and feed items, and add items to the feed. + +Once a feed is fully composed with its items, the feed class can generate the necessary XML structure to describe the feed in **RSS** or **ATOM** format. This structure can be directly sent to the browser, or just returned as string. + +## Requirements + +- PHP 5.3 or higher + +If you don't have **PHP 5.3** available on your system there is a version supporting **PHP 5.0** and above. See the `legacy-php-5.0` branch. + +## Documentation + +The documentation can be found in the `gh-pages` branch, or on [GitHub Pages](https://mibe.github.io/FeedWriter/). + +See the `/examples` directory for usage examples. + +See the `CHANGELOG.md` file for changes between the different versions. + +## Authors + +In chronological order: + +- [Anis uddin Ahmad](https://github.com/ajaxray) +- [Michael Bemmerl](https://github.com/mibe) +- Phil Freo +- Paul Ferrett +- Brennen Bearnes +- Michael Robinson +- Baptiste Fontaine +- Kristián Valentín +- Brandtley McMinn +- Julian Bogdani +- Cedric Gampert +- Yamek +- Thielj +- Pavel Khakhlou +- Daniel +- Tino Goratsch diff --git a/module/blog/vendor/FeedWriter/RSS1.php b/module/blog/vendor/FeedWriter/RSS1.php new file mode 100644 index 00000000..a0465cf5 --- /dev/null +++ b/module/blog/vendor/FeedWriter/RSS1.php @@ -0,0 +1,37 @@ + + * + * This file is part of the "Universal Feed Writer" project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * Wrapper for creating RSS1 feeds + * + * @package UniversalFeedWriter + */ +class RSS1 extends Feed +{ + /** + * {@inheritdoc} + */ + public function __construct() + { + parent::__construct(Feed::RSS1); + } +} diff --git a/module/blog/vendor/FeedWriter/RSS2.php b/module/blog/vendor/FeedWriter/RSS2.php new file mode 100644 index 00000000..9e36a728 --- /dev/null +++ b/module/blog/vendor/FeedWriter/RSS2.php @@ -0,0 +1,37 @@ + + * + * This file is part of the "Universal Feed Writer" project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * Wrapper for creating RSS2 feeds + * + * @package UniversalFeedWriter + */ +class RSS2 extends Feed +{ + /** + * {@inheritdoc} + */ + public function __construct() + { + parent::__construct(Feed::RSS2); + } +} diff --git a/module/blog/view/add/add.php b/module/blog/view/add/add.php index f3ec7731..62fb356f 100644 --- a/module/blog/view/add/add.php +++ b/module/blog/view/add/add.php @@ -56,7 +56,9 @@
- + $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'hidePicture']) + ]); ?>
diff --git a/module/blog/view/article/article.css b/module/blog/view/article/article.css index 7828afc9..763f8384 100755 --- a/module/blog/view/article/article.css +++ b/module/blog/view/article/article.css @@ -50,4 +50,14 @@ .blogArticlePicture { height:auto; max-width: 100%;} + } + + +#rssFeed { + text-align: right; + float: right; +} +#rssFeed p { + display: inline; + vertical-align: top; } diff --git a/module/blog/view/article/article.php b/module/blog/view/article/article.php index 527163ef..4c080174 100644 --- a/module/blog/view/article/article.php +++ b/module/blog/view/article/article.php @@ -3,12 +3,12 @@
- getData(['module', $this->getUrl(0), $this->getUrl(1), 'publishedOn'])), 'UTF-8', true) - ? strftime('%d %B %Y', $this->getData(['module', $this->getUrl(0), $this->getUrl(1), 'publishedOn'])) - : utf8_encode(strftime('%d %B %Y', $this->getData(['module', $this->getUrl(0), $this->getUrl(1), 'publishedOn']))); - $heure = mb_detect_encoding(strftime('%H:%M', $this->getData(['module', $this->getUrl(0), $this->getUrl(1), 'publishedOn'])), 'UTF-8', true) - ? strftime('%H:%M', $this->getData(['module', $this->getUrl(0), $this->getUrl(1), 'publishedOn'])) - : utf8_encode(strftime('%H:%M', $this->getData(['module', $this->getUrl(0), $this->getUrl(1), 'publishedOn']))); + getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'publishedOn'])), 'UTF-8', true) + ? strftime('%d %B %Y', $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'publishedOn'])) + : utf8_encode(strftime('%d %B %Y', $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'publishedOn']))); + $heure = mb_detect_encoding(strftime('%H:%M', $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'publishedOn'])), 'UTF-8', true) + ? strftime('%H:%M', $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'publishedOn'])) + : utf8_encode(strftime('%H:%M', $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'publishedOn']))); echo $date . ' à ' . $heure; ?>
@@ -43,11 +43,11 @@
- getData(['module', $this->getUrl(0), $this->getUrl(1), 'pictureSize']) === null ? '100' : $this->getData(['module', $this->getUrl(0), $this->getUrl(1), 'pictureSize']); ?> - getData(['module', $this->getUrl(0), $this->getUrl(1), 'hidePicture']) == false) { - echo '' . $this->getData(['module', $this->getUrl(0), $this->getUrl(1), 'picture']) . ''; + getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'pictureSize']) === null ? '100' : $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'pictureSize']); ?> + getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'hidePicture']) == false) { + echo '' . $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'picture']) . ''; } ?> getData(['module', $this->getUrl(0), $this->getUrl(1), 'content']); ?>

@@ -124,9 +124,7 @@ - -
$comment): ?> diff --git a/module/blog/view/config/config.php b/module/blog/view/config/config.php index d1e22ec0..a3779b0a 100644 --- a/module/blog/view/config/config.php +++ b/module/blog/view/config/config.php @@ -1,11 +1,29 @@ -
-
- 'buttonGrey', - 'href' => helper::baseUrl() . 'page/edit/' . $this->getUrl(0), - 'ico' => 'left', - 'value' => 'Retour' - ]); ?> + +
+
+ 'buttonGrey', + 'href' => helper::baseUrl() . 'page/edit/' . $this->getUrl(0), 'posts', + 'ico' => 'left', + 'value' => 'Retour' + ]); ?> +
+
+ helper::baseUrl() . $this->getUrl(0) . '/comment', + 'value' => 'Gérer les commentaires' + ]); ?> +
+
+ helper::baseUrl() . $this->getUrl(0) . '/add', + 'ico' => 'plus', + 'value' => 'Article' + ]); ?> +
+
+ +
+
+
+
+

Paramètres du module

+
+
+ $this->getData(['module', $this->getUrl(0), 'config', 'feeds']), + ]); ?> +
+
+ 'Texte de l\'étiquette', + 'value' => $this->getData(['module', $this->getUrl(0), 'config', 'feedsLabel']) + ]); ?> +
+
+
+
+
+
diff --git a/module/blog/view/edit/edit.php b/module/blog/view/edit/edit.php index 1b8c37a6..2fb13cda 100644 --- a/module/blog/view/edit/edit.php +++ b/module/blog/view/edit/edit.php @@ -32,7 +32,7 @@
'Titre', - 'value' => $this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'title']) + 'value' => $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'title']) ]); ?>
@@ -42,19 +42,19 @@ 'help' => 'Taille optimale de l\'image de couverture : ' . ((int) substr($this->getData(['theme', 'site', 'width']), 0, -2) - (20 * 2)) . ' x 350 pixels.', 'label' => 'Image de couverture', 'type' => 1, - 'value' => $this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'picture']) + 'value' => $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'picture']) ]); ?>
'Largeur de l\'image', - 'selected' => $this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'pictureSize']) + 'selected' => $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'pictureSize']) ]); ?>
'Position', - 'selected' => $this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'picturePosition']), + 'selected' => $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'picturePosition']), 'help' => 'Le texte de l\'article est adapté autour de l\'image' ]); ?>
@@ -62,7 +62,7 @@
$this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'hidePicture']) + 'checked' => $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'hidePicture']) ]); ?>
@@ -71,7 +71,7 @@ 'editorWysiwyg', - 'value' => $this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'content']) + 'value' => $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'content']) ]); ?>
diff --git a/module/blog/view/index/index.css b/module/blog/view/index/index.css index 8213aa1c..ae129985 100755 --- a/module/blog/view/index/index.css +++ b/module/blog/view/index/index.css @@ -46,4 +46,16 @@ .blogContent { display: none; } +} + +/* +* Flux RSS +*/ +#rssFeed { + text-align: right; + float: right; +} +#rssFeed p { + display: inline; + vertical-align: top; } \ No newline at end of file diff --git a/module/blog/view/index/index.php b/module/blog/view/index/index.php index b719040c..1434d5b7 100644 --- a/module/blog/view/index/index.php +++ b/module/blog/view/index/index.php @@ -52,6 +52,16 @@
+ getData(['module',$this->getUrl(0), 'config', 'feeds'])): ?> + + \ No newline at end of file diff --git a/module/blog/view/rss/rss.php b/module/blog/view/rss/rss.php new file mode 100644 index 00000000..4b8f6d3c --- /dev/null +++ b/module/blog/view/rss/rss.php @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/module/news/news.php b/module/news/news.php index 10486ec8..a588ee15 100644 --- a/module/news/news.php +++ b/module/news/news.php @@ -19,7 +19,8 @@ class news extends common { 'config' => self::GROUP_MODERATOR, 'delete' => self::GROUP_MODERATOR, 'edit' => self::GROUP_MODERATOR, - 'index' => self::GROUP_VISITOR + 'index' => self::GROUP_VISITOR, + 'rss' => self::GROUP_VISITOR ]; public static $news = []; @@ -32,10 +33,57 @@ class news extends common { false => 'Brouillon', true => 'Publié' ]; - const NEWS_VERSION = '1.2'; + const NEWS_VERSION = '1.3'; public static $users = []; + /** + * Flux RSS + */ + public function rss() { + + // Inclure les classes + include_once 'module/news/vendor/FeedWriter/Item.php'; + include_once 'module/news/vendor/FeedWriter/Feed.php'; + include_once 'module/news/vendor/FeedWriter/RSS2.php'; + include_once 'module/news/vendor/FeedWriter/InvalidOperationException.php'; + + date_default_timezone_set('UTC'); + + $feeds = new \FeedWriter\RSS2(); + + // En-tête + $feeds->setTitle($this->getData (['page', $this->getUrl(0),'posts','title'])); + $feeds->setLink(helper::baseUrl() . $this->getUrl(0)); + $feeds->setDescription(html_entity_decode(strip_tags($this->getData (['page', $this->getUrl(0), 'metaDescription'])))); + $feeds->setChannelElement('language', 'fr-FR'); + $feeds->setDate(time()); + $feeds->addGenerator(); + // Corps des articles + $newsIdsPublishedOns = helper::arrayCollumn($this->getData(['module', $this->getUrl(0), 'posts']), 'publishedOn', 'SORT_DESC'); + // Articles de la première page uniquement + $newsIdsPublishedOns = array_slice($newsIdsPublishedOns, 0, $this->getData(['config', 'itemsperPage']) ); + $newsIdsStates = helper::arrayCollumn($this->getData(['module', $this->getUrl(0), 'posts']), 'state', 'SORT_DESC'); + foreach($newsIdsPublishedOns as $newsId => $newsPublishedOn) { + if($newsPublishedOn <= time() AND $newsIdsStates[$newsId]) { + $newsArticle = $feeds->createNewItem(); + $newsArticle->addElementArray([ + 'title' => strip_tags($this->getData(['module', $this->getUrl(0),'posts', $newsId, 'title']) ), + 'link' => helper::baseUrl() . $this->getUrl(0), + 'description' => html_entity_decode(strip_tags($this->getData(['module', $this->getUrl(0),'posts', $newsId, 'content']))) + ]); + $feeds->addItem($newsArticle); + } + } + + // Valeurs en sortie + $this->addOutput([ + 'display' => self::DISPLAY_RSS, + 'content' => $feeds->generateFeed(), + 'view' => 'rss' + ]); + } + /** * Édition */ @@ -44,7 +92,7 @@ class news extends common { if($this->isPost()) { // Crée la news $newsId = helper::increment($this->getInput('newsAddTitle', helper::FILTER_ID), (array) $this->getData(['module', $this->getUrl(0)])); - $this->setData(['module', $this->getUrl(0), $newsId, [ + $this->setData(['module', $this->getUrl(0),'posts', $newsId, [ 'content' => $this->getInput('newsAddContent', null), 'publishedOn' => $this->getInput('newsAddPublishedOn', helper::FILTER_DATETIME, true), 'state' => $this->getInput('newsAddState', helper::FILTER_BOOLEAN), @@ -80,41 +128,55 @@ class news extends common { * Configuration */ public function config() { - // Ids des news par ordre de publication - $newsIds = array_keys(helper::arrayCollumn($this->getData(['module', $this->getUrl(0)]), 'publishedOn', 'SORT_DESC')); - // Pagination - $pagination = helper::pagination($newsIds, $this->getUrl(),$this->getData(['config','itemsperPage'])); - // Liste des pages - self::$pages = $pagination['pages']; - // News en fonction de la pagination - for($i = $pagination['first']; $i < $pagination['last']; $i++) { - // Met en forme le tableau - $date = mb_detect_encoding(strftime('%d %B %Y', $this->getData(['module', $this->getUrl(0), $newsIds[$i], 'publishedOn'])), 'UTF-8', true) - ? strftime('%d %B %Y', $this->getData(['module', $this->getUrl(0), $newsIds[$i], 'publishedOn'])) - : utf8_encode(strftime('%d %B %Y', $this->getData(['module', $this->getUrl(0), $newsIds[$i], 'publishedOn']))); - $heure = mb_detect_encoding(strftime('%H:%M', $this->getData(['module', $this->getUrl(0), $newsIds[$i], 'publishedOn'])), 'UTF-8', true) - ? strftime('%H:%M', $this->getData(['module', $this->getUrl(0), $newsIds[$i], 'publishedOn'])) - : utf8_encode(strftime('%H:%M', $this->getData(['module', $this->getUrl(0), $newsIds[$i], 'publishedOn']))); - self::$news[] = [ - $this->getData(['module', $this->getUrl(0), $newsIds[$i], 'title']), - $date .' à '. $heure, - self::$states[$this->getData(['module', $this->getUrl(0), $newsIds[$i], 'state'])], - template::button('newsConfigEdit' . $newsIds[$i], [ - 'href' => helper::baseUrl() . $this->getUrl(0) . '/edit/' . $newsIds[$i]. '/' . $_SESSION['csrf'], - 'value' => template::ico('pencil') - ]), - template::button('newsConfigDelete' . $newsIds[$i], [ - 'class' => 'newsConfigDelete buttonRed', - 'href' => helper::baseUrl() . $this->getUrl(0) . '/delete/' . $newsIds[$i] . '/' . $_SESSION['csrf'], - 'value' => template::ico('cancel') - ]) - ]; + // Soumission du formulaire + if($this->isPost()) { + $this->setData(['module', $this->getUrl(0), 'config',[ + 'feeds' => $this->getInput('newsConfigShowFeeds',helper::FILTER_BOOLEAN), + 'feedsLabel' => $this->getInput('newsConfigFeedslabel',helper::FILTER_STRING_SHORT) + ]]); + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . $this->getUrl(0) . '/config', + 'notification' => 'Modifications enregistrées', + 'state' => true + ]); + } else { + // Ids des news par ordre de publication + $newsIds = array_keys(helper::arrayCollumn($this->getData(['module', $this->getUrl(0), 'posts']), 'publishedOn', 'SORT_DESC')); + // Pagination + $pagination = helper::pagination($newsIds, $this->getUrl(),$this->getData(['config','itemsperPage'])); + // Liste des pages + self::$pages = $pagination['pages']; + // News en fonction de la pagination + for($i = $pagination['first']; $i < $pagination['last']; $i++) { + // Met en forme le tableau + $date = mb_detect_encoding(strftime('%d %B %Y', $this->getData(['module', $this->getUrl(0),'posts', $newsIds[$i], 'publishedOn'])), 'UTF-8', true) + ? strftime('%d %B %Y', $this->getData(['module', $this->getUrl(0),'posts', $newsIds[$i], 'publishedOn'])) + : utf8_encode(strftime('%d %B %Y', $this->getData(['module', $this->getUrl(0),'posts', $newsIds[$i], 'publishedOn']))); + $heure = mb_detect_encoding(strftime('%H:%M', $this->getData(['module', $this->getUrl(0),'posts', $newsIds[$i], 'publishedOn'])), 'UTF-8', true) + ? strftime('%H:%M', $this->getData(['module', $this->getUrl(0),'posts', $newsIds[$i], 'publishedOn'])) + : utf8_encode(strftime('%H:%M', $this->getData(['module', $this->getUrl(0),'posts', $newsIds[$i], 'publishedOn']))); + self::$news[] = [ + $this->getData(['module', $this->getUrl(0),'posts', $newsIds[$i], 'title']), + $date .' à '. $heure, + self::$states[$this->getData(['module', $this->getUrl(0),'posts', $newsIds[$i], 'state'])], + template::button('newsConfigEdit' . $newsIds[$i], [ + 'href' => helper::baseUrl() . $this->getUrl(0) . '/edit/' . $newsIds[$i]. '/' . $_SESSION['csrf'], + 'value' => template::ico('pencil') + ]), + template::button('newsConfigDelete' . $newsIds[$i], [ + 'class' => 'newsConfigDelete buttonRed', + 'href' => helper::baseUrl() . $this->getUrl(0) . '/delete/' . $newsIds[$i] . '/' . $_SESSION['csrf'], + 'value' => template::ico('cancel') + ]) + ]; + } + // Valeurs en sortie + $this->addOutput([ + 'title' => 'Configuration du module', + 'view' => 'config' + ]); } - // Valeurs en sortie - $this->addOutput([ - 'title' => 'Configuration du module', - 'view' => 'config' - ]); } /** @@ -122,7 +184,7 @@ class news extends common { */ public function delete() { // La news n'existe pas - if($this->getData(['module', $this->getUrl(0), $this->getUrl(2)]) === null) { + if($this->getData(['module', $this->getUrl(0),'posts', $this->getUrl(2)]) === null) { // Valeurs en sortie $this->addOutput([ 'access' => false @@ -138,7 +200,7 @@ class news extends common { } // Suppression else { - $this->deleteData(['module', $this->getUrl(0), $this->getUrl(2)]); + $this->deleteData(['module', $this->getUrl(0),'posts', $this->getUrl(2)]); // Valeurs en sortie $this->addOutput([ 'redirect' => helper::baseUrl() . $this->getUrl(0) . '/config', @@ -161,7 +223,7 @@ class news extends common { ]); } // La news n'existe pas - if($this->getData(['module', $this->getUrl(0), $this->getUrl(2)]) === null) { + if($this->getData(['module', $this->getUrl(0),'posts', $this->getUrl(2)]) === null) { // Valeurs en sortie $this->addOutput([ 'access' => false @@ -177,9 +239,9 @@ class news extends common { // Incrémente le nouvel id de la news $newsId = helper::increment($newsId, $this->getData(['module', $this->getUrl(0)])); // Supprime l'ancien news - $this->deleteData(['module', $this->getUrl(0), $this->getUrl(2)]); + $this->deleteData(['module', $this->getUrl(0),'posts', $this->getUrl(2)]); } - $this->setData(['module', $this->getUrl(0), $newsId, [ + $this->setData(['module', $this->getUrl(0),'posts', $newsId, [ 'content' => $this->getInput('newsEditContent', null), 'publishedOn' => $this->getInput('newsEditPublishedOn', helper::FILTER_DATETIME, true), 'state' => $this->getInput('newsEditState', helper::FILTER_BOOLEAN), @@ -202,7 +264,7 @@ class news extends common { unset($userFirstname); // Valeurs en sortie $this->addOutput([ - 'title' => $this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'title']), + 'title' => $this->getData(['module', $this->getUrl(0),'posts', $this->getUrl(2), 'title']), 'vendor' => [ 'flatpickr', 'tinymce' @@ -217,8 +279,8 @@ class news extends common { */ public function index() { // Ids des news par ordre de publication - $newsIdsPublishedOns = helper::arrayCollumn($this->getData(['module', $this->getUrl(0)]), 'publishedOn', 'SORT_DESC'); - $newsIdsStates = helper::arrayCollumn($this->getData(['module', $this->getUrl(0)]), 'state', 'SORT_DESC'); + $newsIdsPublishedOns = helper::arrayCollumn($this->getData(['module', $this->getUrl(0), 'posts']), 'publishedOn', 'SORT_DESC'); + $newsIdsStates = helper::arrayCollumn($this->getData(['module', $this->getUrl(0), 'posts']), 'state', 'SORT_DESC'); $newsIds = []; foreach($newsIdsPublishedOns as $newsId => $newsPublishedOn) { if($newsPublishedOn <= time() AND $newsIdsStates[$newsId]) { @@ -231,7 +293,7 @@ class news extends common { self::$pages = $pagination['pages']; // News en fonction de la pagination for($i = $pagination['first']; $i < $pagination['last']; $i++) { - self::$news[$newsIds[$i]] = $this->getData(['module', $this->getUrl(0), $newsIds[$i]]); + self::$news[$newsIds[$i]] = $this->getData(['module', $this->getUrl(0),'posts', $newsIds[$i]]); } // Valeurs en sortie $this->addOutput([ diff --git a/module/news/ressource/feed-icon-16.gif b/module/news/ressource/feed-icon-16.gif new file mode 100644 index 00000000..b0e4adf1 Binary files /dev/null and b/module/news/ressource/feed-icon-16.gif differ diff --git a/module/news/vendor/FeedWriter/ATOM.php b/module/news/vendor/FeedWriter/ATOM.php new file mode 100644 index 00000000..28494501 --- /dev/null +++ b/module/news/vendor/FeedWriter/ATOM.php @@ -0,0 +1,38 @@ + + * + * This file is part of the "Universal Feed Writer" project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * Wrapper for creating ATOM feeds + * + * @package UniversalFeedWriter + */ +class ATOM extends Feed +{ + /** + * {@inheritdoc} + */ + public function __construct() + { + parent::__construct(Feed::ATOM); + } + +} diff --git a/module/news/vendor/FeedWriter/Feed.php b/module/news/vendor/FeedWriter/Feed.php new file mode 100644 index 00000000..9e0650a8 --- /dev/null +++ b/module/news/vendor/FeedWriter/Feed.php @@ -0,0 +1,1017 @@ + + * Copyright (C) 2010-2016 Michael Bemmerl + * + * This file is part of the "Universal Feed Writer" project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * Universal Feed Writer class + * + * Generate RSS 1.0, RSS2.0 and ATOM Feeds + * + * @package UniversalFeedWriter + * @author Anis uddin Ahmad + * @link http://www.ajaxray.com/projects/rss + */ +abstract class Feed +{ + // RSS 0.90 Officially obsoleted by 1.0 + // RSS 0.91, 0.92, 0.93 and 0.94 Officially obsoleted by 2.0 + // So, define constants for RSS 1.0, RSS 2.0 and ATOM + + const RSS1 = 'RSS 1.0'; + const RSS2 = 'RSS 2.0'; + const ATOM = 'ATOM'; + + const VERSION = '1.1.0'; + + /** + * Collection of all channel elements + */ + private $channels = array(); + + /** + * Collection of items as object of \FeedWriter\Item class. + */ + private $items = array(); + + /** + * Collection of other version wise data. + * + * Currently used to store the 'rdf:about' attribute and image element of the channel (both RSS1 only). + */ + private $data = array(); + + /** + * The tag names which have to encoded as CDATA + */ + private $CDATAEncoding = array(); + + /** + * Collection of XML namespaces + */ + private $namespaces = array(); + + /** + * Contains the format of this feed. + */ + private $version = null; + + /** + * Constructor + * + * If no version is given, a feed in RSS 2.0 format will be generated. + * + * @param string $version the version constant (RSS1/RSS2/ATOM). + */ + protected function __construct($version = Feed::RSS2) + { + $this->version = $version; + + // Setting default encoding + $this->encoding = 'utf-8'; + + // Setting default value for essential channel element + $this->setTitle($version . ' Feed'); + + // Add some default XML namespaces + $this->namespaces['content'] = 'http://purl.org/rss/1.0/modules/content/'; + $this->namespaces['wfw'] = 'http://wellformedweb.org/CommentAPI/'; + $this->namespaces['atom'] = 'http://www.w3.org/2005/Atom'; + $this->namespaces['rdf'] = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'; + $this->namespaces['rss1'] = 'http://purl.org/rss/1.0/'; + $this->namespaces['dc'] = 'http://purl.org/dc/elements/1.1/'; + $this->namespaces['sy'] = 'http://purl.org/rss/1.0/modules/syndication/'; + + // Tag names to encode in CDATA + $this->addCDATAEncoding(array('description', 'content:encoded', 'summary')); + } + + // Start # public functions --------------------------------------------- + + /** + * Set the URLs for feed pagination. + * + * See RFC 5005, chapter 3. At least one page URL must be specified. + * + * @param string $nextURL The URL to the next page of this feed. Optional. + * @param string $previousURL The URL to the previous page of this feed. Optional. + * @param string $firstURL The URL to the first page of this feed. Optional. + * @param string $lastURL The URL to the last page of this feed. Optional. + * @link http://tools.ietf.org/html/rfc5005#section-3 + * @return self + * @throws \LogicException if none of the parameters are set. + */ + public function setPagination($nextURL = null, $previousURL = null, $firstURL = null, $lastURL = null) + { + if (empty($nextURL) && empty($previousURL) && empty($firstURL) && empty($lastURL)) + throw new \LogicException('At least one URL must be specified for pagination to work.'); + + if (!empty($nextURL)) + $this->setAtomLink($nextURL, 'next'); + + if (!empty($previousURL)) + $this->setAtomLink($previousURL, 'previous'); + + if (!empty($firstURL)) + $this->setAtomLink($firstURL, 'first'); + + if (!empty($lastURL)) + $this->setAtomLink($lastURL, 'last'); + + return $this; + } + + /** + * Add a channel element indicating the program used to generate the feed. + * + * @return self + * @throws InvalidOperationException if this method is called on an RSS1 feed. + */ + public function addGenerator() + { + if ($this->version == Feed::ATOM) + $this->setChannelElement('atom:generator', 'FeedWriter', array('uri' => 'https://github.com/mibe/FeedWriter')); + else if ($this->version == Feed::RSS2) + $this->setChannelElement('generator', 'FeedWriter'); + else + throw new InvalidOperationException('The generator element is not supported in RSS1 feeds.'); + + return $this; + } + + /** + * Add a XML namespace to the internal list of namespaces. After that, + * custom channel elements can be used properly to generate a valid feed. + * + * @access public + * @param string $prefix namespace prefix + * @param string $uri namespace name (URI) + * @return self + * @link http://www.w3.org/TR/REC-xml-names/ + * @throws \InvalidArgumentException if the prefix or uri is empty or NULL. + */ + public function addNamespace($prefix, $uri) + { + if (empty($prefix)) + throw new \InvalidArgumentException('The prefix may not be emtpy or NULL.'); + if (empty($uri)) + throw new \InvalidArgumentException('The uri may not be empty or NULL.'); + + $this->namespaces[$prefix] = $uri; + + return $this; + } + + /** + * Add a channel element to the feed. + * + * @access public + * @param string $elementName name of the channel tag + * @param string $content content of the channel tag + * @param array array of element attributes with attribute name as array key + * @param bool TRUE if this element can appear multiple times + * @return self + * @throws \InvalidArgumentException if the element name is not a string, empty or NULL. + */ + public function setChannelElement($elementName, $content, array $attributes = null, $multiple = false) + { + if (empty($elementName)) + throw new \InvalidArgumentException('The element name may not be empty or NULL.'); + if (!is_string($elementName)) + throw new \InvalidArgumentException('The element name must be a string.'); + + $entity['content'] = $content; + $entity['attributes'] = $attributes; + + if ($multiple === TRUE) + $this->channels[$elementName][] = $entity; + else + $this->channels[$elementName] = $entity; + + return $this; + } + + /** + * Set multiple channel elements from an array. Array elements + * should be 'channelName' => 'channelContent' format. + * + * @access public + * @param array array of channels + * @return self + */ + public function setChannelElementsFromArray(array $elementArray) + { + foreach ($elementArray as $elementName => $content) { + $this->setChannelElement($elementName, $content); + } + + return $this; + } + + /** + * Get the appropriate MIME type string for the current feed. + * + * @access public + * @return string The MIME type string. + */ + public function getMIMEType() + { + switch ($this->version) { + case Feed::RSS2 : $mimeType = "application/rss+xml"; + break; + case Feed::RSS1 : $mimeType = "application/rdf+xml"; + break; + case Feed::ATOM : $mimeType = "application/atom+xml"; + break; + default : $mimeType = "text/xml"; + } + + return $mimeType; + } + + /** + * Print the actual RSS/ATOM file + * + * Sets a Content-Type header and echoes the contents of the feed. + * Should only be used in situations where direct output is desired; + * if you need to pass a string around, use generateFeed() instead. + * + * @access public + * @param bool FALSE if the specific feed media type should be sent. + * @return void + * @throws \InvalidArgumentException if the useGenericContentType parameter is not boolean. + */ + public function printFeed($useGenericContentType = false) + { + if (!is_bool($useGenericContentType)) + throw new \InvalidArgumentException('The useGenericContentType parameter must be boolean.'); + + $contentType = "text/xml"; + + if (!$useGenericContentType) { + $contentType = $this->getMIMEType(); + } + + // Generate the feed before setting the header, so Exceptions will be nicely visible. + $feed = $this->generateFeed(); + header("Content-Type: " . $contentType . "; charset=" . $this->encoding); + echo $feed; + } + + /** + * Generate the feed. + * + * @access public + * @return string The complete feed XML. + * @throws InvalidOperationException if the link element of the feed is not set. + */ + public function generateFeed() + { + if ($this->version != Feed::ATOM && !array_key_exists('link', $this->channels)) + throw new InvalidOperationException('RSS1 & RSS2 feeds need a link element. Call the setLink method before this method.'); + + return $this->makeHeader() + . $this->makeChannels() + . $this->makeItems() + . $this->makeFooter(); + } + + /** + * Create a new Item. + * + * @access public + * @return Item instance of Item class + */ + public function createNewItem() + { + $Item = new Item($this->version); + + return $Item; + } + + /** + * Add one or more tags to the list of CDATA encoded tags + * + * @access public + * @param array $tags An array of tag names that are merged into the list of tags which should be encoded as CDATA + * @return self + */ + public function addCDATAEncoding(array $tags) + { + $this->CDATAEncoding = array_merge($this->CDATAEncoding, $tags); + + return $this; + } + + /** + * Get list of CDATA encoded properties + * + * @access public + * @return array Return an array of CDATA properties that are to be encoded as CDATA + */ + public function getCDATAEncoding() + { + return $this->CDATAEncoding; + } + + /** + * Remove tags from the list of CDATA encoded tags + * + * @access public + * @param array $tags An array of tag names that should be removed. + * @return void + */ + public function removeCDATAEncoding(array $tags) + { + // Call array_values to re-index the array. + $this->CDATAEncoding = array_values(array_diff($this->CDATAEncoding, $tags)); + } + + /** + * Add a FeedItem to the main class + * + * @access public + * @param Item $feedItem instance of Item class + * @return self + * @throws \InvalidArgumentException if the given item version mismatches. + */ + public function addItem(Item $feedItem) + { + if ($feedItem->getVersion() != $this->version) + { + $msg = sprintf('Feed type mismatch: This instance can handle %s feeds only, but item for %s feeds given.', $this->version, $feedItem->getVersion()); + throw new \InvalidArgumentException($msg); + } + + $this->items[] = $feedItem; + + return $this; + } + + // Wrapper functions ------------------------------------------------------------------- + + /** + * Set the 'encoding' attribute in the XML prolog. + * + * @access public + * @param string $encoding value of 'encoding' attribute + * @return self + * @throws \InvalidArgumentException if the encoding is not a string, empty or NULL. + */ + public function setEncoding($encoding) + { + if (empty($encoding)) + throw new \InvalidArgumentException('The encoding may not be empty or NULL.'); + if (!is_string($encoding)) + throw new \InvalidArgumentException('The encoding must be a string.'); + + $this->encoding = $encoding; + + return $this; + } + + /** + * Set the 'title' channel element + * + * @access public + * @param string $title value of 'title' channel tag + * @return self + */ + public function setTitle($title) + { + return $this->setChannelElement('title', $title); + } + + /** + * Set the date when the feed was lastly updated. + * + * This adds the 'updated' element to the feed. The value of the date parameter + * can be either an instance of the DateTime class, an integer containing a UNIX + * timestamp or a string which is parseable by PHP's 'strtotime' function. + * + * Not supported in RSS1 feeds. + * + * @access public + * @param DateTime|int|string Date which should be used. + * @return self + * @throws \InvalidArgumentException if the given date is not an instance of DateTime, a UNIX timestamp or a date string. + * @throws InvalidOperationException if this method is called on an RSS1 feed. + */ + public function setDate($date) + { + if ($this->version == Feed::RSS1) + throw new InvalidOperationException('The publication date is not supported in RSS1 feeds.'); + + // The feeds have different date formats. + $format = $this->version == Feed::ATOM ? \DATE_ATOM : \DATE_RSS; + + if ($date instanceof DateTime) + $date = $date->format($format); + else if(is_numeric($date) && $date >= 0) + $date = date($format, $date); + else if (is_string($date)) + { + $timestamp = strtotime($date); + if ($timestamp === FALSE) + throw new \InvalidArgumentException('The given date was not parseable.'); + + $date = date($format, $timestamp); + } + else + throw new \InvalidArgumentException('The given date is not an instance of DateTime, a UNIX timestamp or a date string.'); + + if ($this->version == Feed::ATOM) + $this->setChannelElement('updated', $date); + else + $this->setChannelElement('lastBuildDate', $date); + + return $this; + } + + /** + * Set a phrase or sentence describing the feed. + * + * @access public + * @param string $description Description of the feed. + * @return self + */ + public function setDescription($description) + { + if ($this->version != Feed::ATOM) + $this->setChannelElement('description', $description); + else + $this->setChannelElement('subtitle', $description); + + return $this; + } + + /** + * Set the 'link' channel element + * + * @access public + * @param string $link value of 'link' channel tag + * @return self + */ + public function setLink($link) + { + if ($this->version == Feed::ATOM) + $this->setAtomLink($link); + else + $this->setChannelElement('link', $link); + + return $this; + } + + /** + * Set custom 'link' channel elements. + * + * In ATOM feeds, only one link with alternate relation and the same combination of + * type and hreflang values. + * + * @access public + * @param string $href URI of this link + * @param string $rel relation type of the resource + * @param string $type MIME type of the target resource + * @param string $hreflang language of the resource + * @param string $title human-readable information about the resource + * @param int $length length of the resource in bytes + * @link https://www.iana.org/assignments/link-relations/link-relations.xml + * @link https://tools.ietf.org/html/rfc4287#section-4.2.7 + * @return self + * @throws \InvalidArgumentException on multiple occasions. + * @throws InvalidOperationException if the same link with the same attributes was already added to the feed. + */ + public function setAtomLink($href, $rel = null, $type = null, $hreflang = null, $title = null, $length = null) + { + $data = array('href' => $href); + + if ($rel != null) { + if (!is_string($rel) || empty($rel)) + throw new \InvalidArgumentException('rel parameter must be a string and a valid relation identifier.'); + + $data['rel'] = $rel; + } + if ($type != null) { + // Regex used from RFC 4287, page 41 + if (!is_string($type) || preg_match('/.+\/.+/', $type) != 1) + throw new \InvalidArgumentException('type parameter must be a string and a MIME type.'); + + $data['type'] = $type; + } + if ($hreflang != null) { + // Regex used from RFC 4287, page 41 + if (!is_string($hreflang) || preg_match('/[A-Za-z]{1,8}(-[A-Za-z0-9]{1,8})*/', $hreflang) != 1) + throw new \InvalidArgumentException('hreflang parameter must be a string and a valid language code.'); + + $data['hreflang'] = $hreflang; + } + if ($title != null) { + if (!is_string($title) || empty($title)) + throw new \InvalidArgumentException('title parameter must be a string and not empty.'); + + $data['title'] = $title; + } + if ($length != null) { + if (!is_int($length) || $length < 0) + throw new \InvalidArgumentException('length parameter must be a positive integer.'); + + $data['length'] = (string) $length; + } + + // ATOM spec. has some restrictions on atom:link usage + // See RFC 4287, page 12 (4.1.1) + if ($this->version == Feed::ATOM) { + foreach ($this->channels as $key => $value) { + if ($key != 'atom:link') + continue; + + // $value is an array , so check every element + foreach ($value as $linkItem) { + $attrib = $linkItem['attributes']; + // Only one link with relation alternate and same hreflang & type is allowed. + if (@$attrib['rel'] == 'alternate' && @$attrib['hreflang'] == $hreflang && @$attrib['type'] == $type) + throw new InvalidOperationException('The feed must not contain more than one link element with a' + . ' relation of "alternate" that has the same combination of type and hreflang attribute values.'); + } + } + } + + return $this->setChannelElement('atom:link', '', $data, true); + } + + /** + * Set an 'atom:link' channel element with relation=self attribute. + * Needs the full URL to this feed. + * + * @link http://www.rssboard.org/rss-profile#namespace-elements-atom-link + * @access public + * @param string $url URL to this feed + * @return self + */ + public function setSelfLink($url) + { + return $this->setAtomLink($url, 'self', $this->getMIMEType()); + } + + /** + * Set the 'image' channel element + * + * @access public + * @param string $url URL of the image + * @param string $title Title of the image. RSS only. + * @param string $link Link target URL of the image. RSS only. + * @return self + * @throws \InvalidArgumentException if the url is invalid. + * @throws \InvalidArgumentException if the title and link parameter are not a string or empty. + */ + public function setImage($url, $title = null, $link = null) + { + if (!is_string($url) || empty($url)) + throw new \InvalidArgumentException('url parameter must be a string and may not be empty or NULL.'); + + // RSS feeds have support for a title & link element. + if ($this->version != Feed::ATOM) + { + if (!is_string($title) || empty($title)) + throw new \InvalidArgumentException('title parameter must be a string and may not be empty or NULL.'); + if (!is_string($link) || empty($link)) + throw new \InvalidArgumentException('link parameter must be a string and may not be empty or NULL.'); + + $data = array('title'=>$title, 'link'=>$link, 'url'=>$url); + $name = 'image'; + } + else + { + $name = 'logo'; + $data = $url; + } + + // Special handling for RSS1 again (since RSS1 is a bit strange...) + if ($this->version == Feed::RSS1) + { + $this->data['Image'] = $data; + return $this->setChannelElement($name, '', array('rdf:resource' => $url), false); + } + else + return $this->setChannelElement($name, $data); + } + + /** + * Set the channel 'rdf:about' attribute, which is used in RSS1 feeds only. + * + * @access public + * @param string $url value of 'rdf:about' attribute of the channel element + * @return self + * @throws InvalidOperationException if this method is called and the feed is not of type RSS1. + * @throws \InvalidArgumentException if the given URL is invalid. + */ + public function setChannelAbout($url) + { + if ($this->version != Feed::RSS1) + throw new InvalidOperationException("This method is only supported in RSS1 feeds."); + if (empty($url)) + throw new \InvalidArgumentException('The about URL may not be empty or NULL.'); + if (!is_string($url)) + throw new \InvalidArgumentException('The about URL must be a string.'); + + $this->data['ChannelAbout'] = $url; + + return $this; + } + + /** + * Generate an UUID. + * + * The UUID is based on an MD5 hash. If no key is given, a unique ID as the input + * for the MD5 hash is generated. + * + * @author Anis uddin Ahmad + * @access public + * @param string $key optional key on which the UUID is generated + * @param string $prefix an optional prefix + * @return string the formatted UUID + */ + public static function uuid($key = null, $prefix = '') + { + $key = ($key == null) ? uniqid(rand()) : $key; + $chars = md5($key); + $uuid = substr($chars,0,8) . '-'; + $uuid .= substr($chars,8,4) . '-'; + $uuid .= substr($chars,12,4) . '-'; + $uuid .= substr($chars,16,4) . '-'; + $uuid .= substr($chars,20,12); + + return $prefix . $uuid; + } + + /** + * Replace invalid XML characters. + * + * @link http://www.phpwact.org/php/i18n/charsets#xml See utf8_for_xml() function + * @link http://www.w3.org/TR/REC-xml/#charsets + * @link https://github.com/mibe/FeedWriter/issues/30 + * + * @access public + * @param string $string string which should be filtered + * @param string $replacement replace invalid characters with this string + * @return string the filtered string + */ + public static function filterInvalidXMLChars($string, $replacement = '_') // default to '\x{FFFD}' ??? + { + $result = preg_replace('/[^\x{0009}\x{000a}\x{000d}\x{0020}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]+/u', $replacement, $string); + + // Did the PCRE replace failed because of bad UTF-8 data? + // If yes, try a non-multibyte regex and without the UTF-8 mode enabled. + if ($result == NULL && preg_last_error() == PREG_BAD_UTF8_ERROR) + $result = preg_replace('/[^\x09\x0a\x0d\x20-\xFF]+/', $replacement, $string); + + // In case the regex replacing failed completely, return the whole unfiltered string. + if ($result == NULL) + $result = $string; + + return $result; + } + // End # public functions ---------------------------------------------- + + // Start # private functions ---------------------------------------------- + + /** + * Returns all used XML namespace prefixes in this instance. + * This includes all channel elements and feed items. + * Unfortunately some namespace prefixes are not included, + * because they are hardcoded, e.g. rdf. + * + * @access private + * @return array Array with namespace prefix as value. + */ + private function getNamespacePrefixes() + { + $prefixes = array(); + + // Get all tag names from channel elements... + $tags = array_keys($this->channels); + + // ... and now all names from feed items + foreach ($this->items as $item) { + foreach (array_keys($item->getElements()) as $key) { + if (!in_array($key, $tags)) { + $tags[] = $key; + } + } + } + + // Look for prefixes in those tag names + foreach ($tags as $tag) { + $elements = explode(':', $tag); + + if (count($elements) != 2) + continue; + + $prefixes[] = $elements[0]; + } + + return array_unique($prefixes); + } + + /** + * Returns the XML header and root element, depending on the feed type. + * + * @access private + * @return string The XML header of the feed. + * @throws InvalidOperationException if an unknown XML namespace prefix is encountered. + */ + private function makeHeader() + { + $out = 'encoding.'" ?>' . PHP_EOL; + + $prefixes = $this->getNamespacePrefixes(); + $attributes = array(); + $tagName = ''; + $defaultNamespace = ''; + + if ($this->version == Feed::RSS2) { + $tagName = 'rss'; + $attributes['version'] = '2.0'; + } elseif ($this->version == Feed::RSS1) { + $tagName = 'rdf:RDF'; + $prefixes[] = 'rdf'; + $defaultNamespace = $this->namespaces['rss1']; + } elseif ($this->version == Feed::ATOM) { + $tagName = 'feed'; + $defaultNamespace = $this->namespaces['atom']; + + // Ugly hack to remove the 'atom' value from the prefixes array. + $prefixes = array_flip($prefixes); + unset($prefixes['atom']); + $prefixes = array_flip($prefixes); + } + + // Iterate through every namespace prefix and add it to the element attributes. + foreach ($prefixes as $prefix) { + if (!isset($this->namespaces[$prefix])) + throw new InvalidOperationException('Unknown XML namespace prefix: \'' . $prefix . '\'.' + . ' Use the addNamespace method to add support for this prefix.'); + else + $attributes['xmlns:' . $prefix] = $this->namespaces[$prefix]; + } + + // Include default namepsace, if required + if (!empty($defaultNamespace)) + $attributes['xmlns'] = $defaultNamespace; + + $out .= $this->makeNode($tagName, '', $attributes, true); + + return $out; + } + + /** + * Closes the open tags at the end of file + * + * @access private + * @return string The XML footer of the feed. + */ + private function makeFooter() + { + if ($this->version == Feed::RSS2) { + return '
' . PHP_EOL . ''; + } elseif ($this->version == Feed::RSS1) { + return ''; + } elseif ($this->version == Feed::ATOM) { + return ''; + } + } + + /** + * Creates a single node in XML format + * + * @access private + * @param string $tagName name of the tag + * @param mixed $tagContent tag value as string or array of nested tags in 'tagName' => 'tagValue' format + * @param array $attributes Attributes (if any) in 'attrName' => 'attrValue' format + * @param bool $omitEndTag True if the end tag should be omitted. Defaults to false. + * @return string formatted xml tag + * @throws \InvalidArgumentException if the tagContent is not an array and not a string. + */ + private function makeNode($tagName, $tagContent, array $attributes = null, $omitEndTag = false) + { + $nodeText = ''; + $attrText = ''; + + if ($attributes != null) { + foreach ($attributes as $key => $value) { + $value = self::filterInvalidXMLChars($value); + $value = htmlspecialchars($value); + $attrText .= " $key=\"$value\""; + } + } + + $attrText .= (in_array($tagName, $this->CDATAEncoding) && $this->version == Feed::ATOM) ? ' type="html"' : ''; + $nodeText .= "<{$tagName}{$attrText}>"; + $nodeText .= (in_array($tagName, $this->CDATAEncoding)) ? ' $value) { + if (is_array($value)) { + $nodeText .= PHP_EOL; + foreach ($value as $subValue) { + $nodeText .= $this->makeNode($key, $subValue); + } + } else if (is_string($value)) { + $nodeText .= $this->makeNode($key, $value); + } else { + throw new \InvalidArgumentException("Unknown node-value type for $key"); + } + } + } else { + $tagContent = self::filterInvalidXMLChars($tagContent); + $nodeText .= (in_array($tagName, $this->CDATAEncoding)) ? $this->sanitizeCDATA($tagContent) : htmlspecialchars($tagContent); + } + + $nodeText .= (in_array($tagName, $this->CDATAEncoding)) ? ']]>' : ''; + + if (!$omitEndTag) + $nodeText .= ""; + + $nodeText .= PHP_EOL; + + return $nodeText; + } + + /** + * Make the channels. + * + * @access private + * @return string The feed header as XML containing all the feed metadata. + */ + private function makeChannels() + { + $out = ''; + + //Start channel tag + switch ($this->version) { + case Feed::RSS2: + $out .= '' . PHP_EOL; + break; + case Feed::RSS1: + $out .= (isset($this->data['ChannelAbout']))? "data['ChannelAbout']}\">" : "channels['link']['content']}\">"; + break; + } + + //Print Items of channel + foreach ($this->channels as $key => $value) { + // In ATOM feeds, strip all ATOM namespace prefixes from the tag name. They are not needed here, + // because the ATOM namespace name is set as default namespace. + if ($this->version == Feed::ATOM && strncmp($key, 'atom', 4) == 0) { + $key = substr($key, 5); + } + + // The channel element can occur multiple times, when the key 'content' is not in the array. + if (!array_key_exists('content', $value)) { + // If this is the case, iterate through the array with the multiple elements. + foreach ($value as $singleElement) { + $out .= $this->makeNode($key, $singleElement['content'], $singleElement['attributes']); + } + } else { + $out .= $this->makeNode($key, $value['content'], $value['attributes']); + } + } + + if ($this->version == Feed::RSS1) { + //RSS 1.0 have special tag with channel + $out .= "" . PHP_EOL . "" . PHP_EOL; + foreach ($this->items as $item) { + $thisItems = $item->getElements(); + $out .= "" . PHP_EOL; + } + $out .= "" . PHP_EOL . "" . PHP_EOL . "" . PHP_EOL; + + // An image has its own element after the channel elements. + if (array_key_exists('image', $this->data)) + $out .= $this->makeNode('image', $this->data['Image'], array('rdf:about' => $this->data['Image']['url'])); + } else if ($this->version == Feed::ATOM) { + // ATOM feeds have a unique feed ID. Use the title channel element as key. + $out .= $this->makeNode('id', Feed::uuid($this->channels['title']['content'], 'urn:uuid:')); + } + + return $out; + } + + /** + * Prints formatted feed items + * + * @access private + * @return string The XML of every feed item. + */ + private function makeItems() + { + $out = ''; + + foreach ($this->items as $item) { + $thisItems = $item->getElements(); + + // The argument is printed as rdf:about attribute of item in RSS 1.0 + // We're using the link set in the item (which is mandatory) as the about attribute. + if ($this->version == Feed::RSS1) + $out .= $this->startItem($thisItems['link']['content']); + else + $out .= $this->startItem(); + + foreach ($thisItems as $feedItem) { + $name = $feedItem['name']; + + // Strip all ATOM namespace prefixes from tags when feed is an ATOM feed. + // Not needed here, because the ATOM namespace name is used as default namespace. + if ($this->version == Feed::ATOM && strncmp($name, 'atom', 4) == 0) + $name = substr($name, 5); + + $out .= $this->makeNode($name, $feedItem['content'], $feedItem['attributes']); + } + $out .= $this->endItem(); + } + + return $out; + } + + /** + * Make the starting tag of channels + * + * @access private + * @param string $about The value of about attribute which is used for RSS 1.0 only. + * @return string The starting XML tag of an feed item. + * @throws InvalidOperationException if this object misses the data for the about attribute. + */ + private function startItem($about = false) + { + $out = ''; + + if ($this->version == Feed::RSS2) { + $out .= '' . PHP_EOL; + } elseif ($this->version == Feed::RSS1) { + if ($about) { + $out .= "" . PHP_EOL; + } else { + throw new InvalidOperationException("Missing data for about attribute. Call setChannelAbout method."); + } + } elseif ($this->version == Feed::ATOM) { + $out .= "" . PHP_EOL; + } + + return $out; + } + + /** + * Closes feed item tag + * + * @access private + * @return string The ending XML tag of an feed item. + */ + private function endItem() + { + if ($this->version == Feed::RSS2 || $this->version == Feed::RSS1) { + return '' . PHP_EOL; + } elseif ($this->version == Feed::ATOM) { + return '' . PHP_EOL; + } + } + + /** + * Sanitizes data which will be later on returned as CDATA in the feed. + * + * A "]]>" respectively "", "]]>", $text); + $text = str_replace(" + * + * This file is part of the "Universal Feed Writer" project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * The exception that is thrown when an invalid operation is performed on + * the object. + * + * @package UniversalFeedWriter + */ +class InvalidOperationException extends LogicException +{ +} diff --git a/module/news/vendor/FeedWriter/Item.php b/module/news/vendor/FeedWriter/Item.php new file mode 100644 index 00000000..695afe41 --- /dev/null +++ b/module/news/vendor/FeedWriter/Item.php @@ -0,0 +1,413 @@ + + * Copyright (C) 2010-2013, 2015-2016 Michael Bemmerl + * + * This file is part of the "Universal Feed Writer" project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * Universal Feed Writer + * + * Item class - Used as feed element in Feed class + * + * @package UniversalFeedWriter + * @author Anis uddin Ahmad + * @link http://www.ajaxray.com/projects/rss + */ +class Item +{ + /** + * Collection of feed item elements + */ + private $elements = array(); + + /** + * Contains the format of this feed. + */ + private $version; + + /** + * Is used as a suffix when multiple elements have the same name. + **/ + private $_cpt = 0; + + /** + * Constructor + * + * @param string $version constant (RSS1/RSS2/ATOM) RSS2 is default. + */ + public function __construct($version = Feed::RSS2) + { + $this->version = $version; + } + + /** + * Return an unique number + * + * @access private + * @return int + **/ + private function cpt() + { + return $this->_cpt++; + } + + /** + * Add an element to elements array + * + * @access public + * @param string $elementName The tag name of an element + * @param string $content The content of tag + * @param array $attributes Attributes (if any) in 'attrName' => 'attrValue' format + * @param boolean $overwrite Specifies if an already existing element is overwritten. + * @param boolean $allowMultiple Specifies if multiple elements of the same name are allowed. + * @return self + * @throws \InvalidArgumentException if the element name is not a string, empty or NULL. + */ + public function addElement($elementName, $content, array $attributes = null, $overwrite = FALSE, $allowMultiple = FALSE) + { + if (empty($elementName)) + throw new \InvalidArgumentException('The element name may not be empty or NULL.'); + if (!is_string($elementName)) + throw new \InvalidArgumentException('The element name must be a string.'); + + $key = $elementName; + + // return if element already exists & if overwriting is disabled + // & if multiple elements are not allowed. + if (isset($this->elements[$elementName]) && !$overwrite) { + if (!$allowMultiple) + return $this; + + $key .= '-' . $this->cpt(); + } + + $this->elements[$key]['name'] = $elementName; + $this->elements[$key]['content'] = $content; + $this->elements[$key]['attributes'] = $attributes; + + return $this; + } + + /** + * Set multiple feed elements from an array. + * Elements which have attributes cannot be added by this method + * + * @access public + * @param array array of elements in 'tagName' => 'tagContent' format. + * @return self + */ + public function addElementArray(array $elementArray) + { + foreach ($elementArray as $elementName => $content) { + $this->addElement($elementName, $content); + } + + return $this; + } + + /** + * Return the collection of elements in this feed item + * + * @access public + * @return array All elements of this item. + * @throws InvalidOperationException on ATOM feeds if either a content or link element is missing. + * @throws InvalidOperationException on RSS1 feeds if a title or link element is missing. + */ + public function getElements() + { + // ATOM feeds have some specific requirements... + if ($this->version == Feed::ATOM) + { + // Add an 'id' element, if it was not added by calling the setLink method. + // Use the value of the title element as key, since no link element was specified. + if (!array_key_exists('id', $this->elements)) + $this->setId(Feed::uuid($this->elements['title']['content'], 'urn:uuid:')); + + // Either a 'link' or 'content' element is needed. + if (!array_key_exists('content', $this->elements) && !array_key_exists('link', $this->elements)) + throw new InvalidOperationException('ATOM feed entries need a link or a content element. Call the setLink or setContent method.'); + } + // ...same with RSS1 feeds. + else if ($this->version == Feed::RSS1) + { + if (!array_key_exists('title', $this->elements)) + throw new InvalidOperationException('RSS1 feed entries need a title element. Call the setTitle method.'); + if (!array_key_exists('link', $this->elements)) + throw new InvalidOperationException('RSS1 feed entries need a link element. Call the setLink method.'); + } + + return $this->elements; + } + + /** + * Return the type of this feed item + * + * @access public + * @return string The feed type, as defined in Feed.php + */ + public function getVersion() + { + return $this->version; + } + + // Wrapper functions ------------------------------------------------------ + + /** + * Set the 'description' element of feed item + * + * @access public + * @param string $description The content of the 'description' or 'summary' element + * @return self + */ + public function setDescription($description) + { + $tag = ($this->version == Feed::ATOM) ? 'summary' : 'description'; + + return $this->addElement($tag, $description); + } + + /** + * Set the 'content' element of the feed item + * For ATOM feeds only + * + * @access public + * @param string $content Content for the item (i.e., the body of a blog post). + * @return self + * @throws InvalidOperationException if this method is called on non-ATOM feeds. + */ + public function setContent($content) + { + if ($this->version != Feed::ATOM) + throw new InvalidOperationException('The content element is supported in ATOM feeds only.'); + + return $this->addElement('content', $content, array('type' => 'html')); + } + + /** + * Set the 'title' element of feed item + * + * @access public + * @param string $title The content of 'title' element + * @return self + */ + public function setTitle($title) + { + return $this->addElement('title', $title); + } + + /** + * Set the 'date' element of the feed item. + * + * The value of the date parameter can be either an instance of the + * DateTime class, an integer containing a UNIX timestamp or a string + * which is parseable by PHP's 'strtotime' function. + * + * @access public + * @param DateTime|int|string $date Date which should be used. + * @return self + * @throws \InvalidArgumentException if the given date was not parseable. + */ + public function setDate($date) + { + if (!is_numeric($date)) { + if ($date instanceof DateTime) + $date = $date->getTimestamp(); + else { + $date = strtotime($date); + + if ($date === FALSE) + throw new \InvalidArgumentException('The given date string was not parseable.'); + } + } elseif ($date < 0) + throw new \InvalidArgumentException('The given date is not an UNIX timestamp.'); + + if ($this->version == Feed::ATOM) { + $tag = 'updated'; + $value = date(\DATE_ATOM, $date); + } elseif ($this->version == Feed::RSS2) { + $tag = 'pubDate'; + $value = date(\DATE_RSS, $date); + } else { + $tag = 'dc:date'; + $value = date("Y-m-d", $date); + } + + return $this->addElement($tag, $value); + } + + /** + * Set the 'link' element of feed item + * + * @access public + * @param string $link The content of 'link' element + * @return self + */ + public function setLink($link) + { + if ($this->version == Feed::RSS2 || $this->version == Feed::RSS1) { + $this->addElement('link', $link); + } else { + $this->addElement('link','',array('href'=>$link)); + $this->setId(Feed::uuid($link,'urn:uuid:')); + } + + return $this; + } + + /** + * Attach a external media to the feed item. + * Not supported in RSS 1.0 feeds. + * + * See RFC 4288 for syntactical correct MIME types. + * + * Note that you should avoid the use of more than one enclosure in one item, + * since some RSS aggregators don't support it. + * + * @access public + * @param string $url The URL of the media. + * @param integer $length The length of the media. + * @param string $type The MIME type attribute of the media. + * @param boolean $multiple Specifies if multiple enclosures are allowed + * @return self + * @link https://tools.ietf.org/html/rfc4288 + * @throws \InvalidArgumentException if the length or type parameter is invalid. + * @throws InvalidOperationException if this method is called on RSS1 feeds. + */ + public function addEnclosure($url, $length, $type, $multiple = TRUE) + { + if ($this->version == Feed::RSS1) + throw new InvalidOperationException('Media attachment is not supported in RSS1 feeds.'); + + // the length parameter should be set to 0 if it can't be determined + // see http://www.rssboard.org/rss-profile#element-channel-item-enclosure + if (!is_numeric($length) || $length < 0) + throw new \InvalidArgumentException('The length parameter must be an integer and greater or equals to zero.'); + + // Regex used from RFC 4287, page 41 + if (!is_string($type) || preg_match('/.+\/.+/', $type) != 1) + throw new \InvalidArgumentException('type parameter must be a string and a MIME type.'); + + $attributes = array('length' => $length, 'type' => $type); + + if ($this->version == Feed::RSS2) { + $attributes['url'] = $url; + $this->addElement('enclosure', '', $attributes, FALSE, $multiple); + } else { + $attributes['href'] = $url; + $attributes['rel'] = 'enclosure'; + $this->addElement('atom:link', '', $attributes, FALSE, $multiple); + } + + return $this; + } + + /** + * Set the 'author' element of feed item. + * Not supported in RSS 1.0 feeds. + * + * @access public + * @param string $author The author of this item + * @param string|null $email Optional email address of the author + * @param string|null $uri Optional URI related to the author + * @return self + * @throws \InvalidArgumentException if the provided email address is syntactically incorrect. + * @throws InvalidOperationException if this method is called on RSS1 feeds. + */ + public function setAuthor($author, $email = null, $uri = null) + { + if ($this->version == Feed::RSS1) + throw new InvalidOperationException('The author element is not supported in RSS1 feeds.'); + + // Regex from RFC 4287 page 41 + if ($email != null && preg_match('/.+@.+/', $email) != 1) + throw new \InvalidArgumentException('The email address is syntactically incorrect.'); + + if ($this->version == Feed::RSS2) + { + if ($email != null) + $author = $email . ' (' . $author . ')'; + + $this->addElement('author', $author); + } + else + { + $elements = array('name' => $author); + + if ($email != null) + $elements['email'] = $email; + + if ($uri != null) + $elements['uri'] = $uri; + + $this->addElement('author', $elements); + } + + return $this; + } + + /** + * Set the unique identifier of the feed item + * + * On ATOM feeds, the identifier must begin with an valid URI scheme. + * + * @access public + * @param string $id The unique identifier of this item + * @param boolean $permaLink The value of the 'isPermaLink' attribute in RSS 2 feeds. + * @return self + * @throws \InvalidArgumentException if the permaLink parameter is not boolean. + * @throws InvalidOperationException if this method is called on RSS1 feeds. + */ + public function setId($id, $permaLink = false) + { + if ($this->version == Feed::RSS2) { + if (!is_bool($permaLink)) + throw new \InvalidArgumentException('The permaLink parameter must be boolean.'); + + $permaLink = $permaLink ? 'true' : 'false'; + + $this->addElement('guid', $id, array('isPermaLink' => $permaLink)); + } elseif ($this->version == Feed::ATOM) { + // Check if the given ID is an valid URI scheme (see RFC 4287 4.2.6) + // The list of valid schemes was generated from http://www.iana.org/assignments/uri-schemes + // by using only permanent or historical schemes. + $validSchemes = array('aaa', 'aaas', 'about', 'acap', 'acct', 'cap', 'cid', 'coap', 'coaps', 'crid', 'data', 'dav', 'dict', 'dns', 'example', 'fax', 'file', 'filesystem', 'ftp', 'geo', 'go', 'gopher', 'h323', 'http', 'https', 'iax', 'icap', 'im', 'imap', 'info', 'ipp', 'ipps', 'iris', 'iris.beep', 'iris.lwz', 'iris.xpc', 'iris.xpcs', 'jabber', 'ldap', 'mailserver', 'mailto', 'mid', 'modem', 'msrp', 'msrps', 'mtqp', 'mupdate', 'news', 'nfs', 'ni', 'nih', 'nntp', 'opaquelocktoken', 'pack', 'pkcs11', 'pop', 'pres', 'prospero', 'reload', 'rtsp', 'rtsps', 'rtspu', 'service', 'session', 'shttp', 'sieve', 'sip', 'sips', 'sms', 'snews', 'snmp', 'soap.beep', 'soap.beeps', 'stun', 'stuns', 'tag', 'tel', 'telnet', 'tftp', 'thismessage', 'tip', 'tn3270', 'turn', 'turns', 'tv', 'urn', 'vemmi', 'videotex', 'vnc', 'wais', 'ws', 'wss', 'xcon', 'xcon-userid', 'xmlrpc.beep', 'xmlrpc.beeps', 'xmpp', 'z39.50', 'z39.50r', 'z39.50s'); + $found = FALSE; + $checkId = strtolower($id); + + foreach($validSchemes as $scheme) + if (strrpos($checkId, $scheme . ':', -strlen($checkId)) !== FALSE) + { + $found = TRUE; + break; + } + + if (!$found) + throw new \InvalidArgumentException("The ID must begin with an IANA-registered URI scheme."); + + $this->addElement('id', $id, NULL, TRUE); + } else + throw new InvalidOperationException('A unique ID is not supported in RSS1 feeds.'); + + return $this; + } + + } // end of class Item diff --git a/module/news/vendor/FeedWriter/README.md b/module/news/vendor/FeedWriter/README.md new file mode 100644 index 00000000..f630af99 --- /dev/null +++ b/module/news/vendor/FeedWriter/README.md @@ -0,0 +1,42 @@ +# Generate **RSS 1.0**, **RSS 2.0** or **ATOM** Formatted Feeds + +This package can be used to generate feeds in either **RSS 1.0**, **RSS 2.0** or **ATOM** format. + +Applications can create a feed object, several feed item objects, set several types of properties of either feed and feed items, and add items to the feed. + +Once a feed is fully composed with its items, the feed class can generate the necessary XML structure to describe the feed in **RSS** or **ATOM** format. This structure can be directly sent to the browser, or just returned as string. + +## Requirements + +- PHP 5.3 or higher + +If you don't have **PHP 5.3** available on your system there is a version supporting **PHP 5.0** and above. See the `legacy-php-5.0` branch. + +## Documentation + +The documentation can be found in the `gh-pages` branch, or on [GitHub Pages](https://mibe.github.io/FeedWriter/). + +See the `/examples` directory for usage examples. + +See the `CHANGELOG.md` file for changes between the different versions. + +## Authors + +In chronological order: + +- [Anis uddin Ahmad](https://github.com/ajaxray) +- [Michael Bemmerl](https://github.com/mibe) +- Phil Freo +- Paul Ferrett +- Brennen Bearnes +- Michael Robinson +- Baptiste Fontaine +- Kristián Valentín +- Brandtley McMinn +- Julian Bogdani +- Cedric Gampert +- Yamek +- Thielj +- Pavel Khakhlou +- Daniel +- Tino Goratsch diff --git a/module/news/vendor/FeedWriter/RSS1.php b/module/news/vendor/FeedWriter/RSS1.php new file mode 100644 index 00000000..a0465cf5 --- /dev/null +++ b/module/news/vendor/FeedWriter/RSS1.php @@ -0,0 +1,37 @@ + + * + * This file is part of the "Universal Feed Writer" project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * Wrapper for creating RSS1 feeds + * + * @package UniversalFeedWriter + */ +class RSS1 extends Feed +{ + /** + * {@inheritdoc} + */ + public function __construct() + { + parent::__construct(Feed::RSS1); + } +} diff --git a/module/news/vendor/FeedWriter/RSS2.php b/module/news/vendor/FeedWriter/RSS2.php new file mode 100644 index 00000000..9e36a728 --- /dev/null +++ b/module/news/vendor/FeedWriter/RSS2.php @@ -0,0 +1,37 @@ + + * + * This file is part of the "Universal Feed Writer" project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * Wrapper for creating RSS2 feeds + * + * @package UniversalFeedWriter + */ +class RSS2 extends Feed +{ + /** + * {@inheritdoc} + */ + public function __construct() + { + parent::__construct(Feed::RSS2); + } +} diff --git a/module/news/view/config/config.php b/module/news/view/config/config.php index 2de459a7..f25fe3d0 100644 --- a/module/news/view/config/config.php +++ b/module/news/view/config/config.php @@ -1,26 +1,52 @@ -
-
- 'buttonGrey', - 'href' => helper::baseUrl() . 'page/edit/' . $this->getUrl(0), - 'ico' => 'left', - 'value' => 'Retour' - ]); ?> + +
+
+ 'buttonGrey', + 'href' => helper::baseUrl() . 'page/edit/' . $this->getUrl(0),'posts', + 'ico' => 'left', + 'value' => 'Retour' + ]); ?> +
+
+ helper::baseUrl() . $this->getUrl(0) . '/add', + 'ico' => 'plus', + 'value' => 'News' + ]); ?> +
+
+ +
-
- helper::baseUrl() . $this->getUrl(0) . '/add', - 'ico' => 'plus', - 'value' => 'News' - ]); ?> -
-
- - - - - - +
+
+
+

Paramètres du module

+
+
+ $this->getData(['module', $this->getUrl(0), 'config', 'feeds']), + 'help' => 'Flux limité aux articles de la première page.' + ]); ?> +
+
+ 'Texte de l\'étiquette', + 'value' => $this->getData(['module', $this->getUrl(0), 'config', 'feedsLabel']) + ]); ?> +
+
+
+
+
+ + + + + + +
Version n°
\ No newline at end of file diff --git a/module/news/view/edit/edit.php b/module/news/view/edit/edit.php index b24292e1..35671003 100644 --- a/module/news/view/edit/edit.php +++ b/module/news/view/edit/edit.php @@ -29,14 +29,14 @@

Informations générales

'Titre', - 'value' => $this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'title']) + 'value' => $this->getData(['module', $this->getUrl(0),'posts', $this->getUrl(2), 'title']) ]); ?>
'editorWysiwyg', - 'value' => $this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'content']) + 'value' => $this->getData(['module', $this->getUrl(0),'posts', $this->getUrl(2), 'content']) ]); ?>
@@ -53,7 +53,7 @@ 'La news est consultable à partir du moment ou la date de publication est passée.', 'label' => 'Date de publication', - 'value' => $this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'publishedOn']) + 'value' => $this->getData(['module', $this->getUrl(0),'posts', $this->getUrl(2), 'publishedOn']) ]); ?>
diff --git a/module/news/view/index/index.css b/module/news/view/index/index.css index 92caffc5..d500e8fe 100755 --- a/module/news/view/index/index.css +++ b/module/news/view/index/index.css @@ -1 +1,25 @@ -/* Volontairement vide */ \ No newline at end of file +/** + * This file is part of Zwii. + * + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2020, Frédéric Tempez + * @license GNU General Public License, version 3 + * @link http://zwiicms.fr/ + */ + +/* +* Flux RSS +*/ +#rssFeed { + text-align: right; + float: right; +} +#rssFeed p { + display: inline; + vertical-align: top; +} \ No newline at end of file diff --git a/module/news/view/index/index.php b/module/news/view/index/index.php index 60bbe60b..2b2636f1 100644 --- a/module/news/view/index/index.php +++ b/module/news/view/index/index.php @@ -21,6 +21,16 @@ + getData(['module',$this->getUrl(0), 'config', 'feeds'])): ?> + + - \ No newline at end of file + diff --git a/module/news/view/rss/rss.php b/module/news/view/rss/rss.php new file mode 100644 index 00000000..0015bc35 --- /dev/null +++ b/module/news/view/rss/rss.php @@ -0,0 +1 @@ +