From 1d9d4e3e3c8dd020ab4d333436264eaa3ef201cd Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Mon, 9 Jan 2023 12:59:30 +0100 Subject: Update dev dependencies (#4993) Related to https://github.com/FreshRSS/FreshRSS/pull/4991 Required a few changes in code to pass the tests --- docs/en/developers/02_Github.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs/en/developers') diff --git a/docs/en/developers/02_Github.md b/docs/en/developers/02_Github.md index 4e6a84ab3..066d6ffb0 100644 --- a/docs/en/developers/02_Github.md +++ b/docs/en/developers/02_Github.md @@ -53,7 +53,7 @@ Now you can create a PR based on your branch. ## How to write a commit message -A commit message should succintly describe the changes on the first line. For example: +A commit message should succinctly describe the changes on the first line. For example: > Fix broken icon -- cgit v1.2.3 From 05ae1b0d2684cea4eda664c5ea1a995cb9f0c4b9 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Thu, 9 Feb 2023 13:57:20 +0100 Subject: XML+XPath (#5076) * XML+XPath #fix https://github.com/FreshRSS/FreshRSS/issues/5075 Implementation allowing to take an XML document as input using an XML parser (instead of an HTML parser for HTML+XPath) * Remove noise from another PR * Better MIME for XML * And add glob *.xml for cache cleaning * Minor syntax * Add glob json for clean cache --- app/Controllers/feedController.php | 14 ++++++++++---- app/Controllers/subscriptionController.php | 2 +- app/Models/Feed.php | 29 ++++++++++++++++++++++++----- app/Services/ExportService.php | 1 + app/Services/ImportService.php | 5 ++++- app/i18n/cz/sub.php | 1 + app/i18n/de/sub.php | 1 + app/i18n/el/sub.php | 1 + app/i18n/en-us/sub.php | 1 + app/i18n/en/sub.php | 1 + app/i18n/es/sub.php | 1 + app/i18n/fr/sub.php | 1 + app/i18n/he/sub.php | 1 + app/i18n/id/sub.php | 1 + app/i18n/it/sub.php | 1 + app/i18n/ja/sub.php | 1 + app/i18n/ko/sub.php | 1 + app/i18n/nl/sub.php | 1 + app/i18n/oc/sub.php | 1 + app/i18n/pl/sub.php | 1 + app/i18n/pt-br/sub.php | 1 + app/i18n/ru/sub.php | 1 + app/i18n/sk/sub.php | 1 + app/i18n/tr/sub.php | 1 + app/i18n/zh-cn/sub.php | 1 + app/i18n/zh-tw/sub.php | 1 + app/views/helpers/export/opml.phtml | 11 +++++++++-- app/views/helpers/feed/update.phtml | 5 +++-- app/views/subscription/add.phtml | 1 + docs/en/developers/OPML.md | 4 +++- lib/lib_rss.php | 14 ++++++++++++-- p/scripts/feed.js | 11 +++++++++-- 32 files changed, 98 insertions(+), 20 deletions(-) (limited to 'docs/en/developers') diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index 2bef85f0e..84f38fe5e 100644 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -81,6 +81,7 @@ class FreshRSS_feed_Controller extends FreshRSS_ActionController { $feed->load(true); //Throws FreshRSS_Feed_Exception, Minz_FileNotExistException break; case FreshRSS_Feed::KIND_HTML_XPATH: + case FreshRSS_Feed::KIND_XML_XPATH: $feed->_website($url); break; } @@ -201,8 +202,8 @@ class FreshRSS_feed_Controller extends FreshRSS_ActionController { $timeout = intval(Minz_Request::param('timeout', 0)); $attributes['timeout'] = $timeout > 0 ? $timeout : null; - $feed_kind = Minz_Request::param('feed_kind', FreshRSS_Feed::KIND_RSS); - if ($feed_kind == FreshRSS_Feed::KIND_HTML_XPATH) { + $feed_kind = (int)Minz_Request::param('feed_kind', FreshRSS_Feed::KIND_RSS); + if ($feed_kind === FreshRSS_Feed::KIND_HTML_XPATH || $feed_kind === FreshRSS_Feed::KIND_XML_XPATH) { $xPathSettings = []; if (Minz_Request::param('xPathFeedTitle', '') != '') $xPathSettings['feedTitle'] = Minz_Request::param('xPathFeedTitle', '', true); if (Minz_Request::param('xPathItem', '') != '') $xPathSettings['item'] = Minz_Request::param('xPathItem', '', true); @@ -385,10 +386,15 @@ class FreshRSS_feed_Controller extends FreshRSS_ActionController { if ($simplePiePush) { $simplePie = $simplePiePush; //Used by WebSub } elseif ($feed->kind() === FreshRSS_Feed::KIND_HTML_XPATH) { - $simplePie = $feed->loadHtmlXpath(false, $isNewFeed); - if ($simplePie == null) { + $simplePie = $feed->loadHtmlXpath(); + if ($simplePie === null) { throw new FreshRSS_Feed_Exception('HTML+XPath Web scraping failed for [' . $feed->url(false) . ']'); } + } elseif ($feed->kind() === FreshRSS_Feed::KIND_XML_XPATH) { + $simplePie = $feed->loadHtmlXpath(); + if ($simplePie === null) { + throw new FreshRSS_Feed_Exception('XML+XPath parsing failed for [' . $feed->url(false) . ']'); + } } else { $simplePie = $feed->load(false, $isNewFeed); } diff --git a/app/Controllers/subscriptionController.php b/app/Controllers/subscriptionController.php index b2ee046d9..f0355a82a 100644 --- a/app/Controllers/subscriptionController.php +++ b/app/Controllers/subscriptionController.php @@ -203,7 +203,7 @@ class FreshRSS_subscription_Controller extends FreshRSS_ActionController { $feed->_filtersAction('read', preg_split('/[\n\r]+/', Minz_Request::param('filteractions_read', ''))); $feed->_kind(intval(Minz_Request::param('feed_kind', FreshRSS_Feed::KIND_RSS))); - if ($feed->kind() == FreshRSS_Feed::KIND_HTML_XPATH) { + if ($feed->kind() === FreshRSS_Feed::KIND_HTML_XPATH || $feed->kind() === FreshRSS_Feed::KIND_XML_XPATH) { $xPathSettings = []; if (Minz_Request::param('xPathItem', '') != '') $xPathSettings['item'] = Minz_Request::param('xPathItem', '', true); if (Minz_Request::param('xPathItemTitle', '') != '') $xPathSettings['itemTitle'] = Minz_Request::param('xPathItemTitle', '', true); diff --git a/app/Models/Feed.php b/app/Models/Feed.php index f7ff76768..7c46199a5 100644 --- a/app/Models/Feed.php +++ b/app/Models/Feed.php @@ -17,6 +17,11 @@ class FreshRSS_Feed extends Minz_Model { * @var int */ const KIND_HTML_XPATH = 10; + /** + * Normal XML with XPath scraping + * @var int + */ + const KIND_XML_XPATH = 15; /** * Normal JSON with XPath scraping * @var int @@ -586,7 +591,7 @@ class FreshRSS_Feed extends Minz_Model { /** * @return SimplePie|null */ - public function loadHtmlXpath(bool $loadDetails = false, bool $noCache = false) { + public function loadHtmlXpath() { if ($this->url == '') { return null; } @@ -614,8 +619,9 @@ class FreshRSS_Feed extends Minz_Model { return null; } - $cachePath = FreshRSS_Feed::cacheFilename($feedSourceUrl, $this->attributes(), FreshRSS_Feed::KIND_HTML_XPATH); - $html = httpGet($feedSourceUrl, $cachePath, 'html', $this->attributes()); + $cachePath = FreshRSS_Feed::cacheFilename($feedSourceUrl, $this->attributes(), $this->kind()); + $html = httpGet($feedSourceUrl, $cachePath, + $this->kind() === FreshRSS_Feed::KIND_XML_XPATH ? 'xml' : 'html', $this->attributes()); if (strlen($html) <= 0) { return null; } @@ -630,7 +636,18 @@ class FreshRSS_Feed extends Minz_Model { $doc = new DOMDocument(); $doc->recover = true; $doc->strictErrorChecking = false; - $doc->loadHTML($html, LIBXML_NONET | LIBXML_NOERROR | LIBXML_NOWARNING); + + switch ($this->kind()) { + case FreshRSS_Feed::KIND_HTML_XPATH: + $doc->loadHTML($html, LIBXML_NONET | LIBXML_NOERROR | LIBXML_NOWARNING); + break; + case FreshRSS_Feed::KIND_XML_XPATH: + $doc->loadXML($html, LIBXML_NONET | LIBXML_NOERROR | LIBXML_NOWARNING); + break; + default: + return null; + } + $xpath = new DOMXPath($doc); $view->rss_title = $xPathFeedTitle == '' ? $this->name() : htmlspecialchars(@$xpath->evaluate('normalize-space(' . $xPathFeedTitle . ')'), ENT_COMPAT, 'UTF-8'); @@ -776,8 +793,10 @@ class FreshRSS_Feed extends Minz_Model { public static function cacheFilename(string $url, array $attributes, int $kind = FreshRSS_Feed::KIND_RSS): string { $simplePie = customSimplePie($attributes); $filename = $simplePie->get_cache_filename($url); - if ($kind == FreshRSS_Feed::KIND_HTML_XPATH) { + if ($kind === FreshRSS_Feed::KIND_HTML_XPATH) { return CACHE_PATH . '/' . $filename . '.html'; + } elseif ($kind === FreshRSS_Feed::KIND_XML_XPATH) { + return CACHE_PATH . '/' . $filename . '.xml'; } else { return CACHE_PATH . '/' . $filename . '.spc'; } diff --git a/app/Services/ExportService.php b/app/Services/ExportService.php index 2f35666a8..6b0a3f178 100644 --- a/app/Services/ExportService.php +++ b/app/Services/ExportService.php @@ -21,6 +21,7 @@ class FreshRSS_Export_Service { const FRSS_NAMESPACE = 'https://freshrss.org/opml'; const TYPE_HTML_XPATH = 'HTML+XPath'; + const TYPE_XML_XPATH = 'XML+XPath'; const TYPE_RSS_ATOM = 'rss'; /** diff --git a/app/Services/ImportService.php b/app/Services/ImportService.php index 68aa6f741..55aa28679 100644 --- a/app/Services/ImportService.php +++ b/app/Services/ImportService.php @@ -160,10 +160,13 @@ class FreshRSS_Import_Service { $feed->_website($website); $feed->_description($description); - switch ($feed_elt['type'] ?? '') { + switch (strtolower($feed_elt['type'] ?? '')) { case strtolower(FreshRSS_Export_Service::TYPE_HTML_XPATH): $feed->_kind(FreshRSS_Feed::KIND_HTML_XPATH); break; + case strtolower(FreshRSS_Export_Service::TYPE_XML_XPATH): + $feed->_kind(FreshRSS_Feed::KIND_XML_XPATH); + break; case strtolower(FreshRSS_Export_Service::TYPE_RSS_ATOM): default: $feed->_kind(FreshRSS_Feed::KIND_RSS); diff --git a/app/i18n/cz/sub.php b/app/i18n/cz/sub.php index a11a9359d..3d08c315b 100644 --- a/app/i18n/cz/sub.php +++ b/app/i18n/cz/sub.php @@ -122,6 +122,7 @@ return array( 'xpath' => 'XPath pro:', ), 'rss' => 'RSS / Atom (výchozí)', + 'xml_xpath' => 'XML + XPath', // TODO ), 'maintenance' => array( 'clear_cache' => 'Vymazat mezipaměť', diff --git a/app/i18n/de/sub.php b/app/i18n/de/sub.php index 580f7d348..b265c1b98 100644 --- a/app/i18n/de/sub.php +++ b/app/i18n/de/sub.php @@ -122,6 +122,7 @@ return array( 'xpath' => 'XPath für:', ), 'rss' => 'RSS / Atom (Standard)', + 'xml_xpath' => 'XML + XPath', // TODO ), 'maintenance' => array( 'clear_cache' => 'Zwischenspeicher leeren', diff --git a/app/i18n/el/sub.php b/app/i18n/el/sub.php index 424fafc7b..aae9ae412 100644 --- a/app/i18n/el/sub.php +++ b/app/i18n/el/sub.php @@ -122,6 +122,7 @@ return array( 'xpath' => 'XPath for:', // TODO ), 'rss' => 'RSS / Atom (default)', // TODO + 'xml_xpath' => 'XML + XPath', // TODO ), 'maintenance' => array( 'clear_cache' => 'Clear cache', // TODO diff --git a/app/i18n/en-us/sub.php b/app/i18n/en-us/sub.php index a6b311084..92d75b81e 100644 --- a/app/i18n/en-us/sub.php +++ b/app/i18n/en-us/sub.php @@ -122,6 +122,7 @@ return array( 'xpath' => 'XPath for:', // IGNORE ), 'rss' => 'RSS / Atom (default)', // IGNORE + 'xml_xpath' => 'XML + XPath', // IGNORE ), 'maintenance' => array( 'clear_cache' => 'Clear cache', // IGNORE diff --git a/app/i18n/en/sub.php b/app/i18n/en/sub.php index c7e100c25..04caaff05 100644 --- a/app/i18n/en/sub.php +++ b/app/i18n/en/sub.php @@ -122,6 +122,7 @@ return array( 'xpath' => 'XPath for:', ), 'rss' => 'RSS / Atom (default)', + 'xml_xpath' => 'XML + XPath', // TODO ), 'maintenance' => array( 'clear_cache' => 'Clear cache', diff --git a/app/i18n/es/sub.php b/app/i18n/es/sub.php index 52d681067..4fd2fa393 100644 --- a/app/i18n/es/sub.php +++ b/app/i18n/es/sub.php @@ -122,6 +122,7 @@ return array( 'xpath' => 'XPath para:', ), 'rss' => 'RSS / Atom (por defecto)', + 'xml_xpath' => 'XML + XPath', // TODO ), 'maintenance' => array( 'clear_cache' => 'Borrar caché', diff --git a/app/i18n/fr/sub.php b/app/i18n/fr/sub.php index f9df0dbcc..be6dc094d 100644 --- a/app/i18n/fr/sub.php +++ b/app/i18n/fr/sub.php @@ -122,6 +122,7 @@ return array( 'xpath' => 'XPath pour :', ), 'rss' => 'RSS / Atom (par défaut)', + 'xml_xpath' => 'XML + XPath', // IGNORE ), 'maintenance' => array( 'clear_cache' => 'Vider le cache', diff --git a/app/i18n/he/sub.php b/app/i18n/he/sub.php index 25552ffa1..bae5f5177 100644 --- a/app/i18n/he/sub.php +++ b/app/i18n/he/sub.php @@ -122,6 +122,7 @@ return array( 'xpath' => 'XPath for:', // TODO ), 'rss' => 'RSS / Atom (default)', // TODO + 'xml_xpath' => 'XML + XPath', // TODO ), 'maintenance' => array( 'clear_cache' => 'Clear cache', // TODO diff --git a/app/i18n/id/sub.php b/app/i18n/id/sub.php index 7fdf5c024..3f9a4916a 100644 --- a/app/i18n/id/sub.php +++ b/app/i18n/id/sub.php @@ -122,6 +122,7 @@ return array( 'xpath' => 'XPath for:', // TODO ), 'rss' => 'RSS / Atom (default)', // TODO + 'xml_xpath' => 'XML + XPath', // TODO ), 'maintenance' => array( 'clear_cache' => 'Clear cache', // TODO diff --git a/app/i18n/it/sub.php b/app/i18n/it/sub.php index 8614caca7..7ab83cf07 100644 --- a/app/i18n/it/sub.php +++ b/app/i18n/it/sub.php @@ -122,6 +122,7 @@ return array( 'xpath' => 'XPath per:', ), 'rss' => 'RSS / Atom (predefinito)', + 'xml_xpath' => 'XML + XPath', // TODO ), 'maintenance' => array( 'clear_cache' => 'Svuota cache', diff --git a/app/i18n/ja/sub.php b/app/i18n/ja/sub.php index 80548c025..2425b21f3 100644 --- a/app/i18n/ja/sub.php +++ b/app/i18n/ja/sub.php @@ -122,6 +122,7 @@ return array( 'xpath' => 'XPathは:', ), 'rss' => 'RSS / Atom (標準)', + 'xml_xpath' => 'XML + XPath', // TODO ), 'maintenance' => array( 'clear_cache' => 'キャッシュのクリア', diff --git a/app/i18n/ko/sub.php b/app/i18n/ko/sub.php index e0ef5990b..f376247d5 100644 --- a/app/i18n/ko/sub.php +++ b/app/i18n/ko/sub.php @@ -122,6 +122,7 @@ return array( 'xpath' => '다음의 XPath:', ), 'rss' => 'RSS / Atom (기본값)', + 'xml_xpath' => 'XML + XPath', // TODO ), 'maintenance' => array( 'clear_cache' => '캐쉬 지우기', diff --git a/app/i18n/nl/sub.php b/app/i18n/nl/sub.php index 0fa767171..631da9477 100644 --- a/app/i18n/nl/sub.php +++ b/app/i18n/nl/sub.php @@ -122,6 +122,7 @@ return array( 'xpath' => 'XPath voor:', ), 'rss' => 'RSS / Atom (standaard)', + 'xml_xpath' => 'XML + XPath', // TODO ), 'maintenance' => array( 'clear_cache' => 'Cache leegmaken', diff --git a/app/i18n/oc/sub.php b/app/i18n/oc/sub.php index 92a73057c..008b4964d 100644 --- a/app/i18n/oc/sub.php +++ b/app/i18n/oc/sub.php @@ -122,6 +122,7 @@ return array( 'xpath' => 'XPath per :', ), 'rss' => 'RSS / Atom (defaut)', + 'xml_xpath' => 'XML + XPath', // TODO ), 'maintenance' => array( 'clear_cache' => 'Escafar lo cache', diff --git a/app/i18n/pl/sub.php b/app/i18n/pl/sub.php index b6121fcb7..565401982 100644 --- a/app/i18n/pl/sub.php +++ b/app/i18n/pl/sub.php @@ -122,6 +122,7 @@ return array( 'xpath' => 'XPath dla:', ), 'rss' => 'RSS / Atom (domyślne)', + 'xml_xpath' => 'XML + XPath', // TODO ), 'maintenance' => array( 'clear_cache' => 'Wyczyść pamięć podręczną', diff --git a/app/i18n/pt-br/sub.php b/app/i18n/pt-br/sub.php index c9755755e..4cdee8681 100644 --- a/app/i18n/pt-br/sub.php +++ b/app/i18n/pt-br/sub.php @@ -122,6 +122,7 @@ return array( 'xpath' => 'XPath para:', ), 'rss' => 'RSS / Atom (padrão)', + 'xml_xpath' => 'XML + XPath', // TODO ), 'maintenance' => array( 'clear_cache' => 'Limpar o cache', diff --git a/app/i18n/ru/sub.php b/app/i18n/ru/sub.php index 5704b53b1..d13c4c4f0 100644 --- a/app/i18n/ru/sub.php +++ b/app/i18n/ru/sub.php @@ -122,6 +122,7 @@ return array( 'xpath' => 'XPath для:', ), 'rss' => 'RSS / Atom (по умолчанию)', + 'xml_xpath' => 'XML + XPath', // TODO ), 'maintenance' => array( 'clear_cache' => 'Очистить кэш', diff --git a/app/i18n/sk/sub.php b/app/i18n/sk/sub.php index f583f6ca0..3c980d202 100644 --- a/app/i18n/sk/sub.php +++ b/app/i18n/sk/sub.php @@ -122,6 +122,7 @@ return array( 'xpath' => 'XPath pre:', ), 'rss' => 'RSS / Atom (prednastavené)', + 'xml_xpath' => 'XML + XPath', // TODO ), 'maintenance' => array( 'clear_cache' => 'Vymazať vyrovnáciu pamäť', diff --git a/app/i18n/tr/sub.php b/app/i18n/tr/sub.php index 056c059ac..3e03f667c 100644 --- a/app/i18n/tr/sub.php +++ b/app/i18n/tr/sub.php @@ -122,6 +122,7 @@ return array( 'xpath' => 'XPath:', ), 'rss' => 'RSS / Atom (varsayılan)', + 'xml_xpath' => 'XML + XPath', // TODO ), 'maintenance' => array( 'clear_cache' => 'Önbelleği temizle', diff --git a/app/i18n/zh-cn/sub.php b/app/i18n/zh-cn/sub.php index 2f9d17ace..5e6e570a9 100644 --- a/app/i18n/zh-cn/sub.php +++ b/app/i18n/zh-cn/sub.php @@ -122,6 +122,7 @@ return array( 'xpath' => 'XPath 定位:', ), 'rss' => 'RSS / Atom (默认)', + 'xml_xpath' => 'XML + XPath', // TODO ), 'maintenance' => array( 'clear_cache' => '清理缓存', diff --git a/app/i18n/zh-tw/sub.php b/app/i18n/zh-tw/sub.php index dddcb2661..8a255645d 100644 --- a/app/i18n/zh-tw/sub.php +++ b/app/i18n/zh-tw/sub.php @@ -122,6 +122,7 @@ return array( 'xpath' => 'XPath 定位:', ), 'rss' => 'RSS / Atom (默認)', + 'xml_xpath' => 'XML + XPath', // TODO ), 'maintenance' => array( 'clear_cache' => '清理暫存', diff --git a/app/views/helpers/export/opml.phtml b/app/views/helpers/export/opml.phtml index eb6f7523b..64c83c960 100644 --- a/app/views/helpers/export/opml.phtml +++ b/app/views/helpers/export/opml.phtml @@ -18,8 +18,15 @@ function feedsToOutlines($feeds, $excludeMutedFeeds = false): array { 'description' => htmlspecialchars_decode($feed->description(), ENT_QUOTES), ]; - if ($feed->kind() === FreshRSS_Feed::KIND_HTML_XPATH) { - $outline['type'] = FreshRSS_Export_Service::TYPE_HTML_XPATH; + if ($feed->kind() === FreshRSS_Feed::KIND_HTML_XPATH || $feed->kind() === FreshRSS_Feed::KIND_XML_XPATH) { + switch ($feed->kind()) { + case FreshRSS_Feed::KIND_HTML_XPATH: + $outline['type'] = FreshRSS_Export_Service::TYPE_HTML_XPATH; + break; + case FreshRSS_Feed::KIND_XML_XPATH: + $outline['type'] = FreshRSS_Export_Service::TYPE_XML_XPATH; + break; + } /** @var array */ $xPathSettings = $feed->attributes('xpath'); $outline['frss:xPathItem'] = $xPathSettings['item'] ?? null; diff --git a/app/views/helpers/feed/update.phtml b/app/views/helpers/feed/update.phtml index 5b958451d..0cd2ec0c3 100644 --- a/app/views/helpers/feed/update.phtml +++ b/app/views/helpers/feed/update.phtml @@ -391,8 +391,9 @@
diff --git a/app/views/subscription/add.phtml b/app/views/subscription/add.phtml index 7fa59e751..4e9da877f 100644 --- a/app/views/subscription/add.phtml +++ b/app/views/subscription/add.phtml @@ -70,6 +70,7 @@ diff --git a/docs/en/developers/OPML.md b/docs/en/developers/OPML.md index 2190a1de3..f65fd2faa 100644 --- a/docs/en/developers/OPML.md +++ b/docs/en/developers/OPML.md @@ -17,12 +17,14 @@ FreshRSS uses the XML namespace to export/import ext The list of the custom FreshRSS attributes can be seen in [the source code](https://github.com/FreshRSS/FreshRSS/blob/edge/app/views/helpers/export/opml.phtml), and here is an overview: -### HTML+XPath +### HTML+XPath or XML+XPath * ` ℹ️ [XPath 1.0](https://en.wikipedia.org/wiki/XPath) is a standard query language, which FreshRSS supports to enable [Web scraping](https://en.wikipedia.org/wiki/Web_scraping). +* ` $attributes */ function httpGet(string $url, string $cachePath, string $type = 'html', array $attributes = []): string { @@ -439,9 +443,15 @@ function httpGet(string $url, string $cachePath, string $type = 'html', array $a $accept = '*/*;q=0.8'; switch ($type) { + case 'json': + $accept = 'application/json,application/javascript;q=0.9,text/javascript;q=0.8,*/*;q=0.7'; + break; case 'opml': $accept = 'text/x-opml,text/xml;q=0.9,application/xml;q=0.9,*/*;q=0.8'; break; + case 'xml': + $accept = 'application/xml,application/xhtml+xml,text/xml;q=0.9,*/*;q=0.8'; + break; case 'html': default: $accept = 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'; diff --git a/p/scripts/feed.js b/p/scripts/feed.js index 1a6833db6..29af2a3ea 100644 --- a/p/scripts/feed.js +++ b/p/scripts/feed.js @@ -88,10 +88,17 @@ function init_disable_elements_on_update(parent) { function init_select_show(parent) { const listener = (select) => { const options = select.querySelectorAll('option[data-show]'); + const shows = {}; // To allow multiple options to show the same element for (const option of options) { - const elem = document.getElementById(option.dataset.show); + if (!shows[option.dataset.show]) { + shows[option.dataset.show] = option.selected; + } + } + + for (const show in shows) { + const elem = document.getElementById(show); if (elem) { - elem.style.display = option.selected ? 'block' : 'none'; + elem.style.display = shows[show] ? 'block' : 'none'; } } }; -- cgit v1.2.3 From 60edc285281459a74f731836dffd20a693a80a21 Mon Sep 17 00:00:00 2001 From: mincerafter42 Date: Thu, 23 Feb 2023 11:14:55 -0800 Subject: Corrected `frss:xPathItemTitle` definition (#5140) --- docs/en/developers/OPML.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs/en/developers') diff --git a/docs/en/developers/OPML.md b/docs/en/developers/OPML.md index f65fd2faa..5191592a8 100644 --- a/docs/en/developers/OPML.md +++ b/docs/en/developers/OPML.md @@ -29,7 +29,7 @@ The following attributes are using similar naming conventions than [RSS-Bridge]( * `frss:xPathItem`: XPath expression for extracting the feed items from the source page. * Example: `//div[@class="news-item"]` -* `frss:xPathItemTitle`: XPath expression for extracting the feed title from the source page. +* `frss:xPathItemTitle`: XPath expression for extracting the item’s title from the item context. * Example: `descendant::h2` * `frss:xPathItemContent`: XPath expression for extracting an item’s content from the item context. * Example: `.` -- cgit v1.2.3 From 859c48383a229db43cf50ca64b09149bab0e3da4 Mon Sep 17 00:00:00 2001 From: maTh Date: Thu, 23 Feb 2023 22:20:36 +0100 Subject: docs: Minz Framwork (#5102) * done * Update docs/fr/developers/Minz/index.md Co-authored-by: Alexandre Alapetite --- docs/en/developers/03_Backend/05_Extensions.md | 190 +-------------- docs/en/developers/Minz/index.md | 198 ++++++++++++++- docs/fr/developers/03_Backend/02_Minz.md | 29 --- docs/fr/developers/03_Backend/05_Extensions.md | 324 +------------------------ docs/fr/developers/Minz/index.md | 249 +++++++++++++++++++ docs/fr/developers/Minz/migration.md | 3 + docs/fr/internationalization.md | 79 ++++++ 7 files changed, 521 insertions(+), 551 deletions(-) delete mode 100644 docs/fr/developers/03_Backend/02_Minz.md create mode 100644 docs/fr/developers/Minz/index.md create mode 100644 docs/fr/developers/Minz/migration.md create mode 100644 docs/fr/internationalization.md (limited to 'docs/en/developers') diff --git a/docs/en/developers/03_Backend/05_Extensions.md b/docs/en/developers/03_Backend/05_Extensions.md index aa707d2e4..e86d73bfa 100644 --- a/docs/en/developers/03_Backend/05_Extensions.md +++ b/docs/en/developers/03_Backend/05_Extensions.md @@ -22,195 +22,9 @@ Another solution consists of an extension system. By allowing users to write the Note: it is quite conceivable that the functionalities of an extension can later be officially integrated into the FreshRSS code. Extensions make it easy to propose a proof of concept. -## Understanding basic mechanics (Minz and MVC) +## Minz Framework -**TODO** : move to 02_Minz.md - -This data sheet should refer to the official FreshRSS and Minz documentation (the PHP framework on which FreshRSS is based). Unfortunately, this documentation does not yet exist. In a few words, here are the main things you should know. It is not necessary to read all the chapters in this section if you don’t need to use a feature in your extension (if you don’t need to translate your extension, no need to know more about the `Minz_Translate` module for example). - -### MVC Architecture - -Minz relies on and imposes an MVC architecture on projects using it. This architecture consists of three main components: - -* The model: this is the base object that we will manipulate. In FreshRSS, categories, flows and articles are templates. The part of the code that makes it possible to manipulate them in a database is also part of the model but is separated from the base model: we speak of DAO (for "Data Access Object"). The templates are stored in a `Models` folder. -* The view: this is what the user sees. The view is therefore simply HTML code mixed with PHP to display dynamic information. The views are stored in a `views` folder. -* The controller: this is what makes it possible to link models and views. Typically, a controller will load templates from the database (like a list of items) to "pass" them to a view for display. Controllers are stored in a `Controllers` directory. - -### Routing - -In order to link a URL to a controller, first you have to go through a "routing" phase. In FreshRSS, this is particularly simple because it suffices to specify the name of the controller to load into the URL using a `c` parameter. -For example, the address will execute the code contained in the `hello` controller. - -One concept that has not yet been discussed is the "actions" system. An action is executed *on* a controller. Concretely, a controller is represented by a class and its actions by methods. To execute an action, it is necessary to specify an `a` parameter in the URL. - -Code example: - -```php -view->a_variable = 'FooBar'; - } - - public function worldAction() { - $this->view->a_variable = 'Hello World!'; - } -} - -?> -``` - -When loading the address , the `world` action is executed on the `hello` controller. - -Note: if `c` or `a` is not specified, the default value for each of these variables is `index`. -So the address will execute the `index` action of the `hello` controller. - -From now on, the `hello/world` naming convention will be used to refer to a controller/action pair. - -### Views - -Each view is associated with a controller and an action. The view associated with `hello/world` will be stored in a very specific file: `views/hello/world. phtml`. This convention is imposed by Minz. - -As explained above, the views consist of HTML mixed with PHP. Code example: - -```html -

- This is a parameter passed from the controller: a_variable ?> -

-``` - -The variable `$this->a_variable` is passed by the controller (see previous example). The difference is that in the controller it is necessary to pass `$this->view`, while in the view `$this` suffices. - -### Working with GET / POST - -It is often necessary to take advantage of parameters passed by GET or POST. In Minz, these parameters are accessible using the `Minz_Request` class. -Code example: - -```php - -``` - -The `Minz_Request::isPost()` method can be used to execute a piece of code only if it is a POST request. - -Note: it is preferable to use `Minz_Request` only in controllers. It is likely that you will encounter this method in FreshRSS views, or even in templates, but be aware that this is **not** good practice. - -### Access session settings - -The access to session parameters is strangely similar to the GET / POST parameters but passes through the `Minz_Session` class this time! There is no example here because you can repeat the previous example by changing all `Minz_Request` to `Minz_Session`. - -### Working with URLs - -To take full advantage of the Minz routing system, it is strongly discouraged to write hard URLs in your code. For example, the following view should be avoided: - -```html -

- Go to page Hello world! -

-``` - -If one day it was decided to use a "url rewriting" system to have addresses in a format, all previous addresses would become ineffective! - -So use the `Minz_Url` class and its `display()` method instead. `Minz_Url::display()` takes an array of the following form as its argument: - -```php - 'hello', - 'a' => 'world', - 'params' => [ - 'foo' => 'bar', - ], -]; - -// Show something like .?c=hello&a=world&foo=bar -echo Minz_Url::display($url_array); - -?> -``` - -Since this can become a bit tedious to use in the long run, especially in views, it is preferable to use the `_url()` shortcut: - -```php - -``` - -Note: as a general rule, the shortened form (`_url()`) should be used in views, while the long form (`Minz_Url::display()`) should be used in controllers. - -### Redirections - -It is often necessary to redirect a user to another page. To do so, the `Minz_Request` class offers another useful method: `forward()`. This method takes the same URL format as the one seen just before as its argument. - -Code example: - -```php - 'hello', - 'a' => 'world', -]; - -// Tells Minz to redirect the user to the hello / world page. -// Note that this is a redirection in the Minz sense of the term, not a redirection that the browser will have to manage (HTTP code 301 or 302) -// The code that follows forward() will thus be executed! -Minz_Request::forward($url_array); - -// To perform a type 302 redirect, add "true". -// The code that follows will never be executed. -Minz_Request::forward($url_array, true); - -?> -``` - -It is very common to want display a message to the user while performing a redirect, to tell the user how the action was carried out (validation of a form for example). Such a message is passed through a `notification` session variable (note: we will talk about feedback from now on to avoid confusion with a notification that can occur at any time). To facilitate this kind of very frequent action, there are two shortcuts that both perform a 302 redirect by assigning a feedback message: - -```php - 'hello', - 'a' => 'world', -]; -$feedback_good = 'All went well!'; -$feedback_bad = 'Oops, something went wrong.'; - -Minz_Request::good($feedback_good, $url_array); - -// or - -Minz_Request::bad($feedback_bad, $url_array); - -?> -``` - -### Translation Management - -This part [is explained here](/docs/en/internationalization.md). - -### Configuration management +see [Minz documentation](/docs/en/developers/Minz/index.md) ## Write an extension for FreshRSS diff --git a/docs/en/developers/Minz/index.md b/docs/en/developers/Minz/index.md index 9b6d46f17..ed5bc0482 100644 --- a/docs/en/developers/Minz/index.md +++ b/docs/en/developers/Minz/index.md @@ -1,19 +1,193 @@ -# Minz +# Minz Framework Minz is the homemade PHP framework used by FreshRSS. -The documentation is still incomplete and it would be great to explain: +This data sheet should refer to the official FreshRSS and Minz documentation (the PHP framework on which FreshRSS is based). Unfortunately, this documentation does not yet exist. In a few words, here are the main things you should know. It is not necessary to read all the chapters in this section if you don’t need to use a feature in your extension (if you don’t need to translate your extension, no need to know more about the `Minz_Translate` module for example). -- routing, controllers and actions -- configuration -- models and database -- views -- URLs management -- sessions -- internationalisation -- extensions -- mailer +## MVC Architecture + +Minz relies on and imposes an MVC architecture on projects using it. This architecture consists of three main components: + +* The model: this is the base object that we will manipulate. In FreshRSS, categories, flows and articles are templates. The part of the code that makes it possible to manipulate them in a database is also part of the model but is separated from the base model: we speak of DAO (for "Data Access Object"). The templates are stored in a `Models` folder. +* The view: this is what the user sees. The view is therefore simply HTML code mixed with PHP to display dynamic information. The views are stored in a `views` folder. +* The controller: this is what makes it possible to link models and views. Typically, a controller will load templates from the database (like a list of items) to "pass" them to a view for display. Controllers are stored in a `Controllers` directory. + +## Routing + +In order to link a URL to a controller, first you have to go through a "routing" phase. In FreshRSS, this is particularly simple because it suffices to specify the name of the controller to load into the URL using a `c` parameter. +For example, the address will execute the code contained in the `hello` controller. + +One concept that has not yet been discussed is the "actions" system. An action is executed *on* a controller. Concretely, a controller is represented by a class and its actions by methods. To execute an action, it is necessary to specify an `a` parameter in the URL. + +Code example: + +```php +view->a_variable = 'FooBar'; + } + + public function worldAction() { + $this->view->a_variable = 'Hello World!'; + } +} + +?> +``` + +When loading the address , the `world` action is executed on the `hello` controller. + +Note: if `c` or `a` is not specified, the default value for each of these variables is `index`. +So the address will execute the `index` action of the `hello` controller. + +From now on, the `hello/world` naming convention will be used to refer to a controller/action pair. + +## Views + +Each view is associated with a controller and an action. The view associated with `hello/world` will be stored in a very specific file: `views/hello/world. phtml`. This convention is imposed by Minz. + +As explained above, the views consist of HTML mixed with PHP. Code example: + +```html +

+ This is a parameter passed from the controller: a_variable ?> +

+``` + +The variable `$this->a_variable` is passed by the controller (see previous example). The difference is that in the controller it is necessary to pass `$this->view`, while in the view `$this` suffices. + +## Working with GET / POST + +It is often necessary to take advantage of parameters passed by GET or POST. In Minz, these parameters are accessible using the `Minz_Request` class. +Code example: + +```php + +``` + +The `Minz_Request::isPost()` method can be used to execute a piece of code only if it is a POST request. + +Note: it is preferable to use `Minz_Request` only in controllers. It is likely that you will encounter this method in FreshRSS views, or even in templates, but be aware that this is **not** good practice. + +## Access session settings + +The access to session parameters is strangely similar to the GET / POST parameters but passes through the `Minz_Session` class this time! There is no example here because you can repeat the previous example by changing all `Minz_Request` to `Minz_Session`. + +## Working with URLs + +To take full advantage of the Minz routing system, it is strongly discouraged to write hard URLs in your code. For example, the following view should be avoided: + +```html +

+ Go to page Hello world! +

+``` + +If one day it was decided to use a "url rewriting" system to have addresses in a format, all previous addresses would become ineffective! + +So use the `Minz_Url` class and its `display()` method instead. `Minz_Url::display()` takes an array of the following form as its argument: + +```php + 'hello', + 'a' => 'world', + 'params' => [ + 'foo' => 'bar', + ], +]; + +// Show something like .?c=hello&a=world&foo=bar +echo Minz_Url::display($url_array); + +?> +``` + +Since this can become a bit tedious to use in the long run, especially in views, it is preferable to use the `_url()` shortcut: + +```php + +``` + +Note: as a general rule, the shortened form (`_url()`) should be used in views, while the long form (`Minz_Url::display()`) should be used in controllers. + +## Redirections + +It is often necessary to redirect a user to another page. To do so, the `Minz_Request` class offers another useful method: `forward()`. This method takes the same URL format as the one seen just before as its argument. + +Code example: + +```php + 'hello', + 'a' => 'world', +]; + +// Tells Minz to redirect the user to the hello / world page. +// Note that this is a redirection in the Minz sense of the term, not a redirection that the browser will have to manage (HTTP code 301 or 302) +// The code that follows forward() will thus be executed! +Minz_Request::forward($url_array); + +// To perform a type 302 redirect, add "true". +// The code that follows will never be executed. +Minz_Request::forward($url_array, true); + +?> +``` + +It is very common to want display a message to the user while performing a redirect, to tell the user how the action was carried out (validation of a form for example). Such a message is passed through a `notification` session variable (note: we will talk about feedback from now on to avoid confusion with a notification that can occur at any time). To facilitate this kind of very frequent action, there are two shortcuts that both perform a 302 redirect by assigning a feedback message: + +```php + 'hello', + 'a' => 'world', +]; +$feedback_good = 'All went well!'; +$feedback_bad = 'Oops, something went wrong.'; + +Minz_Request::good($feedback_good, $url_array); + +// or + +Minz_Request::bad($feedback_bad, $url_array); + +?> +``` + +## Translation Management + +This part [is explained here](/docs/en/internationalization.md). + +## Migration Existing documentation includes: -- [How to manage migrations](migrations.md) +* [How to manage migrations](migrations.md) diff --git a/docs/fr/developers/03_Backend/02_Minz.md b/docs/fr/developers/03_Backend/02_Minz.md deleted file mode 100644 index 5daf684f0..000000000 --- a/docs/fr/developers/03_Backend/02_Minz.md +++ /dev/null @@ -1,29 +0,0 @@ -# Minz - -## Modèles - -> **À FAIRE** - -## Contrôleurs et actions - -> **À FAIRE** - -## Vues - -> **À FAIRE** - -## Routage - -> **À FAIRE** - -## Écriture des URL - -> **À FAIRE** - -## Internationalisation - -> **À FAIRE** - -## Comprendres les mécanismes internes - -> **À FAIRE** diff --git a/docs/fr/developers/03_Backend/05_Extensions.md b/docs/fr/developers/03_Backend/05_Extensions.md index a715c40b3..548aebf4f 100644 --- a/docs/fr/developers/03_Backend/05_Extensions.md +++ b/docs/fr/developers/03_Backend/05_Extensions.md @@ -36,329 +36,9 @@ puissent par la suite être intégrées dans le code initial de FreshRSS de façon officielle. Cela permet de proposer un « proof of concept » assez facilement. -## Comprendre les mécaniques de base (Minz et MVC) - -**TODO** : bouger dans 02_Minz.md - -Cette fiche technique devrait renvoyer vers la documentation officielle de -FreshRSS et de Minz (le framework PHP sur lequel repose -FreshRSS). Malheureusement cette documentation n’existe pas encore. Voici -donc en quelques mots les principaux éléments à connaître. Il n’est pas -nécessaire de lire l’ensemble des chapitres de cette section si vous n’avez -pas à utiliser une fonctionnalité dans votre extension (si vous n’avez pas -besoin de traduire votre extension, pas besoin d’en savoir plus sur le -module `Minz_Translate` par exemple). - -### Architecture MVC - -Minz repose et impose une architecture MVC pour les projets l’utilisant. On -distingue dans cette architecture trois composants principaux : - -* Le Modèle : c’est l’objet de base que l’on va manipuler. Dans FreshRSS, - les catégories, les flux et les articles sont des modèles. La partie du - code qui permet de les manipuler en base de données fait aussi partie du - modèle mais est séparée du modèle de base : on parle de DAO (pour « Data - Access Object »). Les modèles sont stockés dans un répertoire `Models`. -* La Vue : c’est ce qui représente ce que verra l’utilisateur. La vue est - donc simplement du code HTML que l’on mixe avec du PHP pour afficher les - informations dynamiques. Les vues sont stockées dans un répertoire - `views`. -* Le Contrôleur : c’est ce qui permet de lier modèles et vues entre - eux. Typiquement, un contrôleur va charger des modèles à partir de la base - de données (une liste d’articles par exemple) pour les « passer » à une - vue afin qu’elle les affiche. Les contrôleurs sont stockés dans un - répertoire `Controllers`. - -### Routage - -Afin de lier une URL à un contrôleur, on doit passer par une phase dite de « -routage ». Dans FreshRSS, cela est particulièrement simple car il suffit -d’indiquer le nom du contrôleur à charger dans l’URL à l’aide d’un paramètre `c`. -Par exemple, l’adresse va exécuter le code -contenu dans le contrôleur `hello`. - -Une notion qui n’a pas encore été évoquée est le système d'« actions ». Une -action est exécutée *sur* un contrôleur. Concrètement, un contrôleur va être -représenté par une classe et ses actions par des méthodes. Pour exécuter une -action, il est nécessaire d’indiquer un paramètre `a` dans l’URL. - -Exemple de code : - -```php -view->a_variable = 'FooBar'; - } - - public function worldAction() { - $this->view->a_variable = 'Hello World!'; - } -} - -?> -``` - -Si l’on charge l’adresse , l’action -`world` va donc être exécutée sur le contrôleur `hello`. - -Note : si `c` ou `a` n’est pas précisée, la valeur par défaut de chacune de -ces variables est `index`. Ainsi l’adresse va -exécuter l’action `index` du contrôleur `hello`. - -Plus loin, sera utilisée la convention `hello/world` pour évoquer un couple -contrôleur/action. - -### Vues - -Chaque vue est associée à un contrôleur et à une action. La vue associée à -`hello/world` va être stockée dans un fichier bien spécifique : -`views/hello/world.phtml`. Cette convention est imposée par Minz. - -Comme expliqué plus haut, les vues sont du code HTML mixé à du PHP. Exemple -de code : - -```html -

