From 35db57fd370016da003ee7ee81c7a6cb9f53c451 Mon Sep 17 00:00:00 2001 From: gerchek Date: Mon, 10 Oct 2022 16:15:19 +0500 Subject: [PATCH] last update --- plugins/koderhut/rssfeedster/Plugin.php | 164 +++++++++++++ plugins/koderhut/rssfeedster/README.md | 22 ++ .../koderhut/rssfeedster/classes/Feedster.php | 150 ++++++++++++ .../classes/contracts/IAdapter.php | 31 +++ .../classes/contracts/IDataSource.php | 32 +++ .../classes/contracts/IRenderer.php | 34 +++ .../classes/datasource/NewsSource.php | 106 +++++++++ .../classes/datasource/PostsSource.php | 117 +++++++++ .../support/adapters/BlogPostXmlAdapter.php | 108 +++++++++ .../support/adapters/NewsXmlAdapter.php | 97 ++++++++ .../classes/support/facades/DataSource.php | 29 +++ .../classes/support/facades/Renderer.php | 30 +++ .../rssfeedster/classes/view/XmlRenderer.php | 134 +++++++++++ .../rssfeedster/config/language_codes.yaml | 97 ++++++++ .../koderhut/rssfeedster/controllers/Rss.php | 32 +++ plugins/koderhut/rssfeedster/lang/en/lang.php | 97 ++++++++ .../koderhut/rssfeedster/models/Settings.php | 95 ++++++++ .../rssfeedster/models/settings/fields.yaml | 78 ++++++ plugins/koderhut/rssfeedster/phpunit.xml | 24 ++ .../rssfeedster/resources/feed-template.xml | 31 +++ .../tests/classes/FeedsterTest.php | 110 +++++++++ .../tests/classes/view/XmlRendererTest.php | 224 ++++++++++++++++++ .../tests/mock/datasource/MockDataSource.php | 75 ++++++ .../tests/mock/view/MockBlankRenderer.php | 64 +++++ .../tests/mock/view/MockJsonRenderer.php | 106 +++++++++ .../koderhut/rssfeedster/updates/version.yaml | 13 + themes/demo/pages/404.htm | 1 + themes/demo/pages/error.htm | 3 +- 28 files changed, 2103 insertions(+), 1 deletion(-) create mode 100644 plugins/koderhut/rssfeedster/Plugin.php create mode 100644 plugins/koderhut/rssfeedster/README.md create mode 100644 plugins/koderhut/rssfeedster/classes/Feedster.php create mode 100644 plugins/koderhut/rssfeedster/classes/contracts/IAdapter.php create mode 100644 plugins/koderhut/rssfeedster/classes/contracts/IDataSource.php create mode 100644 plugins/koderhut/rssfeedster/classes/contracts/IRenderer.php create mode 100644 plugins/koderhut/rssfeedster/classes/datasource/NewsSource.php create mode 100644 plugins/koderhut/rssfeedster/classes/datasource/PostsSource.php create mode 100644 plugins/koderhut/rssfeedster/classes/support/adapters/BlogPostXmlAdapter.php create mode 100644 plugins/koderhut/rssfeedster/classes/support/adapters/NewsXmlAdapter.php create mode 100644 plugins/koderhut/rssfeedster/classes/support/facades/DataSource.php create mode 100644 plugins/koderhut/rssfeedster/classes/support/facades/Renderer.php create mode 100644 plugins/koderhut/rssfeedster/classes/view/XmlRenderer.php create mode 100644 plugins/koderhut/rssfeedster/config/language_codes.yaml create mode 100644 plugins/koderhut/rssfeedster/controllers/Rss.php create mode 100644 plugins/koderhut/rssfeedster/lang/en/lang.php create mode 100644 plugins/koderhut/rssfeedster/models/Settings.php create mode 100644 plugins/koderhut/rssfeedster/models/settings/fields.yaml create mode 100644 plugins/koderhut/rssfeedster/phpunit.xml create mode 100644 plugins/koderhut/rssfeedster/resources/feed-template.xml create mode 100644 plugins/koderhut/rssfeedster/tests/classes/FeedsterTest.php create mode 100644 plugins/koderhut/rssfeedster/tests/classes/view/XmlRendererTest.php create mode 100644 plugins/koderhut/rssfeedster/tests/mock/datasource/MockDataSource.php create mode 100644 plugins/koderhut/rssfeedster/tests/mock/view/MockBlankRenderer.php create mode 100644 plugins/koderhut/rssfeedster/tests/mock/view/MockJsonRenderer.php create mode 100644 plugins/koderhut/rssfeedster/updates/version.yaml diff --git a/plugins/koderhut/rssfeedster/Plugin.php b/plugins/koderhut/rssfeedster/Plugin.php new file mode 100644 index 0000000..c5852e8 --- /dev/null +++ b/plugins/koderhut/rssfeedster/Plugin.php @@ -0,0 +1,164 @@ + + */ + +namespace KoderHut\RssFeedster; + +use Illuminate\Support\Facades\App; +use Route, + Url; + +use Cms\Classes\Controller; + +use System\Classes\PluginBase, + System\Classes\PluginManager; + +use KoderHut\RssFeedster\Classes\Feedster, + KoderHut\RssFeedster\Classes\DataSource\NewsSource, + KoderHut\RssFeedster\Classes\View\XmlRenderer, + KoderHut\RssFeedster\Models\Settings, + KoderHut\RssFeedster\Classes\Support\Adapters\NewsXmlAdapter, + KoderHut\RssFeedster\Classes\Contracts\IAdapter; + + +/** + * RssFeed Plugin Information File + */ +class Plugin + extends PluginBase +{ + /** + * Namespace const + */ + const KODERHUT_RSSFEEDSTER_NS = 'KoderHut\RssFeedster'; + + /** + * Define the plug-in dependencies + * + * @var array Plugin dependencies + */ + public $require = ['Indikator.News']; + + /** + * Returns information about this plugin. + * + * @return array + */ + public function pluginDetails() + { + return [ + 'name' => 'koderhut.rssfeedster::lang.plugin.name', + 'description' => 'koderhut.rssfeedster::lang.plugin.description', + 'author' => 'Denis-Florin Rendler (KoderHut)', + 'icon' => 'icon-rss', + 'homepage' => 'https://github.com/rendler-denis/rssfeedster', + ]; + } + + /** + * Set up the route for the RSS builder + */ + public function boot() + { + $rssUrl = Settings::get('feed_url'); + + Route::get('/{locale?}'.$rssUrl, 'KoderHut\RssFeedster\Controllers\Rss@buildRssFeed'); + + $this->app->bind('KoderHut\RssFeedster\Feed', function($app) + { + return new Feedster(); + }); + } + + /** + * Register our data source and renderer + */ + public function register() + { + /** + * Set up the data source for the feed + */ + $this->app->bind('KoderHut\RssFeedster\DataSource', function($app) + { + $config['page'] = Settings::get('post_page'); + $config['comments_anchor'] = Settings::get('comments_anchor'); + $config['controller'] = new Controller(); + + return new NewsSource($config); + }); + + /** + * Set up the adapter interface between the XML renderer and + * the blog posts data + */ + $this->app->bind(IAdapter::DI_NAMESPACE, function () { + return new NewsXmlAdapter(); + }); + + /** + * Set up the feed renderer + */ + $this->app->bind('KoderHut\RssFeedster\Renderer', function($app) + { + $config = [ + 'description' => Settings::get('feed_description'), + 'title' => Settings::get('feed_title'), + 'category' => Settings::get('feed_category'), + 'copyright' => Settings::get('feed_copyright'), + 'language' => App::getLocale(), + 'link' => Url::action('KoderHut\RssFeedster\Controllers\Rss@buildRssFeed'), + ]; + $pluginPath = PluginManager::instance()->getPluginPath(Plugin::KODERHUT_RSSFEEDSTER_NS); + $xmlTemplate = $pluginPath . DIRECTORY_SEPARATOR . 'resources' + . DIRECTORY_SEPARATOR . 'feed-template.xml'; + + return + new XmlRenderer( + $config, + file_get_contents($xmlTemplate), + $this->app->make(IAdapter::DI_NAMESPACE) + ); + }); + + return; + } + + /** + * Register the plug-in settings + * + * @return array + */ + public function registerSettings() + { + return [ + 'settings' => [ + 'label' => 'koderhut.rssfeedster::lang.plugin.name', + 'description' => 'koderhut.rssfeedster::lang.settings.base.description', + 'icon' => 'icon-rss', + 'class' => 'KoderHut\RssFeedster\Models\Settings', + 'order' => 500, + 'keywords' => 'rss feed', + 'category' => 'KoderHut', + 'permissions' => ['koderhut.rssfeedster.access_config'], + ] + ]; + } + + /** + * Register plug-in permissions + * + * @return array + */ + public function registerPermissions() + { + return [ + 'koderhut.rssfeedster.access_config' => [ + 'label' => 'koderhut.rssfeedster::lang.messages.permissions.access_config_label', + 'tab' => 'koderhut.rssfeedster::lang.plugin.name' + ], + ]; + } +} diff --git a/plugins/koderhut/rssfeedster/README.md b/plugins/koderhut/rssfeedster/README.md new file mode 100644 index 0000000..6b79f12 --- /dev/null +++ b/plugins/koderhut/rssfeedster/README.md @@ -0,0 +1,22 @@ +## RssFeedster + +> Note: This plug-in extends and depends on the [RainLab's Blog](https://octobercms.com/plugin/rainlab-blog) plug-in to generate the feed. + +An on-the-fly, W3C validated, customisable RSS feed builder plug-in. +The generated RSS feed was built according to the W3C standards. It includes all the required tags as well as a few optional ones, like copyright, feed category, feed language. These optional tags can help feed aggregators to better promote your feed. + +The feed is built on request, so it always generates an up-to-date feed. +The plug-in is very customisable by both end-user as well as extendable by other developers. + +## Features +- customisable URL +- feed built on request +- includes optional RSS fields for better integration with feed aggregators +- data source and renderer easily extendable by developers. + +## License + +The License is MIT with the exception that you are NOT allowed to sell the plug-in under ANY circumstance. +You can install, debug and fix the code for your clients and charge for those services but you are NOT allowed to charge for the plug-in itself. + +If you are in doubt about the license you can contact me at connect [ at ] rendler.me \ No newline at end of file diff --git a/plugins/koderhut/rssfeedster/classes/Feedster.php b/plugins/koderhut/rssfeedster/classes/Feedster.php new file mode 100644 index 0000000..9f13ff0 --- /dev/null +++ b/plugins/koderhut/rssfeedster/classes/Feedster.php @@ -0,0 +1,150 @@ + + */ + +namespace KoderHut\RssFeedster\Classes; + +use October\Rain\Exception\ApplicationException; + +use KoderHut\RssFeedster\Classes\Contracts\IDataSource, + KoderHut\RssFeedster\Classes\Contracts\IRenderer, + KoderHut\RssFeedster\Models\Settings, + KoderHut\RssFeedster\Classes\Support\Facades\Renderer, + KoderHut\RssFeedster\Classes\Support\Facades\DataSource; + +/** + * Class Feedster + * Service class in charge of collecting the feed data + * and rendering it + * + * @package KoderHut\RssFeedster\Classes + */ +class Feedster +{ + /** + * The feed generator name + */ + const FEED_GENERATOR_NAME = 'KoderHut.eu - RSSFeedster for OctoberCMS'; + + /** + * Feed title + * + * @var string + */ + protected $feedTitle = ''; + + /** + * Maximum number of rows to retrieve and render + * + * @var int + */ + protected $dataRowsLimit = 1; + + /** + * Either to display the full content + * + * @var bool + */ + protected $fullContent = false; + + /** + * Data source for retrieving the feed data + * + * @var IDataSource + */ + protected $source = null; + + /** + * Feed renderer + * + * @var IRenderer + */ + protected $renderer = null; + + /** + * Constructor + * + * @throws ApplicationException + */ + public function __construct(IDataSource $source = null, IRenderer $renderer = null, Settings $config = null) + { + $config = $config instanceof Settings ? $config : Settings::instance(); + + $this->validateSettings($config); + + $this->feedTitle = $config->feed_title; + $this->dataRowsLimit = $config->post_max_number; + $this->fullContent = $config->post_full_content; + + $this->source = $source ?: DataSource::getFacadeRoot(); + $this->renderer = $renderer ?: Renderer::getFacadeRoot(); + } + + /** + * Validate required settings + * + * @param Settings $settings + * + * @return bool + * + * @throws ApplicationException + */ + protected function validateSettings(Settings $settings) + { + if (empty($settings->feed_title)) { + throw new ApplicationException('Feed Title is required!'); + } + + if ($settings->post_max_number <= 0) { + $this->dataRowsLimit = 1000; + } + + return true; + } + + /** + * Set a data source for building the feed + * + * @param IDataSource $source + */ + public function setSource(IDataSource $source) + { + $this->source = $source; + } + + /** + * Set a renderer for the feed + * + * @param Renderer $renderer + */ + public function setRenderer(IRenderer $renderer) + { + $this->renderer = $renderer; + } + + /** + * Return the rendered feed data + * + * @return mixed + */ + public function getFeed() + { + $feedData = $this->source->getData($this->dataRowsLimit); + $renderedData = $this->renderer->renderData($feedData); + + return $renderedData; + } + + /** + * Return the content-type for this renderer + * + * @return string + */ + public function getContentType() + { + return $this->renderer->getContentType(); + } +} diff --git a/plugins/koderhut/rssfeedster/classes/contracts/IAdapter.php b/plugins/koderhut/rssfeedster/classes/contracts/IAdapter.php new file mode 100644 index 0000000..66614cd --- /dev/null +++ b/plugins/koderhut/rssfeedster/classes/contracts/IAdapter.php @@ -0,0 +1,31 @@ + + */ + +namespace KoderHut\RssFeedster\Classes\Contracts; + +/** + * Interface IAdapter + * + * @package KoderHut\RssFeedster\Classes\Contracts + */ +interface IAdapter +{ + /** + * Namespace const used for DI + */ + const DI_NAMESPACE = 'KoderHut\RssFeedster\Adapter'; + + /** + * Method used to connect a data source to a renderer + * + * @param mixed $item + * @param mixed $dataItem + * + * @return mixed + */ + public function getDataValue($item, $dataItem); +} \ No newline at end of file diff --git a/plugins/koderhut/rssfeedster/classes/contracts/IDataSource.php b/plugins/koderhut/rssfeedster/classes/contracts/IDataSource.php new file mode 100644 index 0000000..ba79be0 --- /dev/null +++ b/plugins/koderhut/rssfeedster/classes/contracts/IDataSource.php @@ -0,0 +1,32 @@ + + */ + +namespace KoderHut\RssFeedster\Classes\Contracts; + +/** + * Interface IDataSource + * + * Data source interface + * + * @package KoderHut\RssFeedster\Classes\Contracts + */ +interface IDataSource +{ + /** + * Load the data from source + * + * @return mixed + */ + public function loadData(); + + /** + * Load, if not laready loaded, and return the data + * + * @return mixed + */ + public function getData(); +} \ No newline at end of file diff --git a/plugins/koderhut/rssfeedster/classes/contracts/IRenderer.php b/plugins/koderhut/rssfeedster/classes/contracts/IRenderer.php new file mode 100644 index 0000000..856c3f2 --- /dev/null +++ b/plugins/koderhut/rssfeedster/classes/contracts/IRenderer.php @@ -0,0 +1,34 @@ + + */ + +namespace KoderHut\RssFeedster\Classes\Contracts; + +/** + * Interface IRenderer + * + * Renderer interface + * + * @package KoderHut\RssFeedster\Classes\Interfaces + */ +interface IRenderer +{ + /** + * Render the data + * + * @param $data + * + * @return mixed + */ + public function renderData($data); + + /** + * Return the content-type for this renderer + * + * @return string + */ + public function getContentType(); +} \ No newline at end of file diff --git a/plugins/koderhut/rssfeedster/classes/datasource/NewsSource.php b/plugins/koderhut/rssfeedster/classes/datasource/NewsSource.php new file mode 100644 index 0000000..44bc913 --- /dev/null +++ b/plugins/koderhut/rssfeedster/classes/datasource/NewsSource.php @@ -0,0 +1,106 @@ +page = $params['page']; + $this->controller = $params['controller']; + $this->commentsAnchor = $params['comments_anchor']; + } + /** + * Load the news + * + * @param int $maxItems + * + * @return mixed + */ + public function loadData($maxItems = self::MAX_NO_ITEMS) + { + $posts = null; + + if (!empty($this->data)) { + return $this->data; + } + $model = new Posts(); + $locale = request('locale'); + + $posts = $model->listFrontEnd([ + 'sort' => 'published_at desc', + 'perPage' => $maxItems, + 'locale' => in_array($locale,['ru','en','tk'])? $locale : 'en' + ]); + + + foreach ($posts as $post) { + $post->setUrl($this->page, $this->controller); +// $post->comments_url = "{$post->url}/#{$this->commentsAnchor}"; + $post->feed_content = true === $this->displayFullContent ? $post->content : $post->introductory; + } + + return $this->data = $posts; + } + + /** + * Retrieve posts from cache or load them and then return them + * + * @param int $maxItems + * + * @return mixed + */ + public function getData($maxItems = self::MAX_NO_ITEMS) + { + + if (null !== $this->data && !empty($this->data)) { + return $this->data; + } + + $this->data = $this->loadData($maxItems); + + return $this->data; + } + + /** + * Set if we display the full post content or a summary + * + * @param bool $fullContent + */ + public function setDisplayFullContent($fullContent = true) + { + $this->displayFullContent = $fullContent; + } +} diff --git a/plugins/koderhut/rssfeedster/classes/datasource/PostsSource.php b/plugins/koderhut/rssfeedster/classes/datasource/PostsSource.php new file mode 100644 index 0000000..a6cebbc --- /dev/null +++ b/plugins/koderhut/rssfeedster/classes/datasource/PostsSource.php @@ -0,0 +1,117 @@ + + */ + +namespace KoderHut\RssFeedster\Classes\DataSource; + +use KoderHut\RssFeedster\Classes\Contracts\IDataSource; + +use RainLab\Blog\Models\Post; + +/** + * Class PostsSource + * + * Feed data source retrieving blog posts. + * Requires: RainLab.Blog plug-in + * + * @package KoderHut\RssFeedster\Classes\DataSource + */ +class PostsSource + implements IDataSource +{ + /** + * Maximum number of items to display in the feed + */ + const MAX_NO_ITEMS = 10; + + /** + * Cache for the data + * + * @var array|mixed + */ + protected $data = []; + + /** + * Disaply the full content of the post + * + * @var bool + */ + protected $displayFullContent = false; + + /** + * Constructor + * + * @param array $params + */ + public function __construct($params = null) + { + if (null === $params) { + return $this; + } + + $this->page = $params['page']; + $this->controller = $params['controller']; + $this->commentsAnchor = $params['comments_anchor']; + } + + /** + * Load the blog posts + * + * @param int $maxItems + * + * @return mixed + */ + public function loadData($maxItems = self::MAX_NO_ITEMS) + { + $posts = null; + + if (!empty($this->data)) { + return $this->data; + } + $model = new Post(); + $posts = $model->listFrontEnd([ + 'sort' => 'published_at desc', + 'perPage' => $maxItems, + ]); + + foreach ($posts as $post) { + $post->setUrl($this->page, $this->controller); + $post->comments_url = "{$post->url}/#{$this->commentsAnchor}"; + $post->feed_content = true === $this->displayFullContent ? $post->content : $post->summary; + } + + return $this->data = $posts; + } + + /** + * Retrieve posts from cache or load them and then return them + * + * @param int $maxItems + * + * @return mixed + */ + public function getData($maxItems = self::MAX_NO_ITEMS) + { + if (null !== $this->data && !empty($this->data)) { + return $this->data; + } + + $this->data = $this->loadData($maxItems); + + + return $this->data; + } + + /** + * Set if we display the full post content or a summary + * + * @param bool $fullContent + */ + public function setDisplayFullContent($fullContent = true) + { + $this->displayFullContent = $fullContent; + } +} diff --git a/plugins/koderhut/rssfeedster/classes/support/adapters/BlogPostXmlAdapter.php b/plugins/koderhut/rssfeedster/classes/support/adapters/BlogPostXmlAdapter.php new file mode 100644 index 0000000..c402164 --- /dev/null +++ b/plugins/koderhut/rssfeedster/classes/support/adapters/BlogPostXmlAdapter.php @@ -0,0 +1,108 @@ + + */ + +namespace KoderHut\RssFeedster\Classes\Support\Adapters; + +use DomElement, + DOMCdataSection; + +use KoderHut\RssFeedster\Classes\Contracts\IAdapter; + +/** + * Class BlogPostXmlAdapter + * Adapter between the Posts data source and the XML renderer + * + * @package KoderHut\RssFeedster\Classes\Support\Adapters + */ +class BlogPostXmlAdapter + implements IAdapter +{ + + /** + * Transform Post data into XML elements + * + * @param mixed $item + * @param mixed $dataItem + * + * @return void + */ + public function getDataValue($item, $dataItem) + { + switch ($item->tagName) { + case 'title': + $item->nodeValue = $dataItem->title; + break; + + case 'link': + case 'guid': + $item->nodeValue = $dataItem->url; + break; + + case 'pubDate': + $item->nodeValue = $dataItem->published_at->format('r'); + break; + + case 'dc:creator': + if (!$dataItem->user instanceof User) { + break; + } + $creator = new DOMCdataSection( + "{$dataItem->user->email} ({$dataItem->user->full_name})" + ); + $item->appendChild($creator); + break; + + case 'category': + $categories = $dataItem->categories; + + if (!empty($item->nodeValue)) { + break; + } + + if (0 !== $categories->count()) { + $this->addCategories($item, $categories->lists('name')); + } + break; + + case 'description': + $description = new DOMCdataSection($dataItem->summary); + $item->appendChild($description); + break; + + case 'content:encoded': + $content = new DOMCdataSection($dataItem->feed_content); + $item->appendChild($content); + break; + + case 'comments': + $item->nodeValue = $dataItem->comments_url; + break; + + default: + break; + } + } + + /** + * Parse and add the categories of the post to the current item + * + * @param DomElement $categoryNode + * @param array $categories + */ + protected function addCategories(DomElement $categoryNode, $categories) + { + $itemNode = $categoryNode->parentNode; + $itemNode->removeChild($categoryNode); + + foreach ($categories as $categoryName) { + $newCategNode = clone($categoryNode); + $newCategNode->appendChild(new DOMCdataSection($categoryName)); + + $itemNode->appendChild($newCategNode); + } + } +} diff --git a/plugins/koderhut/rssfeedster/classes/support/adapters/NewsXmlAdapter.php b/plugins/koderhut/rssfeedster/classes/support/adapters/NewsXmlAdapter.php new file mode 100644 index 0000000..388e116 --- /dev/null +++ b/plugins/koderhut/rssfeedster/classes/support/adapters/NewsXmlAdapter.php @@ -0,0 +1,97 @@ +tagName) { + case 'title': + $item->nodeValue = $dataItem->title; + break; + + case 'link': + case 'guid': + $item->nodeValue = $dataItem->url; + break; + + case 'pubDate': + $item->nodeValue = $dataItem->published_at->format('r'); + break; + + case 'dc:creator': + if (!$dataItem->user instanceof User) { + break; + } + $creator = new DOMCdataSection( + "{$dataItem->user->email} ({$dataItem->user->full_name})" + ); + $item->appendChild($creator); + break; + +// case 'category': +// $categories = $dataItem->categories; +// +// if (!empty($item->nodeValue)) { +// break; +// } +// +// if (0 !== $categories->count()) { +// $this->addCategory($item, $categories->lists('name')); +// } +// break; + + case 'description': + $description = new DOMCdataSection($dataItem->seo_desc); + $item->appendChild($description); + break; + + case 'content:encoded': + $content = new DOMCdataSection($dataItem->feed_content); + $item->appendChild($content); + break; + + case 'comments': + $item->nodeValue = $dataItem->comments_url; + break; + + default: + break; + } + } +// /** +// * Parse and add the categories of the post to the current item +// * +// * @param DomElement $categoryNode +// * @param array $categories +// */ +// protected function addCategory(DomElement $categoryNode, $categories) +// { +// $itemNode = $categoryNode->parentNode; +// $itemNode->removeChild($categoryNode); +// +// foreach ($categories as $categoryName) { +// $newCategNode = clone($categoryNode); +// $newCategNode->appendChild(new DOMCdataSection($categoryName)); +// +// $itemNode->appendChild($newCategNode); +// } +// } +} diff --git a/plugins/koderhut/rssfeedster/classes/support/facades/DataSource.php b/plugins/koderhut/rssfeedster/classes/support/facades/DataSource.php new file mode 100644 index 0000000..d0476a1 --- /dev/null +++ b/plugins/koderhut/rssfeedster/classes/support/facades/DataSource.php @@ -0,0 +1,29 @@ + + */ + +namespace KoderHut\RssFeedster\Classes\Support\Facades; + +use Illuminate\Support\Facades\Facade; + +/** + * Class DataSource + * Facade for the data source object + * + * @package KoderHut\RssFeedster\Classes\Support\Facades + */ +class DataSource + extends Facade +{ + /** + * Return the facade namespace + * + * @return string + */ + protected static function getFacadeAccessor() { + return 'KoderHut\RssFeedster\DataSource'; + } +} \ No newline at end of file diff --git a/plugins/koderhut/rssfeedster/classes/support/facades/Renderer.php b/plugins/koderhut/rssfeedster/classes/support/facades/Renderer.php new file mode 100644 index 0000000..1140d4e --- /dev/null +++ b/plugins/koderhut/rssfeedster/classes/support/facades/Renderer.php @@ -0,0 +1,30 @@ + + */ + +namespace KoderHut\RssFeedster\Classes\Support\Facades; + +use Illuminate\Support\Facades\Facade; + +/** + * Class Renderer + * Facade for the renderer object + * + * @package KoderHut\RssFeedster\Classes\Support\Facades + */ +class Renderer + extends Facade +{ + + /** + * Return the facade namespace + * + * @return string + */ + protected static function getFacadeAccessor() { + return 'KoderHut\RssFeedster\Renderer'; + } +} \ No newline at end of file diff --git a/plugins/koderhut/rssfeedster/classes/view/XmlRenderer.php b/plugins/koderhut/rssfeedster/classes/view/XmlRenderer.php new file mode 100644 index 0000000..e6ac8ef --- /dev/null +++ b/plugins/koderhut/rssfeedster/classes/view/XmlRenderer.php @@ -0,0 +1,134 @@ + + */ + +namespace KoderHut\RssFeedster\Classes\View; + +use KoderHut\RssFeedster\Classes\Contracts\IAdapter; +use KoderHut\RssFeedster\Classes\Contracts\IRenderer as Renderer; +use KoderHut\RssFeedster\Classes\Feedster; + + +/** + * Class XmlRenderer + * Build an RSS Atom valid feed xml file based on the data passed + * + * @package KoderHut\RssFeedster\Classes\View + */ +class XmlRenderer + implements Renderer +{ + /** + * Content type header + */ + const RENDERER_CONTENT_TYPE = 'text/xml'; + + /** + * Sections that need to be wrapped into a CDATA element + * + * @var array + */ + private $cdataSections = ['link', 'description', ]; + + /** + * Init method used to initialize the feed document + * + * @param array $channelData base channel data taken from settings + * @param string $xmlTemplate + * @param IAdapter $adapter + */ + public function __construct($channelData = [], $xmlTemplate = null, IAdapter $adapter = null) + { + $this->adapter = $adapter; + + $xmlDoc = new \DOMDocument('1.0', 'UTF-8'); + $xmlDoc->formatOutput = true; + + if (null !== $xmlTemplate) { + $xmlDoc->loadXML($xmlTemplate); + } + + $this->itemTmpl = $xmlDoc->getElementsByTagName('item')->item(0); + + if (null !== $this->itemTmpl) { + $channelElement = $xmlDoc->getElementsByTagName('channel')->item(0); + $channelElement->removeChild($this->itemTmpl); + } + + $this->addChannelData($channelData, $xmlDoc); + + $this->xml = $xmlDoc; + } + + /** + * Build the feed items and attach them to the feed document + * + * @param array|Illuminate\Contracts\Pagination\Paginator $data + * + * @return mixed + */ + public function renderData($data = []) + { + $channel = $this->xml->getElementsByTagName('channel')->item(0); + + if (0 == count($data) || null === $this->itemTmpl || !$this->itemTmpl->hasChildNodes()) { + return $this->xml->saveXML(); + } + + foreach ($data as $dataItem) { + $xmlItem = clone($this->itemTmpl); + + foreach ($xmlItem->childNodes as $node) { + if (!$node instanceof \DOMElement) { + continue; + } + + $this->adapter->getDataValue($node, $dataItem); + } + + $channel->appendChild($xmlItem); + } + + return $this->xml->saveXML(); + } + + /** + * Return the content-type for this renderer + * + * @return string + */ + public function getContentType() + { + return self::RENDERER_CONTENT_TYPE; + } + + /** + * Add the channel data + * + * @param \DomDocument $xmlDoc + * @param array $channelData + */ + protected function addChannelData($channelData, $xmlDoc) + { +// $xmlDoc->getElementsByTagName('generator')->item(0) +// ->textContent = Feedster::FEED_GENERATOR_NAME; + $xmlDoc->getElementsByTagName('lastBuildDate')->item(0) + ->textContent = date('r'); + + foreach ($channelData as $element => $elValue) { + $xmlItem = $xmlDoc->getElementsByTagName($element)->item(0); + + if ($xmlItem instanceof \DOMElement) { + $xmlItem->textContent = $elValue; + } + } + + $atomLink = $xmlDoc->getElementsByTagNameNS($xmlDoc->lookupNamespaceUri('atom'), 'link')->item(0); + if ($atomLink instanceof \DOMElement) { + $atomLink->setAttribute('href', isset($channelData['link']) ? $channelData['link']: ''); + } + } +} diff --git a/plugins/koderhut/rssfeedster/config/language_codes.yaml b/plugins/koderhut/rssfeedster/config/language_codes.yaml new file mode 100644 index 0000000..a03a0a7 --- /dev/null +++ b/plugins/koderhut/rssfeedster/config/language_codes.yaml @@ -0,0 +1,97 @@ +"Afrikaans": af +"Albanian": sq +"Basque": eu +"Belarusian": be +"Bulgarian": bg +"Catalan": ca +"Chinese (Simplified)": zh-cn +"Chinese (Traditional)": zh-tw +"Croatian": hr +"Czech": cs +"Danish": da +"Dutch": nl +"Dutch (Belgium)": nl-be +"Dutch (Netherlands)": nl-nl +"English": en +"English (Australia)": en-au +"English (Belize)": en-bz +"English (Canada)": en-ca +"English (Ireland)": en-ie +"English (Jamaica)": en-jm +"English (New Zealand)": en-nz +"English (Phillipines)": en-ph +"English (South Africa)": en-za +"English (Trinidad)": en-tt +"English (United Kingdom)": en-gb +"English (United States)": en-us +"English (Zimbabwe)": en-zw +"Estonian": et +"Faeroese": fo +"Finnish": fi +"French": fr +"French (Belgium)": fr-be +"French (Canada)": fr-ca +"French (France)": fr-fr +"French (Luxembourg)": fr-lu +"French (Monaco)": fr-mc +"French (Switzerland)": fr-ch +"Galician": gl +"Gaelic": gd +"German": de +"German (Austria)": de-at +"German (Germany)": de-de +"German (Liechtenstein)": de-li +"German (Luxembourg)": de-lu +"German (Switzerland)": de-ch +"Greek": el +"Hawaiian": haw +"Hungarian": hu +"Icelandic": is +"Indonesian": in +"Irish": ga +"Italian": it +"Italian (Italy)": it-it +"Italian (Switzerland)": it-ch +"Japanese": ja +"Korean": ko +"Macedonian": mk +"Norwegian": no +"Polish": pl +"Portuguese": pt +"Portuguese (Brazil)": pt-br +"Portuguese (Portugal)": pt-pt +"Romanian": ro +"Romanian (Moldova)": ro-mo +"Romanian (Romania)": ro-ro +"Russian": ru +"Russian (Moldova)": ru-mo +"Russian (Russia)": ru-ru +"Serbian": sr +"Slovak": sk +"Slovenian": sl +"Spanish": es +"Spanish (Argentina)": es-ar +"Spanish (Bolivia)": es-bo +"Spanish (Chile)": es-cl +"Spanish (Colombia)": es-co +"Spanish (Costa Rica)": es-cr +"Spanish (Dominican Republic)": es-do +"Spanish (Ecuador)": es-ec +"Spanish (El Salvador)": es-sv +"Spanish (Guatemala)": es-gt +"Spanish (Honduras)": es-hn +"Spanish (Mexico)": es-mx +"Spanish (Nicaragua)": es-ni +"Spanish (Panama)": es-pa +"Spanish (Paraguay)": es-py +"Spanish (Peru)": es-pe +"Spanish (Puerto Rico)": es-pr +"Spanish (Spain)": es-es +"Spanish (Uruguay)": es-uy +"Spanish (Venezuela)": es-ve +"Swedish": sv +"Swedish (Finland)": sv-fi +"Swedish (Sweden)": sv-se +"Turkish": tr +"Turkmen": tk +"Ukranian": uk diff --git a/plugins/koderhut/rssfeedster/controllers/Rss.php b/plugins/koderhut/rssfeedster/controllers/Rss.php new file mode 100644 index 0000000..5e439b0 --- /dev/null +++ b/plugins/koderhut/rssfeedster/controllers/Rss.php @@ -0,0 +1,32 @@ + + */ + +namespace KoderHut\RssFeedster\Controllers; + +use App; + +use Illuminate\Support\Facades\Response; +use Illuminate\Routing\Controller as ControllerBase; + + +/** + * Rss feed builder controller + */ +class Rss + extends ControllerBase +{ + /** + * Build the RSS feed action + */ + public function buildRssFeed($locale = 'en') + { + $feedster = App::make('KoderHut\RssFeedster\Feed'); + + return Response::make($feedster->getFeed($locale)) + ->header('Content-Type', $feedster->getContentType()); + } +} diff --git a/plugins/koderhut/rssfeedster/lang/en/lang.php b/plugins/koderhut/rssfeedster/lang/en/lang.php new file mode 100644 index 0000000..8378fa4 --- /dev/null +++ b/plugins/koderhut/rssfeedster/lang/en/lang.php @@ -0,0 +1,97 @@ + [ + 'name' => 'RSSFeedster', + 'description' => 'RSS Feed on-the-fly generator.', + 'namespace' => 'KoderHut', + ], + + 'settings' => [ + 'base' => [ + 'description' => 'Manage RSSFeedster base settings.', + ], + 'tabs' => [ + 'general_title' => 'General Options', + 'posts_title' => 'Posts Options', + ], + + 'fields' => [ + 'feed_url' => [ + 'label' => 'RSS Feed URL (without domain)', + 'comment' => 'The URL you want assigned to the RSS feed without the install path', + 'description' => '', + 'default' => '/rss.xml', + ], + + 'feed_title' => [ + 'label' => 'RSS Feed Title', + 'comment' => 'The title of your RSS feed (can also be the site name)', + 'description' => 'The title of your RSS feed (can also be the site name)', + 'placeholder' => '', + 'default' => 'RSS Feed Title', + ], + + 'feed_description' => [ + 'label' => 'Short Description of The RSS Feed', + 'comment' => 'A short description of the website that will be displayed on the feed', + 'description' => 'A short description of the website that will be displayed on the feed', + ], + + 'feed_category' => [ + 'label' => 'RSS Feed Category', + 'comment' => 'The category element is used to specify a category for your feed.', + 'description' => 'The category element makes it possible for RSS aggregators to group sites based on category.', + 'placeholder' => '', + ], + + 'feed_copyright' => [ + 'label' => 'RSS Feed Copyright Notice', + 'comment' => 'The copyright element notifies about copyrighted material.', + 'description' => 'The copyright element notifies about copyrighted material.', + 'placeholder' => '', + ], + + 'feed_language' => [ + 'label' => 'RSS Feed Language', + 'comment' => 'The language element is used to specify the language used to write your document.', + 'description' => 'The language element makes it possible for RSS aggregators to group sites based on language', + 'placeholder' => '', + ], + + 'post_max_number' => [ + 'label' => 'Max No. of Posts', + 'comment' => 'The maximum number of posts to display into the feed. Could generate overload if the number is to high! Use -1 to disable this limit.', + 'description' => 'The maximum number of posts to display into the feed. Could generate overload if the number is to high!', + ], + + 'post_full_content' => [ + 'label' => 'Display Full Content', + 'comment' => 'Display the full post content into the feed or an excerpt', + 'description' => 'Display the full post content into the feed or an excerpt', + ], + + 'post_page' => [ + 'label' => 'Post page', + 'comment' => 'Set the page used to render the post', + 'description' => 'Set the page used to render the post', + ], + + 'comments_anchor' => [ + 'label' => 'Comments section anchor', + 'comment' => 'Set ID of the element hosting the comments section', + 'description' => 'Set ID of the element hosting the comments section', + 'placeholder' => 'post-comments', + ], + + ], + + ], + + 'messages' => [ + 'permissions' => [ + 'access_config_label' => 'Access Feed Configs' + ], + ], + +]; diff --git a/plugins/koderhut/rssfeedster/models/Settings.php b/plugins/koderhut/rssfeedster/models/Settings.php new file mode 100644 index 0000000..14937a1 --- /dev/null +++ b/plugins/koderhut/rssfeedster/models/Settings.php @@ -0,0 +1,95 @@ + + */ + +namespace KoderHut\RssFeedster\Models; + +use Model, + Lang, + File, + Yaml as YamlParser; + +use System\Classes\PluginManager; + +use Cms\Classes\Page; + +use KoderHut\RssFeedster\Plugin; + +/** + * Settings Model + */ +class Settings + extends Model +{ + use \October\Rain\Database\Traits\Validation; + + /** + * @var array + */ + public $implement = ['System.Behaviors.SettingsModel']; + + /** + * @var string + */ + public $settingsCode = 'koderhut_rssfeedster_settings'; + + /** + * @var string + */ + public $settingsFields = 'fields.yaml'; + + /** + * Validation rules + */ + public $rules = [ + 'feed_url' => 'required', + 'feed_title' => 'required', + ]; + + + /** + * Init default data + */ + public function initSettingsData() + { + $this->feed_title + = Lang::get('koderhut.rssfeedster::lang.settings.fields.feed_title.default'); + $this->feed_url + = Lang::get('koderhut.rssfeedster::lang.settings.fields.feed_url.default'); + $this->post_page = '404'; + } + + /** + * Return the post_page dropdown options + * + * @return array + */ + public function getPostPageOptions() + { + return Page::sortBy('baseFileName')->lists('baseFileName', 'baseFileName'); + } + + /** + * Load and return the feed_language dropdown options + * + * @return array + */ + public function getFeedLanguageOptions() + { + $pluginPath = PluginManager::instance()->getPluginPath(Plugin::KODERHUT_RSSFEEDSTER_NS); + $path = $pluginPath . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'language_codes.yaml'; + + if (!File::exists($path)) { + return []; + } + + $languages = YamlParser::parseFile($path); + + $languages = is_array($languages) ? array_flip($languages) : []; + + return $languages; + } +} \ No newline at end of file diff --git a/plugins/koderhut/rssfeedster/models/settings/fields.yaml b/plugins/koderhut/rssfeedster/models/settings/fields.yaml new file mode 100644 index 0000000..29de0a0 --- /dev/null +++ b/plugins/koderhut/rssfeedster/models/settings/fields.yaml @@ -0,0 +1,78 @@ +fields: + feed_url: + tab: koderhut.rssfeedster::lang.settings.tabs.general_title + label: koderhut.rssfeedster::lang.settings.fields.feed_url.label + commentAbove: koderhut.rssfeedster::lang.settings.fields.feed_url.comment + default: '/rss.xml' + +tabs: + fields: +# General tab + feed_title: + tab: koderhut.rssfeedster::lang.settings.tabs.general_title + label: koderhut.rssfeedster::lang.settings.fields.feed_title.label + placeholder: koderhut.rssfeedster::lang.settings.fields.feed_title.placeholder + comment: koderhut.rssfeedster::lang.settings.fields.feed_title.comment + description: koderhut.rssfeedster::lang.settings.fields.feed_title.description + + feed_description: + tab: koderhut.rssfeedster::lang.settings.tabs.general_title + label: koderhut.rssfeedster::lang.settings.fields.feed_description.label + comment: koderhut.rssfeedster::lang.settings.fields.feed_description.comment + description: koderhut.rssfeedster::lang.settings.fields.feed_description.description + type: textarea + size: small + + feed_category: + tab: koderhut.rssfeedster::lang.settings.tabs.general_title + label: koderhut.rssfeedster::lang.settings.fields.feed_category.label + placeholder: koderhut.rssfeedster::lang.settings.fields.feed_category.placeholder + comment: koderhut.rssfeedster::lang.settings.fields.feed_category.comment + description: koderhut.rssfeedster::lang.settings.fields.feed_category.description + + feed_copyright: + tab: koderhut.rssfeedster::lang.settings.tabs.general_title + label: koderhut.rssfeedster::lang.settings.fields.feed_copyright.label + placeholder: koderhut.rssfeedster::lang.settings.fields.feed_copyright.placeholder + comment: koderhut.rssfeedster::lang.settings.fields.feed_copyright.comment + description: koderhut.rssfeedster::lang.settings.fields.feed_copyright.description + + feed_language: + tab: koderhut.rssfeedster::lang.settings.tabs.general_title + label: koderhut.rssfeedster::lang.settings.fields.feed_language.label + placeholder: koderhut.rssfeedster::lang.settings.fields.feed_language.placeholder + comment: koderhut.rssfeedster::lang.settings.fields.feed_language.comment + description: koderhut.rssfeedster::lang.settings.fields.feed_language.description + type: dropdown + + comments_anchor: + tab: koderhut.rssfeedster::lang.settings.tabs.general_title + label: koderhut.rssfeedster::lang.settings.fields.comments_anchor.label + placeholder: koderhut.rssfeedster::lang.settings.fields.comments_anchor.placeholder + comment: koderhut.rssfeedster::lang.settings.fields.comments_anchor.comment + description: koderhut.rssfeedster::lang.settings.fields.comments_anchor.description + type: text + + +# Posts tab + post_max_number: + tab: koderhut.rssfeedster::lang.settings.tabs.posts_title + label: koderhut.rssfeedster::lang.settings.fields.post_max_number.label + comment: koderhut.rssfeedster::lang.settings.fields.post_max_number.comment + description: koderhut.rssfeedster::lang.settings.fields.post_max_number.description + default: 20 + + post_full_content: + tab: koderhut.rssfeedster::lang.settings.tabs.posts_title + label: koderhut.rssfeedster::lang.settings.fields.post_full_content.label + comment: koderhut.rssfeedster::lang.settings.fields.post_full_content.comment + description: koderhut.rssfeedster::lang.settings.fields.post_full_content.description + type: checkbox + + post_page: + tab: koderhut.rssfeedster::lang.settings.tabs.posts_title + label: koderhut.rssfeedster::lang.settings.fields.post_page.label + comment: koderhut.rssfeedster::lang.settings.fields.post_page.comment + description: koderhut.rssfeedster::lang.settings.fields.post_full_content.description + type: dropdown + default: 'blog/post' diff --git a/plugins/koderhut/rssfeedster/phpunit.xml b/plugins/koderhut/rssfeedster/phpunit.xml new file mode 100644 index 0000000..83246b5 --- /dev/null +++ b/plugins/koderhut/rssfeedster/phpunit.xml @@ -0,0 +1,24 @@ + + + + + ./tests + + + + + + + + + \ No newline at end of file diff --git a/plugins/koderhut/rssfeedster/resources/feed-template.xml b/plugins/koderhut/rssfeedster/resources/feed-template.xml new file mode 100644 index 0000000..91806b2 --- /dev/null +++ b/plugins/koderhut/rssfeedster/resources/feed-template.xml @@ -0,0 +1,31 @@ + + + + + + + <link /> + <atom:link href="" rel="self" type="application/rss+xml" /> + <description /> + <copyright /> + <lastBuildDate /> + <language /> + <docs>http://www.rssboard.org/rss-specification</docs> + + <item> + <link /> + <title /> + <guid isPermaLink="false" /> + <pubDate /> + <dc:creator /> + <description /> + <content:encoded /> + <comments /> + <category /> + </item> + </channel> +</rss> diff --git a/plugins/koderhut/rssfeedster/tests/classes/FeedsterTest.php b/plugins/koderhut/rssfeedster/tests/classes/FeedsterTest.php new file mode 100644 index 0000000..f91b141 --- /dev/null +++ b/plugins/koderhut/rssfeedster/tests/classes/FeedsterTest.php @@ -0,0 +1,110 @@ +<?php +/** + * Author : Denis-Florin Rendler + * Date : 21/09/15 + * Copyright (c) 2015 Denis-Florin Rendler <connect@rendler.me> + */ + +namespace KoderHut\RssFeedster\Tests\Classes; + +use App, + PluginTestCase; + +use KoderHut\RssFeedster\Classes\Feedster, + KoderHut\RssFeedster\Classes\Support\Facades\DataSource, + KoderHut\RssFeedster\Classes\Support\Facades\Renderer, + KoderHut\RssFeedster\Models\Settings; + +use KoderHut\RssFeedster\Tests\Mock\DataSource\MockDataSource; +use KoderHut\RssFeedster\Tests\Mock\View\MockBlankRenderer; +use KoderHut\RssFeedster\Tests\Mock\View\MockJsonRenderer; + + +/** + * Class FeedsterTest + * @package KoderHut\RssFeedster\Tests\Classes + */ +class FeedsterTest + extends PluginTestCase +{ + + /** + * Set up the test case + */ + public function setUp() + { + parent::setUp(); + + /** + * Create a mock-up of the Settings object + * TODO: find a better way to mock this object + */ + $config = Settings::instance(); + $config->setAttribute('feed_title', 'test'); + + /** + * Set up the data source for the feed + */ + App::bind('KoderHut\RssFeedster\DataSource', function($app) + { + return new MockDataSource(); + }); + + /** + * Set up the feed renderer + */ + App::bind('KoderHut\RssFeedster\Renderer', function($app) + { + $config = [ + 'description' => 'feed description', + 'title' => 'feed title', + 'category' => 'feed category', + 'copyright' => 'feed copyright', + 'language' => 'feed language', + 'link' => 'feed url', + ]; + + return new MockJsonRenderer($config); + }); + + $this->feed = new Feedster(null, null, $config); + } + + /** + * Test object injection replacement + */ + public function testObjectReplacement() + { + $this->assertInstanceOf( + 'KoderHut\RssFeedster\Tests\Mock\DataSource\MockDataSource', + DataSource::getFacadeRoot() + ); + + $this->assertInstanceOf( + 'KoderHut\RssFeedster\Tests\Mock\View\MockJsonRenderer', + Renderer::getFacadeRoot() + ); + } + + /** + * Test that we successfully changed the renderer to + * a JSON renderer + */ + public function testJsonReplacementRenderer() + { + $this->assertJson($this->feed->getFeed()); + } + + /** + * Test that we successfully changed the sources + */ + public function testSources() + { + $this->feed->setRenderer(new MockBlankRenderer(['customCfgData' => 'data'])); + $feedData = $this->feed->getFeed(); + + $this->assertArrayHasKey('customCfgData', $feedData['channel']); + + $this->assertCount(3, $feedData['items']); + } +} \ No newline at end of file diff --git a/plugins/koderhut/rssfeedster/tests/classes/view/XmlRendererTest.php b/plugins/koderhut/rssfeedster/tests/classes/view/XmlRendererTest.php new file mode 100644 index 0000000..e95700d --- /dev/null +++ b/plugins/koderhut/rssfeedster/tests/classes/view/XmlRendererTest.php @@ -0,0 +1,224 @@ +<?php +/** + * Author : Denis-Florin Rendler + * Date : 21/09/15 + * Copyright (c) 2015 Denis-Florin Rendler <connect@rendler.me> + */ + +namespace KoderHut\RssFeedster\Tests\Classes\View; + +use Carbon\Carbon; +use KoderHut\RssFeedster\Classes\Support\Adapters\BlogPostXmlAdapter; +use PluginTestCase, + Model; + +use KoderHut\RssFeedster\Classes\Contracts\IAdapter; +use KoderHut\RssFeedster\Classes\View\XmlRenderer; + +/** + * Class XmlRendererTest + * @package KoderHut\RssFeedster\Tests\Classes\View + */ +class XmlRendererTest + extends PluginTestCase +{ + /** + * Base feed XML structure + * + * @var string + */ + private $xmlBaseStructure = <<<XML +<?xml version="1.0" encoding="UTF-8"?> +<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"> + <channel> + <description /> + <title /> + <category /> + <copyright /> + <language /> + <link /> + <atom:link rel="self" type="application/atom+xml" href=""/> + <generator /> + <lastBuildDate /> + </channel> +</rss> +XML; + + /** + * Base feed XML structure for the channel tag + * + * @var string + */ + private $xmlItemStructure = <<<XML +<?xml version="1.0" encoding="UTF-8"?> +<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"> + <channel> + <description /> + <title /> + <category /> + <copyright /> + <language /> + <link /> + <atom:link rel="self" type="application/atom+xml" href=""/> + <generator /> + <lastBuildDate /> + + <item> + <title /> + <link /> + <guid /> + <description /> + <pubDate /> + </item> + </channel> +</rss> +XML; + + private $xmlTestItemStructure = <<<XML +<?xml version="1.0" encoding="UTF-8"?> +<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"> + <channel> + <description /> + <title /> + <category /> + <copyright /> + <language /> + <link /> + <atom:link rel="self" type="application/atom+xml" href=""/> + <generator /> + <lastBuildDate /> + + <item> + <title>post title + + + + Tue, 29-Sep-2015 + + + +XML; + + /** + * Object placeholder + * + * @var null + */ + private $stub = null; + + /** + * Set up the test + */ + public function setUp() + { + parent::setUp(); + + $adapterObj = new BlogPostXmlAdapter(); + + $this->stub + = $this->getMockBuilder(IAdapter::class)->getMock(); + + $this->stub + ->method('getDataValue') + ->willReturnCallback([$adapterObj, 'getDataValue']); + } + + /** + * Tear down after test + */ + public function tearDown() + { + parent::tearDown(); + + unset($this->stub); + } + + /** + * Test the XMLRenderer initial object + */ + public function testObjectBuild() + { + $xml = new XmlRenderer( + [ + 'description' => 'feed description', + 'title' => 'feed title', + 'category' => 'feed category', + 'copyright' => 'feed copyright', + 'language' => 'en_gb', + 'link' => 'http://koderhut.eu' + ], + $this->xmlBaseStructure, + $this->stub + ); + $baseXmlDoc = new \DOMDocument(); + $objXmlDoc = new \DOMDocument(); + + $objXmlDoc->loadXml($xml->renderData()); + $baseXmlDoc->loadXml($this->xmlBaseStructure); + + $this->assertEquals(XmlRenderer::RENDERER_CONTENT_TYPE, $xml->getContentType()); + + $this->assertEqualXMLStructure($baseXmlDoc->firstChild, $objXmlDoc->firstChild); + + $this->assertEqualXMLStructure($baseXmlDoc->firstChild, $objXmlDoc->firstChild, true); + } + + /** + * Test the XMLRenderer object with data + */ + public function testItemRender() + { + $xml = new XmlRenderer( + [ + 'description' => 'feed description', + 'title' => 'feed title', + 'category' => 'feed category', + 'copyright' => 'feed copyright', + 'language' => 'en_gb', + 'link' => 'http://koderhut.eu' + ], + $this->xmlItemStructure, + $this->stub + ); + $objXmlDoc = new \DOMDocument(); + $baseRssDoc = new \DOMDocument(); + $objItem = false; + $baseItem = false; + $attributes = [ + 'title' => 'post title', + 'url' => 'post url', + 'summary' => 'post summary', + 'published_at' => new Carbon('Tue, 29-Sep-2015'), + ]; + $items = [ + Model::make($attributes), + Model::make($attributes), + Model::make($attributes), + ]; + + $objXmlDoc->loadXml($xml->renderData($items)); + $baseRssDoc->loadXml($this->xmlTestItemStructure); + + $this->assertEqualXMLStructure( + $baseRssDoc->getElementsByTagName('item')->item(0), + $objXmlDoc->getElementsByTagName('item')->item(0) + ); + + $objItem = $objXmlDoc->getElementsByTagName('item')->item(0); + $baseItem = $baseRssDoc->getElementsByTagName('item')->item(0); + + $this->assertTrue($objItem->hasChildNodes()); + + $this->assertEquals( + $baseItem->getElementsByTagName('title')->item(0)->nodeValue, + $objItem->getElementsByTagName('title')->item(0)->nodeValue + ); + + $this->assertEquals( + $baseItem->getElementsByTagName('link')->item(0)->nodeValue, + $objItem->getElementsByTagName('link')->item(0)->nodeValue + ); + + } + +} \ No newline at end of file diff --git a/plugins/koderhut/rssfeedster/tests/mock/datasource/MockDataSource.php b/plugins/koderhut/rssfeedster/tests/mock/datasource/MockDataSource.php new file mode 100644 index 0000000..f035b0d --- /dev/null +++ b/plugins/koderhut/rssfeedster/tests/mock/datasource/MockDataSource.php @@ -0,0 +1,75 @@ + + */ + +namespace KoderHut\RssFeedster\Tests\Mock\DataSource; + +use Model; + +use KoderHut\RssFeedster\Classes\Contracts\IDataSource; + +/** + * Class PostsSource + * + * Feed data source retrieving blog posts. + * Requires: RainLab.Blog plug-in + * + * @package KoderHut\RssFeedster\Classes\DataSource + */ +class MockDataSource + implements IDataSource +{ + /** + * Cache for the data + * + * @var array|mixed + */ + protected $data = []; + + /** + * Load the blog posts + * + * @return mixed + */ + public function loadData() + { + $items = []; + $attributes = [ + 'title' => 'post title', + 'url' => 'post url', + 'summary' => 'post summary', + 'pubDate' => 'Tue, 29-Sep-2015', + ]; + + if (!empty($this->data)) { + return $this->data; + } + + $items = [ + Model::make($attributes), + Model::make($attributes), + Model::make($attributes), + ]; + + return $this->data = $items; + } + + /** + * Retrieve posts from cache or load them and then return them + * + * @return mixed + */ + public function getData() + { + if (null !== $this->data && !empty($this->data)) { + return $this->data; + } + + $this->data = $this->loadData(); + + return $this->data; + } +} \ No newline at end of file diff --git a/plugins/koderhut/rssfeedster/tests/mock/view/MockBlankRenderer.php b/plugins/koderhut/rssfeedster/tests/mock/view/MockBlankRenderer.php new file mode 100644 index 0000000..0c129a2 --- /dev/null +++ b/plugins/koderhut/rssfeedster/tests/mock/view/MockBlankRenderer.php @@ -0,0 +1,64 @@ + + */ + +namespace KoderHut\RssFeedster\Tests\Mock\View; + +use KoderHut\RssFeedster\Classes\Contracts\IRenderer as Renderer; + + +/** + * Class MockBlankRenderer + * Build an RSS Atom valid feed xml file based on the data passed + * + * @package KoderHut\RssFeedster\Classes\View + */ +class MockBlankRenderer + implements Renderer +{ + /** + * Content type header + */ + const RENDERER_CONTENT_TYPE = 'text/html'; + + /** + * Init method used to initialize the feed document + * + * @param array $channelData base channel data taken from settings + */ + public function __construct($channelData = []) + { + $this->data['channel'] = $channelData; + } + + /** + * Build the feed items and attach them to the feed document + * + * @param array|Illuminate\Contracts\Pagination\Paginator $data + * + * @return mixed + */ + public function renderData($data = []) + { + if (empty($data)) { + return $this->data; + } + + $this->data['items'] = $data; + + return $this->data; + } + + /** + * Return the content-type for this renderer + * + * @return string + */ + public function getContentType() + { + return self::RENDERER_CONTENT_TYPE; + } +} diff --git a/plugins/koderhut/rssfeedster/tests/mock/view/MockJsonRenderer.php b/plugins/koderhut/rssfeedster/tests/mock/view/MockJsonRenderer.php new file mode 100644 index 0000000..543806b --- /dev/null +++ b/plugins/koderhut/rssfeedster/tests/mock/view/MockJsonRenderer.php @@ -0,0 +1,106 @@ + + */ + +namespace KoderHut\RssFeedster\Tests\Mock\View; + +use KoderHut\RssFeedster\Classes\Contracts\IRenderer as Renderer; +use KoderHut\RssFeedster\Classes\Feedster; + + +/** + * Class XmlRenderer + * Build an RSS Atom valid feed xml file based on the data passed + * + * @package KoderHut\RssFeedster\Classes\View + */ +class MockJsonRenderer + implements Renderer +{ + /** + * Content type header + */ + const RENDERER_CONTENT_TYPE = 'application/json'; + + /** + * Init method used to initialize the feed document + * + * @param array $channelData base channel data taken from settings + */ + public function __construct($channelData = []) + { + $jsonDoc = new \StdClass(); + $rssElement = new \StdClass(); + $channelElement = new \StdClass(); + + + $this->addChannelData($channelElement, $channelData, $jsonDoc); + + $rssElement->channel = $channelElement; + + $jsonDoc->rss = $rssElement; + + $this->jsonDoc = $jsonDoc; + } + + /** + * Build the feed items and attach them to the feed document + * + * @param array|Illuminate\Contracts\Pagination\Paginator $data + * + * @return mixed + */ + public function renderData($data = []) + { + $channel = $this->jsonDoc->rss->channel; + + if (empty($data)) { + return json_encode($this->jsonDoc); + } + + foreach ($data as $item) { + $jsonItem = new \StdClass(); + + $jsonItem->title = $item->title; + $jsonItem->link = $item->url; + $jsonItem->guid = $item->url; + $jsonItem->description = $item->description; + $jsonItem->pubDate = date('D, d-M-Y', $item->published_at); + + $channelItems[] = $jsonItem; + } + + $channel->item = $channelItems; + + return json_encode($this->jsonDoc); + } + + /** + * Return the content-type for this renderer + * + * @return string + */ + public function getContentType() + { + return self::RENDERER_CONTENT_TYPE; + } + + /** + * Add the channel data + * + * @param \DomDocument $xmlDoc + * @param array $channelData + */ + protected function addChannelData($channelEl, $channelData, $jsonDoc) + { + $jsonDoc->generator = Feedster::FEED_GENERATOR_NAME; + $jsonDoc->lastBuildDate = date('D, d-M-Y'); + + foreach ($channelData as $element => $elValue) { + $channelEl->$element = $elValue; + } + } +} diff --git a/plugins/koderhut/rssfeedster/updates/version.yaml b/plugins/koderhut/rssfeedster/updates/version.yaml new file mode 100644 index 0000000..e57bf82 --- /dev/null +++ b/plugins/koderhut/rssfeedster/updates/version.yaml @@ -0,0 +1,13 @@ +1.0.1: + - First version of RssFeedster plug-in +1.0.2: + - Fix functionality bugs and plug-in description bugs +1.1.1: + - Add XML template for the RSS feed and cleanup of feed generation code +1.2.1: + - Add plug-in permissions for the configuration section +1.2.2: + - Fix typo preventing proper sorting of Posts when listing RSS feed. Thanks go to @Abhimanyu Saharan +1.2.3: + - Fix crash when installing plugin along default blog post + diff --git a/themes/demo/pages/404.htm b/themes/demo/pages/404.htm index 984fb0e..01ff17e 100644 --- a/themes/demo/pages/404.htm +++ b/themes/demo/pages/404.htm @@ -1,6 +1,7 @@ title = "Page not found (404)" url = "/404" layout = "default" +is_hidden = 0 ==
diff --git a/themes/demo/pages/error.htm b/themes/demo/pages/error.htm index 38ecc71..5643e38 100644 --- a/themes/demo/pages/error.htm +++ b/themes/demo/pages/error.htm @@ -1,10 +1,11 @@ title = "Error page (500)" url = "/error" layout = "default" +is_hidden = 0 ==

Error

-

We're sorry, but something went wrong and the page cannot be displayed.

+

We're sorry, but sometmhing went wrong and the page cannot be displayed.

\ No newline at end of file