- Phrase passée en paramètre : a_variable ?> -

-``` - -La variable `$this->a_variable` a été passée précédemment par le contrôleur (voir exemple précédent). La différence est que dans le contrôleur il est nécessaire de passer par `$this->view` et que dans la vue `$this` suffit. - -### Accéder aux paramètres GET / POST - -Il est souvent nécessaire de profiter des paramètres passés par GET ou par -POST. Dans Minz, ces paramètres sont accessibles de façon indistincts à -l’aide de la classe `Minz_Request`. Exemple de code : - -```php - -``` - -La méthode `Minz_Request::isPost()` peut être utile pour n’exécuter un -morceau de code que s’il s’agit d’une requête POST. - -Note : il est préférable de n’utiliser `Minz_Request` que dans les -contrôleurs. Il est probable que vous rencontriez cette méthode dans les -vues de FreshRSS, voire dans les modèles, mais sachez qu’il ne s’agit -**pas** d’une bonne pratique. - -### Accéder aux paramètres de session - -L’accès aux paramètres de session est étrangement similaire aux paramètres -GET / POST mais passe par la classe `Minz_Session` cette fois-ci ! Il n’y a -pas d’exemple ici car vous pouvez reprendre le précédent en changeant tous -les `Minz_Request` par des `Minz_Session`. - -### Gestion des URL - -Pour profiter pleinement du système de routage de Minz, il est fortement -déconseillé d’écrire les URL en dur dans votre code. Par exemple, la vue -suivante doit être évitée : - -```html -

- Accéder à la page Hello world! -

-``` - -Si un jour il est décidé d’utiliser un système d'« url rewriting » pour -avoir des adresses au format , toutes -les adresses précédentes deviendraient ineffectives ! - -Préférez donc l’utilisation de la classe `Minz_Url` et de sa méthode -`display()`. `Minz_Url::display()` prend en paramètre un tableau de la forme -suivante : - -```php - 'hello', - 'a' => 'world', - 'params' => [ - 'foo' => 'bar', - ], -]; - -// Affichera quelque chose comme .?c=hello&a=world&foo=bar -echo Minz_Url::display($url_array); - -?> -``` - -Comme cela peut devenir un peu pénible à utiliser à la longue, surtout dans -les vues, il est préférable d’utiliser le raccourci `_url()` : - -```php - -``` - -Note : en règle générale, la forme raccourcie (`_url()`) doit être utilisée -dans les vues tandis que la forme longue (`Minz_Url::display()`) doit être -utilisée dans les contrôleurs. - -### Redirections - -Il est souvent nécessaire de rediriger un utilisateur vers une autre -page. Pour cela, la classe `Minz_Request` dispose d’une autre méthode utile -: `forward()`. Cette méthode prend en argument le même format d’URL que -celui vu juste avant. - -Exemple de code : - -```php - 'hello', - 'a' => 'world', -]; - -// Indique à Minz de rediriger l’utilisateur vers la page hello/world. -// Notez qu’il s’agit d’une redirection au sens Minz du terme, pas d’une redirection que le navigateur va avoir à gérer (code HTTP 301 ou 302) -// Le code qui suit forward() va ainsi être exécuté ! -Minz_Request::forward($url_array); - -// Pour effectuer une redirection type 302, ajoutez "true". -// Le code qui suivra ne sera alors jamais exécuté. -Minz_Request::forward($url_array, true); - -?> -``` - -Il est très fréquent de vouloir effectuer une redirection tout en affichant -un message à l’utilisateur pour lui indiquer comment s’est déroulée l’action -effectuée juste avant (validation d’un formulaire par exemple). Un tel -message est passé par une variable de session `notification` (note : nous -parlerons plutôt de « feedback » désormais pour éviter la confusion avec une -notification qui peut survenir à tout moment). Pour faciliter ce genre -d’action très fréquente, il existe deux raccourcis qui effectuent tout deux -une redirection type 302 en affectant un message de feedback : - -```php - 'hello', - 'a' => 'world', -]; -$feedback_good = 'Tout s’est bien passé !'; -$feedback_bad = 'Oups, quelque chose n’a pas marché.'; - -Minz_Request::good($feedback_good, $url_array); - -// ou - -Minz_Request::bad($feedback_bad, $url_array); - -?> -``` - -### Gestion de la traduction - -Il est fréquent (et c’est un euphémisme) de vouloir afficher des phrases à -l’utilisateur. Dans l’exemple précédent par exemple, nous affichions un -feedback à l’utilisateur en fonction du résultat d’une validation de -formulaire. Le problème est que FreshRSS possède des utilisateurs de -différentes nationalités. Il est donc nécessaire de pouvoir gérer -différentes langues pour ne pas rester cantonné à l’Anglais ou au Français. - -La solution consiste à utiliser la classe `Minz_Translate` qui permet de -traduire dynamiquement FreshRSS (ou toute application basée sur Minz). Avant -d’utiliser ce module, il est nécessaire de savoir où trouver les chaînes de -caractères à traduire. Chaque langue possède son propre sous-répertoire dans -un répertoire parent nommé `i18n`. Par exemple, les fichiers de langue en -Français sont situés dans `i18n/fr/`. Il existe sept fichiers différents : - -* `admin.php` pour tout ce qui est relatif à l’administration de FreshRSS ; -* `conf.php` pour l’aspect configuration ; -* `feedback.php` contient les traductions des messages de feedback ; -* `gen.php` stocke ce qui est global à FreshRSS (gen pour « general ») ; -* `index.php` pour la page principale qui liste les flux et la page « À propos » ; -* `install.php` contient les phrases relatives à l’installation de FreshRSS ; -* `sub.php` pour l’aspect gestion des abonnements (sub pour « subscription »). - -Cette organisation permet de ne pas avoir un unique énorme fichier de -traduction. - -Les fichiers de traduction sont assez simples : il s’agit seulement de -retourner un tableau PHP contenant les traductions. Extrait du fichier -`app/i18n/fr/gen.php` : - -```php - [ - 'actualize' => 'Actualiser', - 'back_to_rss_feeds' => '← Retour à vos flux RSS', - 'cancel' => 'Annuler', - 'create' => 'Créer', - 'disable' => 'Désactiver', - ), - 'freshrss' => array( - '_' => 'FreshRSS', - 'about' => 'À propos de FreshRSS', - ), -]; - -?> -``` - -Pour accéder à ces traductions, `Minz_Translate` va nous aider à l’aide de -sa méthode `Minz_Translate::t()`. Comme cela peut être un peu long à taper, -il a été introduit un raccourci qui **doit** être utilisé en toutes -circonstances : `_t()`. Exemple de code : - -```html -

- - - -

-``` +## Minz Framework -La chaîne à passer à la fonction `_t()` consiste en une série d’identifiants -séparés par des points. Le premier identifiant indique de quel fichier on -veut extraire la traduction (dans notre cas présent, de `gen.php`), tandis -que les suivantes indiquent des entrées de tableaux. Ainsi `action` est une -entrée du tableau principal et `back_to_rss_feeds` est une entrée du tableau -`action`. Cela permet d’organiser encore un peu plus nos fichiers de -traduction. - -Il existe un petit cas particulier qui permet parfois de se simplifier la -vie : le cas de l’identifiant `_`. Celui-ci doit nécessairement être présent -en bout de chaîne et permet de donner une valeur à l’identifiant de niveau -supérieur. C’est assez dur à expliquer mais très simple à comprendre. Dans -l’exemple donné plus haut, un `_` est associé à la valeur `FreshRSS` : cela -signifie qu’il n’y a pas besoin d’écrire `_t('gen.freshrss._')` mais -`_t('gen.freshrss')` suffit. - -### Gestion de la configuration +see [Minz documentation](/docs/fr/developers/Minz/index.md) ## Écrire une extension pour FreshRSS diff --git a/docs/fr/developers/Minz/index.md b/docs/fr/developers/Minz/index.md new file mode 100644 index 000000000..0d1d2124a --- /dev/null +++ b/docs/fr/developers/Minz/index.md @@ -0,0 +1,249 @@ +# Minz + +Cette fiche technique devrait renvoyer vers la documentation officielle de +FreshRSS et de Minz (le framework PHP sur lequel repose +FreshRSS). Malheureusement cette documentation n’existe pas encore. Voici +donc en quelques mots les principaux éléments à connaître. Il n’est pas +nécessaire de lire l’ensemble des chapitres de cette section si vous n’avez +pas à utiliser une fonctionnalité dans votre extension (si vous n’avez pas +besoin de traduire votre extension, pas besoin d’en savoir plus sur le +module `Minz_Translate` par exemple). + +## Architecture MVC + +Minz repose et impose une architecture MVC pour les projets l’utilisant. On +distingue dans cette architecture trois composants principaux : + +* Le Modèle : c’est l’objet de base que l’on va manipuler. Dans FreshRSS, + les catégories, les flux et les articles sont des modèles. La partie du + code qui permet de les manipuler en base de données fait aussi partie du + modèle mais est séparée du modèle de base : on parle de DAO (pour « Data + Access Object »). Les modèles sont stockés dans un répertoire `Models`. +* La Vue : c’est ce qui représente ce que verra l’utilisateur. La vue est + donc simplement du code HTML que l’on mixe avec du PHP pour afficher les + informations dynamiques. Les vues sont stockées dans un répertoire + `views`. +* Le Contrôleur : c’est ce qui permet de lier modèles et vues entre + eux. Typiquement, un contrôleur va charger des modèles à partir de la base + de données (une liste d’articles par exemple) pour les « passer » à une + vue afin qu’elle les affiche. Les contrôleurs sont stockés dans un + répertoire `Controllers`. + +## Routage + +Afin de lier une URL à un contrôleur, on doit passer par une phase dite de « +routage ». Dans FreshRSS, cela est particulièrement simple car il suffit +d’indiquer le nom du contrôleur à charger dans l’URL à l’aide d’un paramètre `c`. +Par exemple, l’adresse va exécuter le code +contenu dans le contrôleur `hello`. + +Une notion qui n’a pas encore été évoquée est le système d'« actions ». Une +action est exécutée *sur* un contrôleur. Concrètement, un contrôleur va être +représenté par une classe et ses actions par des méthodes. Pour exécuter une +action, il est nécessaire d’indiquer un paramètre `a` dans l’URL. + +Exemple de code : + +```php +view->a_variable = 'FooBar'; + } + + public function worldAction() { + $this->view->a_variable = 'Hello World!'; + } +} + +?> +``` + +Si l’on charge l’adresse , l’action +`world` va donc être exécutée sur le contrôleur `hello`. + +Note : si `c` ou `a` n’est pas précisée, la valeur par défaut de chacune de +ces variables est `index`. Ainsi l’adresse va +exécuter l’action `index` du contrôleur `hello`. + +Plus loin, sera utilisée la convention `hello/world` pour évoquer un couple +contrôleur/action. + +## Vues + +Chaque vue est associée à un contrôleur et à une action. La vue associée à +`hello/world` va être stockée dans un fichier bien spécifique : +`views/hello/world.phtml`. Cette convention est imposée par Minz. + +Comme expliqué plus haut, les vues sont du code HTML mixé à du PHP. Exemple +de code : + +```html +

+ Phrase passée en paramètre : a_variable ?> +

+``` + +La variable `$this->a_variable` a été passée précédemment par le contrôleur (voir exemple précédent). La différence est que dans le contrôleur il est nécessaire de passer par `$this->view` et que dans la vue `$this` suffit. + +## Accéder aux paramètres GET / POST + +Il est souvent nécessaire de profiter des paramètres passés par GET ou par +POST. Dans Minz, ces paramètres sont accessibles de façon indistincts à +l’aide de la classe `Minz_Request`. Exemple de code : + +```php + +``` + +La méthode `Minz_Request::isPost()` peut être utile pour n’exécuter un +morceau de code que s’il s’agit d’une requête POST. + +Note : il est préférable de n’utiliser `Minz_Request` que dans les +contrôleurs. Il est probable que vous rencontriez cette méthode dans les +vues de FreshRSS, voire dans les modèles, mais sachez qu’il ne s’agit +**pas** d’une bonne pratique. + +## Accéder aux paramètres de session + +L’accès aux paramètres de session est étrangement similaire aux paramètres +GET / POST mais passe par la classe `Minz_Session` cette fois-ci ! Il n’y a +pas d’exemple ici car vous pouvez reprendre le précédent en changeant tous +les `Minz_Request` par des `Minz_Session`. + +## Gestion des URL + +Pour profiter pleinement du système de routage de Minz, il est fortement +déconseillé d’écrire les URL en dur dans votre code. Par exemple, la vue +suivante doit être évitée : + +```html +

+ Accéder à la page Hello world! +

+``` + +Si un jour il est décidé d’utiliser un système d'« url rewriting » pour +avoir des adresses au format , toutes +les adresses précédentes deviendraient ineffectives ! + +Préférez donc l’utilisation de la classe `Minz_Url` et de sa méthode +`display()`. `Minz_Url::display()` prend en paramètre un tableau de la forme +suivante : + +```php + 'hello', + 'a' => 'world', + 'params' => [ + 'foo' => 'bar', + ], +]; + +// Affichera quelque chose comme .?c=hello&a=world&foo=bar +echo Minz_Url::display($url_array); + +?> +``` + +Comme cela peut devenir un peu pénible à utiliser à la longue, surtout dans +les vues, il est préférable d’utiliser le raccourci `_url()` : + +```php + +``` + +Note : en règle générale, la forme raccourcie (`_url()`) doit être utilisée +dans les vues tandis que la forme longue (`Minz_Url::display()`) doit être +utilisée dans les contrôleurs. + +## Redirections + +Il est souvent nécessaire de rediriger un utilisateur vers une autre +page. Pour cela, la classe `Minz_Request` dispose d’une autre méthode utile +: `forward()`. Cette méthode prend en argument le même format d’URL que +celui vu juste avant. + +Exemple de code : + +```php + 'hello', + 'a' => 'world', +]; + +// Indique à Minz de rediriger l’utilisateur vers la page hello/world. +// Notez qu’il s’agit d’une redirection au sens Minz du terme, pas d’une redirection que le navigateur va avoir à gérer (code HTTP 301 ou 302) +// Le code qui suit forward() va ainsi être exécuté ! +Minz_Request::forward($url_array); + +// Pour effectuer une redirection type 302, ajoutez "true". +// Le code qui suivra ne sera alors jamais exécuté. +Minz_Request::forward($url_array, true); + +?> +``` + +Il est très fréquent de vouloir effectuer une redirection tout en affichant +un message à l’utilisateur pour lui indiquer comment s’est déroulée l’action +effectuée juste avant (validation d’un formulaire par exemple). Un tel +message est passé par une variable de session `notification` (note : nous +parlerons plutôt de « feedback » désormais pour éviter la confusion avec une +notification qui peut survenir à tout moment). Pour faciliter ce genre +d’action très fréquente, il existe deux raccourcis qui effectuent tout deux +une redirection type 302 en affectant un message de feedback : + +```php + 'hello', + 'a' => 'world', +]; +$feedback_good = 'Tout s’est bien passé !'; +$feedback_bad = 'Oups, quelque chose n’a pas marché.'; + +Minz_Request::good($feedback_good, $url_array); + +// ou + +Minz_Request::bad($feedback_bad, $url_array); + +?> +``` + +## Gestion de la traduction + +Cette partie est [expliquée dans la page dédiée](/docs/fr/internationalization.md). + +## Migration + +Existing documentation includes: + +* [How to manage migrations](migrations.md) diff --git a/docs/fr/developers/Minz/migration.md b/docs/fr/developers/Minz/migration.md new file mode 100644 index 000000000..ad2eef949 --- /dev/null +++ b/docs/fr/developers/Minz/migration.md @@ -0,0 +1,3 @@ +# Migration + +see [English documentation](/docs/en/developers/Minz/migrations.md) diff --git a/docs/fr/internationalization.md b/docs/fr/internationalization.md new file mode 100644 index 000000000..532ed457d --- /dev/null +++ b/docs/fr/internationalization.md @@ -0,0 +1,79 @@ +# Gestion de la traduction + +Il est fréquent (et c’est un euphémisme) de vouloir afficher des phrases à +l’utilisateur. Dans l’exemple précédent par exemple, nous affichions un +feedback à l’utilisateur en fonction du résultat d’une validation de +formulaire. Le problème est que FreshRSS possède des utilisateurs de +différentes nationalités. Il est donc nécessaire de pouvoir gérer +différentes langues pour ne pas rester cantonné à l’Anglais ou au Français. + +La solution consiste à utiliser la classe `Minz_Translate` qui permet de +traduire dynamiquement FreshRSS (ou toute application basée sur Minz). Avant +d’utiliser ce module, il est nécessaire de savoir où trouver les chaînes de +caractères à traduire. Chaque langue possède son propre sous-répertoire dans +un répertoire parent nommé `i18n`. Par exemple, les fichiers de langue en +Français sont situés dans `i18n/fr/`. Il existe sept fichiers différents : + +* `admin.php` pour tout ce qui est relatif à l’administration de FreshRSS ; +* `conf.php` pour l’aspect configuration ; +* `feedback.php` contient les traductions des messages de feedback ; +* `gen.php` stocke ce qui est global à FreshRSS (gen pour « general ») ; +* `index.php` pour la page principale qui liste les flux et la page « À propos » ; +* `install.php` contient les phrases relatives à l’installation de FreshRSS ; +* `sub.php` pour l’aspect gestion des abonnements (sub pour « subscription »). + +Cette organisation permet de ne pas avoir un unique énorme fichier de +traduction. + +Les fichiers de traduction sont assez simples : il s’agit seulement de +retourner un tableau PHP contenant les traductions. Extrait du fichier +`app/i18n/fr/gen.php` : + +```php + [ + 'actualize' => 'Actualiser', + 'back_to_rss_feeds' => '← Retour à vos flux RSS', + 'cancel' => 'Annuler', + 'create' => 'Créer', + 'disable' => 'Désactiver', + ), + 'freshrss' => array( + '_' => 'FreshRSS', + 'about' => 'À propos de FreshRSS', + ), +]; + +?> +``` + +Pour accéder à ces traductions, `Minz_Translate` va nous aider à l’aide de +sa méthode `Minz_Translate::t()`. Comme cela peut être un peu long à taper, +il a été introduit un raccourci qui **doit** être utilisé en toutes +circonstances : `_t()`. Exemple de code : + +```html +

+ + + +

+``` + +La chaîne à passer à la fonction `_t()` consiste en une série d’identifiants +séparés par des points. Le premier identifiant indique de quel fichier on +veut extraire la traduction (dans notre cas présent, de `gen.php`), tandis +que les suivantes indiquent des entrées de tableaux. Ainsi `action` est une +entrée du tableau principal et `back_to_rss_feeds` est une entrée du tableau +`action`. Cela permet d’organiser encore un peu plus nos fichiers de +traduction. + +Il existe un petit cas particulier qui permet parfois de se simplifier la +vie : le cas de l’identifiant `_`. Celui-ci doit nécessairement être présent +en bout de chaîne et permet de donner une valeur à l’identifiant de niveau +supérieur. C’est assez dur à expliquer mais très simple à comprendre. Dans +l’exemple donné plus haut, un `_` est associé à la valeur `FreshRSS` : cela +signifie qu’il n’y a pas besoin d’écrire `_t('gen.freshrss._')` mais +`_t('gen.freshrss')` suffit. -- cgit v1.2.3