diff options
| -rw-r--r-- | CHANGELOG.md | 15 | ||||
| -rw-r--r-- | app/Controllers/extensionController.php | 8 | ||||
| -rwxr-xr-x | app/Controllers/feedController.php | 9 | ||||
| -rw-r--r-- | app/Models/Entry.php | 101 | ||||
| -rw-r--r-- | app/Models/EntryDAO.php | 17 | ||||
| -rw-r--r-- | app/Models/Feed.php | 72 | ||||
| -rw-r--r-- | app/shares.php | 7 | ||||
| -rw-r--r-- | config.default.php | 4 | ||||
| -rw-r--r-- | constants.php | 2 | ||||
| -rw-r--r-- | docs/en/developers/03_Backend/05_Extensions.md | 1 | ||||
| -rw-r--r-- | docs/en/users/06_Fever_API.md | 2 | ||||
| -rw-r--r-- | docs/fr/developers/03_Backend/05_Extensions.md | 1 | ||||
| -rw-r--r-- | docs/fr/users/06_Mobile_access.md | 16 | ||||
| -rw-r--r-- | extensions/Tumblr-GDPR/README.md | 4 | ||||
| -rw-r--r-- | extensions/Tumblr-GDPR/extension.php | 13 | ||||
| -rw-r--r-- | extensions/Tumblr-GDPR/metadata.json | 8 | ||||
| -rw-r--r-- | lib/Minz/Configuration.php | 14 | ||||
| -rw-r--r-- | lib/Minz/ExtensionManager.php | 29 | ||||
| -rw-r--r-- | lib/Minz/Translate.php | 26 | ||||
| -rw-r--r-- | lib/SimplePie/SimplePie.php | 7 | ||||
| -rw-r--r-- | lib/lib_rss.php | 86 | ||||
| -rw-r--r-- | p/api/fever.php | 261 | ||||
| -rw-r--r-- | p/api/pshb.php | 6 | ||||
| -rw-r--r-- | p/themes/base-theme/template.css | 6 |
24 files changed, 386 insertions, 329 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index ec30f7470..84ab9fc95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # FreshRSS changelog +## 2018-06-16 FreshRSS 1.11.1 + +* Features + * Better support of `media:` tags such as thumbnails and descriptions (e.g. for YouTube) [#944](https://github.com/FreshRSS/FreshRSS/issues/944) +* Extensions + * New extension mechanism allowing changing HTTP headers and other SimplePie parameters [#1924](https://github.com/FreshRSS/FreshRSS/pull/1924) + * Built-in extension to fix Tumblr feeds from European Union due to GDPR [#1894](https://github.com/FreshRSS/FreshRSS/issues/1894) +* Bug fixing + * Fix bug in case of bad i18n in extensions [#1797](https://github.com/FreshRSS/FreshRSS/issues/1797) + * Fix extension callback for updated articles and PubSubHubbub [#1926](https://github.com/FreshRSS/FreshRSS/issues/1926) + * Fix regression in fetching full articles content [#1917](https://github.com/FreshRSS/FreshRSS/issues/1917) + * Fix several bugs in the new Fever API [#1930](https://github.com/FreshRSS/FreshRSS/issues/1930) + * Updated sharing to Mastodon [#1904](https://github.com/FreshRSS/FreshRSS/issues/1904) + + ## 2018-06-03 FreshRSS 1.11.0 * API diff --git a/app/Controllers/extensionController.php b/app/Controllers/extensionController.php index 311fd2e96..806e5a696 100644 --- a/app/Controllers/extensionController.php +++ b/app/Controllers/extensionController.php @@ -140,7 +140,7 @@ class FreshRSS_extension_Controller extends Minz_ActionController { if ($res === true) { $ext_list = $conf->extensions_enabled; - array_push_unique($ext_list, $ext_name); + $ext_list[$ext_name] = true; $conf->extensions_enabled = $ext_list; $conf->save(); @@ -196,7 +196,11 @@ class FreshRSS_extension_Controller extends Minz_ActionController { if ($res === true) { $ext_list = $conf->extensions_enabled; - array_remove($ext_list, $ext_name); + $legacyKey = array_search($ext_name, $ext_list, true); + if ($legacyKey !== false) { //Legacy format FreshRSS < 1.11.1 + unset($ext_list[$legacyKey]); + } + $ext_list[$ext_name] = false; $conf->extensions_enabled = $ext_list; $conf->save(); diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index ca85e7cb8..ec88156f9 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -351,13 +351,20 @@ class FreshRSS_feed_Controller extends Minz_ActionController { //This entry already exists and is unchanged. TODO: Remove the test with the zero'ed hash in FreshRSS v1.3 $oldGuids[] = $entry->guid(); } else { //This entry already exists but has been updated - //Minz_Log::debug('Entry with GUID `' . $entry->guid() . '` updated in feed ' . $feed->id() . + //Minz_Log::debug('Entry with GUID `' . $entry->guid() . '` updated in feed ' . $feed->url() . //', old hash ' . $existingHash . ', new hash ' . $entry->hash()); $mark_updated_article_unread = $feed->attributes('mark_updated_article_unread') !== null ? ( $feed->attributes('mark_updated_article_unread') ) : FreshRSS_Context::$user_conf->mark_updated_article_unread; $needFeedCacheRefresh = $mark_updated_article_unread; $entry->_isRead(FreshRSS_Context::$user_conf->mark_updated_article_unread ? false : null); //Change is_read according to policy. + + $entry = Minz_ExtensionManager::callHook('entry_before_insert', $entry); + if ($entry === null) { + // An extension has returned a null value, there is nothing to insert. + continue; + } + if (!$entryDAO->inTransaction()) { $entryDAO->beginTransaction(); } diff --git a/app/Models/Entry.php b/app/Models/Entry.php index c6b26a7cc..2b6059638 100644 --- a/app/Models/Entry.php +++ b/app/Models/Entry.php @@ -17,10 +17,11 @@ class FreshRSS_Entry extends Minz_Model { private $hash = null; private $is_read; //Nullable boolean private $is_favorite; + private $feedId; private $feed; private $tags; - public function __construct($feed = '', $guid = '', $title = '', $author = '', $content = '', + public function __construct($feedId = '', $guid = '', $title = '', $author = '', $content = '', $link = '', $pubdate = 0, $is_read = false, $is_favorite = false, $tags = '') { $this->_title($title); $this->_author($author); @@ -29,7 +30,7 @@ class FreshRSS_Entry extends Minz_Model { $this->_date($pubdate); $this->_isRead($is_read); $this->_isFavorite($is_favorite); - $this->_feed($feed); + $this->_feedId($feedId); $this->_tags(preg_split('/[\s#]/', $tags)); $this->_guid($guid); } @@ -75,10 +76,13 @@ class FreshRSS_Entry extends Minz_Model { } public function feed($object = false) { if ($object) { - $feedDAO = FreshRSS_Factory::createFeedDao(); - return $feedDAO->searchById($this->feed); - } else { + if ($this->feed == null) { + $feedDAO = FreshRSS_Factory::createFeedDao(); + $this->feed = $feedDAO->searchById($this->feedId); + } return $this->feed; + } else { + return $this->feedId; } } public function tags($inString = false) { @@ -145,7 +149,14 @@ class FreshRSS_Entry extends Minz_Model { $this->is_favorite = $value; } public function _feed($value) { - $this->feed = $value; + if ($value != null) { + $this->feed = $value; + $this->feedId = $this->feed->id(); + } + } + private function _feedId($value) { + $this->feed = null; + $this->feedId = $value; } public function _tags($value) { $this->hash = null; @@ -179,12 +190,74 @@ class FreshRSS_Entry extends Minz_Model { } } - public function loadCompleteContent($pathEntries) { + private static function get_content_by_parsing($url, $path, $attributes = array()) { + require_once(LIB_PATH . '/lib_phpQuery.php'); + $system_conf = Minz_Configuration::get('system'); + $limits = $system_conf->limits; + $feed_timeout = empty($attributes['timeout']) ? 0 : intval($attributes['timeout']); + + if ($system_conf->simplepie_syslog_enabled) { + syslog(LOG_INFO, 'FreshRSS GET ' . SimplePie_Misc::url_remove_credentials($url)); + } + + $ch = curl_init(); + curl_setopt_array($ch, array( + CURLOPT_URL => $url, + CURLOPT_REFERER => SimplePie_Misc::url_remove_credentials($url), + CURLOPT_HTTPHEADER => array('Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'), + CURLOPT_USERAGENT => FRESHRSS_USERAGENT, + CURLOPT_CONNECTTIMEOUT => $feed_timeout > 0 ? $feed_timeout : $limits['timeout'], + CURLOPT_TIMEOUT => $feed_timeout > 0 ? $feed_timeout : $limits['timeout'], + //CURLOPT_FAILONERROR => true; + CURLOPT_MAXREDIRS => 4, + CURLOPT_RETURNTRANSFER => true, + )); + if (version_compare(PHP_VERSION, '5.6.0') >= 0 || ini_get('open_basedir') == '') { + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); //Keep option separated for open_basedir PHP bug 65646 + } + if (defined('CURLOPT_ENCODING')) { + curl_setopt($ch, CURLOPT_ENCODING, ''); //Enable all encodings + } + curl_setopt_array($ch, $system_conf->curl_options); + if (isset($attributes['ssl_verify'])) { + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $attributes['ssl_verify'] ? 2 : 0); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $attributes['ssl_verify'] ? true : false); + } + $html = curl_exec($ch); + $c_status = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $c_error = curl_error($ch); + curl_close($ch); + + if ($c_status != 200 || $c_error != '') { + Minz_Log::warning('Error fetching content: HTTP code ' . $c_status . ': ' . $c_error . ' ' . $url); + } + + if ($html) { + $doc = phpQuery::newDocument($html); + $content = $doc->find($path); + + foreach (pq('img[data-src]') as $img) { + $imgP = pq($img); + $dataSrc = $imgP->attr('data-src'); + if (strlen($dataSrc) > 4) { + $imgP->attr('src', $dataSrc); + $imgP->removeAttr('data-src'); + } + } + + return trim(sanitizeHTML($content->__toString(), $url)); + } else { + throw new Exception(); + } + } + + public function loadCompleteContent() { // Gestion du contenu // On cherche à récupérer les articles en entier... même si le flux ne le propose pas - if ($pathEntries) { + $feed = $this->feed(true); + if ($feed != null && trim($feed->pathEntries()) != '') { $entryDAO = FreshRSS_Factory::createEntryDao(); - $entry = $entryDAO->searchByGuid($this->feed, $this->guid); + $entry = $entryDAO->searchByGuid($this->feedId, $this->guid); if ($entry) { // l'article existe déjà en BDD, en se contente de recharger ce contenu @@ -192,10 +265,14 @@ class FreshRSS_Entry extends Minz_Model { } else { try { // l'article n'est pas en BDD, on va le chercher sur le site - $this->content = get_content_by_parsing( - htmlspecialchars_decode($this->link(), ENT_QUOTES), $pathEntries, - $this->feed->attributes() + $fullContent = self::get_content_by_parsing( + htmlspecialchars_decode($this->link(), ENT_QUOTES), + $feed->pathEntries(), + $feed->attributes() ); + if ($fullContent != '') { + $this->content = $fullContent; + } } catch (Exception $e) { // rien à faire, on garde l'ancien contenu(requête a échoué) Minz_Log::warning($e->getMessage()); diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index a3bca3727..59f826c3e 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -904,8 +904,8 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } public function countUnreadRead() { - $sql = 'SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id WHERE priority > 0' - . ' UNION SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id WHERE priority > 0 AND is_read=0'; + $sql = 'SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id WHERE f.priority > 0' + . ' UNION SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id WHERE f.priority > 0 AND e.is_read=0'; $stm = $this->bd->prepare($sql); $stm->execute(); $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); @@ -914,9 +914,10 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { return array('all' => $all, 'unread' => $unread, 'read' => $all - $unread); } public function count($minPriority = null) { - $sql = 'SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id'; + $sql = 'SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e'; if ($minPriority !== null) { - $sql = ' WHERE priority > ' . intval($minPriority); + $sql .= ' INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id'; + $sql .= ' WHERE f.priority > ' . intval($minPriority); } $stm = $this->bd->prepare($sql); $stm->execute(); @@ -924,9 +925,13 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { return $res[0]; } public function countNotRead($minPriority = null) { - $sql = 'SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id WHERE is_read=0'; + $sql = 'SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e'; if ($minPriority !== null) { - $sql = ' AND priority > ' . intval($minPriority); + $sql .= ' INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id'; + } + $sql .= ' WHERE e.is_read=0'; + if ($minPriority !== null) { + $sql .= ' AND f.priority > ' . intval($minPriority); } $stm = $this->bd->prepare($sql); $stm->execute(); diff --git a/app/Models/Feed.php b/app/Models/Feed.php index 7eb079f15..89eb0a53c 100644 --- a/app/Models/Feed.php +++ b/app/Models/Feed.php @@ -286,6 +286,7 @@ class FreshRSS_Feed extends Minz_Model { if (!$loadDetails) { //Only activates auto-discovery when adding a new feed $feed->set_autodiscovery_level(SIMPLEPIE_LOCATOR_NONE); } + Minz_ExtensionManager::callHook('simplepie_before_init', $feed, $this); $mtime = $feed->init(); if ((!$mtime) || $feed->error()) { @@ -355,24 +356,52 @@ class FreshRSS_Feed extends Minz_Model { $content = html_only_entity_decode($item->get_content()); - $elinks = array(); - foreach ($item->get_enclosures() as $enclosure) { - $elink = $enclosure->get_link(); - if ($elink != '' && empty($elinks[$elink])) { - $elinks[$elink] = '1'; - $mime = strtolower($enclosure->get_type()); - if (strpos($mime, 'image/') === 0) { - $content .= '<p class="enclosure"><img src="' . $elink . '" alt="" /></p>'; - } elseif (strpos($mime, 'audio/') === 0) { - $content .= '<p class="enclosure"><audio preload="none" src="' . $elink - . '" controls="controls"></audio> <a download="" href="' . $elink . '">💾</a></p>'; - } elseif (strpos($mime, 'video/') === 0) { - $content .= '<p class="enclosure"><video preload="none" src="' . $elink - . '" controls="controls"></video> <a download="" href="' . $elink . '">💾</a></p>'; - } elseif (strpos($mime, 'application/') === 0 || strpos($mime, 'text/') === 0) { - $content .= '<p class="enclosure"><a download="" href="' . $elink . '">💾</a></p>'; - } else { - unset($elinks[$elink]); + if ($item->get_enclosures() != null) { + $elinks = array(); + foreach ($item->get_enclosures() as $enclosure) { + $elink = $enclosure->get_link(); + if ($elink != '' && empty($elinks[$elink])) { + $content .= '<div class="enclosure">'; + + if ($enclosure->get_title() != '') { + $content .= '<p class="enclosure-title">' . $enclosure->get_title() . '</p>'; + } + + $enclosureContent = ''; + $elinks[$elink] = true; + $mime = strtolower($enclosure->get_type()); + $medium = strtolower($enclosure->get_medium()); + if ($medium === 'image' || strpos($mime, 'image/') === 0) { + $enclosureContent .= '<p class="enclosure-content"><img src="' . $elink . '" alt="" /></p>'; + } elseif ($medium === 'audio' || strpos($mime, 'audio/') === 0) { + $enclosureContent .= '<p class="enclosure-content"><audio preload="none" src="' . $elink + . '" controls="controls"></audio> <a download="" href="' . $elink . '">💾</a></p>'; + } elseif ($medium === 'video' || strpos($mime, 'video/') === 0) { + $enclosureContent .= '<p class="enclosure-content"><video preload="none" src="' . $elink + . '" controls="controls"></video> <a download="" href="' . $elink . '">💾</a></p>'; + } elseif ($medium != '' || strpos($mime, 'application/') === 0 || strpos($mime, 'text/') === 0) { + $enclosureContent .= '<p class="enclosure-content"><a download="" href="' . $elink . '">💾</a></p>'; + } else { + unset($elinks[$elink]); + } + + $thumbnailContent = ''; + if ($enclosure->get_thumbnails() != null) { + foreach ($enclosure->get_thumbnails() as $thumbnail) { + if (empty($elinks[$thumbnail])) { + $elinks[$thumbnail] = true; + $thumbnailContent .= '<p><img class="enclosure-thumbnail" src="' . $thumbnail . '" alt="" /></p>'; + } + } + } + + $content .= $thumbnailContent; + $content .= $enclosureContent; + + if ($enclosure->get_description() != '') { + $content .= '<pre class="enclosure-description">' . $enclosure->get_description() . '</pre>'; + } + $content .= "</div>\n"; } } } @@ -391,8 +420,11 @@ class FreshRSS_Feed extends Minz_Model { $date ? $date : time() ); $entry->_tags($tags); - // permet de récupérer le contenu des flux tronqués - $entry->loadCompleteContent($this->pathEntries()); + $entry->_feed($this); + if ($this->pathEntries != '') { + // Optionally load full content for truncated feeds + $entry->loadCompleteContent(); + } $entries[] = $entry; unset($item); diff --git a/app/shares.php b/app/shares.php index cd09aa19c..8c6b4eac6 100644 --- a/app/shares.php +++ b/app/shares.php @@ -120,11 +120,10 @@ return array( 'method' => 'GET', ), 'mastodon' => array( - 'url' => '~URL~/api/v1/statuses', - 'transform' => array(), + 'url' => '~URL~/share?title=~TITLE~&url=~LINK~', + 'transform' => array('rawurlencode'), 'form' => 'advanced', - 'method' => 'POST', - 'field' => 'status', + 'method' => 'GET', ), 'pocket' => array( 'url' => 'https://getpocket.com/save?url=~LINK~&title=~TITLE~', diff --git a/config.default.php b/config.default.php index e6297a160..5b993f9e5 100644 --- a/config.default.php +++ b/config.default.php @@ -145,7 +145,9 @@ return array( ), # List of enabled FreshRSS extensions. - 'extensions_enabled' => array(), + 'extensions_enabled' => array( + 'Tumblr-GDPR' => true, + ), # Disable self-update, 'disable_update' => false, diff --git a/constants.php b/constants.php index 29ee59181..7fedc0d59 100644 --- a/constants.php +++ b/constants.php @@ -2,7 +2,7 @@ //NB: Do not edit; use ./constants.local.php instead. //<Not customisable> -define('FRESHRSS_VERSION', '1.11.0'); +define('FRESHRSS_VERSION', '1.11.1'); define('FRESHRSS_WEBSITE', 'https://freshrss.org'); define('FRESHRSS_WIKI', 'https://freshrss.github.io/FreshRSS/'); diff --git a/docs/en/developers/03_Backend/05_Extensions.md b/docs/en/developers/03_Backend/05_Extensions.md index 034328e07..ae304f6e0 100644 --- a/docs/en/developers/03_Backend/05_Extensions.md +++ b/docs/en/developers/03_Backend/05_Extensions.md @@ -366,6 +366,7 @@ The following events are available: - `entry_before_insert` (`function($entry) -> Entry | null`) : will be executed when a feed is refreshed and new entries will be imported into the database. The new entry (instance of FreshRSS_Entry) will be passed as parameter. - `feed_before_insert` (`function($feed) -> Feed | null`) : will be executed when a new feed is imported into the database. The new feed (instance of FreshRSS_Feed) will be passed as parameter. - `post_update` (`function(none) -> none`) : **TODO** add documentation +- `simplepie_before_init` (`function($simplePie, $feed) -> none`) : **TODO** add documentation ### Writing your own configure.phtml diff --git a/docs/en/users/06_Fever_API.md b/docs/en/users/06_Fever_API.md index 6d8a103b9..7d2af8b02 100644 --- a/docs/en/users/06_Fever_API.md +++ b/docs/en/users/06_Fever_API.md @@ -43,7 +43,7 @@ Following features are implemented: - setting starred marker for item(s) - setting read marker for feed - setting read marker for category -- supports FreshRSS extensions, which use th `entry_before_display` hook +- supports FreshRSS extensions, which use the `entry_before_display` hook Following features are not supported: - **Hot Links** aka **hot** as there is nothing in FreshRSS yet that is similar or could be used to simulate it diff --git a/docs/fr/developers/03_Backend/05_Extensions.md b/docs/fr/developers/03_Backend/05_Extensions.md index f844accd1..2ee81b781 100644 --- a/docs/fr/developers/03_Backend/05_Extensions.md +++ b/docs/fr/developers/03_Backend/05_Extensions.md @@ -329,6 +329,7 @@ TODO : - `entry_before_insert` (`function($entry) -> Entry | null`) - `feed_before_insert` (`function($feed) -> Feed | null`) - `post_update` (`function(none) -> none`) +- `simplepie_before_init` (`function($simplePie, $feed) -> none`) ### Écrire le fichier configure.phtml diff --git a/docs/fr/users/06_Mobile_access.md b/docs/fr/users/06_Mobile_access.md index b8e7c31f0..6981a74a6 100644 --- a/docs/fr/users/06_Mobile_access.md +++ b/docs/fr/users/06_Mobile_access.md @@ -41,6 +41,22 @@ Voir la [page sur l’API compatible Fever](06_Fever_API.md) pour une autre poss * Mettre à jour et retourner à l’étape 3. +# Tests sur mobile + +6. Vous pouvez maintenant tester sur une application mobile (News+, FeedMe, ou EasyRSS sur Android) + * en utilisant comme adresse https://rss.example.net/api/greader.php ou http://example.net/FreshRSS/p/api/greader.php selon la configuration de votre site Web. + * ⚠️ attention aux majuscules et aux espaces en tapant l’adresse avec le clavier du mobile ⚠️ + * avec votre nom d’utilisateur et le mot de passe enregistré au point 2 (mot de passe API). + + +# En cas de problèmes + + * Vous pouvez voir les logs API dans `./FreshRSS/data/users/_/log_api.txt` + * Si vous avez une erreur 404 (fichier non trouvé) lors de l’étape de test, et que vous êtes sous Apache, + voir http://httpd.apache.org/docs/trunk/mod/core.html#allowencodedslashes pour utiliser News+ +(facultatif pour EasyRSS et FeedMe qui devraient fonctionner dès lors que vous obtenez un PASS au test *Check partial server configuration*). + + # Clients compatibles Tout client supportant une API de type Google Reader. Sélection : diff --git a/extensions/Tumblr-GDPR/README.md b/extensions/Tumblr-GDPR/README.md new file mode 100644 index 000000000..fc5e4dd50 --- /dev/null +++ b/extensions/Tumblr-GDPR/README.md @@ -0,0 +1,4 @@ +# Tumblr-GDPR + +Needed for accessing [Tumblr](https://www.tumblr.com/) RSS feeds from the European Union: +bypass the [GPDR](https://en.wikipedia.org/wiki/General_Data_Protection_Regulation) check, implying consent. diff --git a/extensions/Tumblr-GDPR/extension.php b/extensions/Tumblr-GDPR/extension.php new file mode 100644 index 000000000..7a7708c9f --- /dev/null +++ b/extensions/Tumblr-GDPR/extension.php @@ -0,0 +1,13 @@ +<?php + +class TumblrGdprExtension extends Minz_Extension { + public function init() { + $this->registerHook('simplepie_before_init', array('TumblrGdprExtension', 'curlHook')); + } + + public static function curlHook($simplePie, $feed) { + if (preg_match('#^https?://[a-zA-Z_0-9-]+.tumblr.com/#i', $feed->url())) { + $simplePie->set_useragent(FRESHRSS_USERAGENT . ' like Googlebot'); + } + } +} diff --git a/extensions/Tumblr-GDPR/metadata.json b/extensions/Tumblr-GDPR/metadata.json new file mode 100644 index 000000000..b5c33787b --- /dev/null +++ b/extensions/Tumblr-GDPR/metadata.json @@ -0,0 +1,8 @@ +{ + "name": "Tumblr-GDPR", + "author": "Alkarex", + "description": "Bypass Tumblr’ GPDR check (implying consent) for the European Union", + "version": 1.0, + "entrypoint": "TumblrGdpr", + "type": "system" +} diff --git a/lib/Minz/Configuration.php b/lib/Minz/Configuration.php index 5470dc85f..3e486d68e 100644 --- a/lib/Minz/Configuration.php +++ b/lib/Minz/Configuration.php @@ -90,15 +90,15 @@ class Minz_Configuration { private $configuration_setter = null; public function removeExtension($ext_name) { - self::$extensions_enabled = array_diff( - self::$extensions_enabled, - array($ext_name) - ); + unset(self::$extensions_enabled[$ext_name]); + $legacyKey = array_search($ext_name, self::$extensions_enabled, true); + if ($legacyKey !== false) { //Legacy format FreshRSS < 1.11.1 + unset(self::$extensions_enabled[$legacyKey]); + } } public function addExtension($ext_name) { - $found = array_search($ext_name, self::$extensions_enabled) !== false; - if (!$found) { - self::$extensions_enabled[] = $ext_name; + if (!isset(self::$extensions_enabled[$ext_name])) { + self::$extensions_enabled[$ext_name] = true; } } diff --git a/lib/Minz/ExtensionManager.php b/lib/Minz/ExtensionManager.php index 10c49a8b6..b086c4001 100644 --- a/lib/Minz/ExtensionManager.php +++ b/lib/Minz/ExtensionManager.php @@ -35,6 +35,10 @@ class Minz_ExtensionManager { 'list' => array(), 'signature' => 'OneToOne', ), + 'simplepie_before_init' => array( // function($simplePie, $feed) -> none + 'list' => array(), + 'signature' => 'PassArguments', + ), ); private static $ext_to_hooks = array(); @@ -160,7 +164,8 @@ class Minz_ExtensionManager { self::$ext_list[$name] = $ext; if ($ext->getType() === 'system' && - in_array($name, self::$ext_auto_enabled)) { + (!empty(self::$ext_auto_enabled[$name]) || + in_array($name, self::$ext_auto_enabled, true))) { //Legacy format < FreshRSS 1.11.1 self::enable($ext->getName()); } @@ -189,8 +194,15 @@ class Minz_ExtensionManager { * @param string[] $ext_list the names of extensions we want to load. */ public static function enableByList($ext_list) { - foreach ($ext_list as $ext_name) { - self::enable($ext_name); + if (!is_array($ext_list)) { + return; + } + foreach ($ext_list as $ext_name => $ext_status) { + if (is_int($ext_name)) { //Legacy format int=>name + self::enable($ext_status); + } elseif ($ext_status) { //New format name=>Boolean + self::enable($ext_name); + } } } @@ -255,10 +267,15 @@ class Minz_ExtensionManager { } $signature = self::$hook_list[$hook_name]['signature']; - $signature = 'self::call' . $signature; $args = func_get_args(); - - return call_user_func_array($signature, $args); + if ($signature === 'PassArguments') { + array_shift($args); + foreach (self::$hook_list[$hook_name]['list'] as $function) { + call_user_func_array($function, $args); + } + } else { + return call_user_func_array('self::call' . $signature, $args); + } } /** diff --git a/lib/Minz/Translate.php b/lib/Minz/Translate.php index d9cd59f7e..db76e9214 100644 --- a/lib/Minz/Translate.php +++ b/lib/Minz/Translate.php @@ -64,12 +64,16 @@ class Minz_Translate { $list_langs = array(); foreach (self::$path_list as $path) { - $path_langs = array_values(array_diff( - scandir($path), - array('..', '.') - )); - - $list_langs = array_merge($list_langs, $path_langs); + $scan = scandir($path); + if (is_array($scan)) { + $path_langs = array_values(array_diff( + $scan, + array('..', '.') + )); + if (is_array($path_langs)) { + $list_langs = array_merge($list_langs, $path_langs); + } + } } return array_unique($list_langs); @@ -80,12 +84,10 @@ class Minz_Translate { * @param $path a path containing i18n directories (e.g. ./en/, ./fr/). */ public static function registerPath($path) { - if (in_array($path, self::$path_list)) { - return; + if (!in_array($path, self::$path_list) && is_dir($path)) { + self::$path_list[] = $path; + self::loadLang($path); } - - self::$path_list[] = $path; - self::loadLang($path); } /** @@ -94,7 +96,7 @@ class Minz_Translate { */ private static function loadLang($path) { $lang_path = $path . '/' . self::$lang_name; - if (!file_exists($lang_path) || is_null(self::$lang_name)) { + if (!file_exists($lang_path) || self::$lang_name == '') { // The lang path does not exist, nothing more to do. return; } diff --git a/lib/SimplePie/SimplePie.php b/lib/SimplePie/SimplePie.php index 5cd445b6d..b591bcddd 100644 --- a/lib/SimplePie/SimplePie.php +++ b/lib/SimplePie/SimplePie.php @@ -1322,7 +1322,12 @@ class SimplePie function cleanMd5($rss) { - return md5(preg_replace(array('#<(lastBuildDate|pubDate|updated|feedDate|dc:date|slash:comments)>[^<]+</\\1>#', '#<!--.+?-->#s'), '', $rss)); + return md5(preg_replace(array( + '#<(lastBuildDate|pubDate|updated|feedDate|dc:date|slash:comments)>[^<]+</\\1>#', + '#<(media:starRating|media:statistics) [^/<>]+/>#', + '#<!--.+?-->#s', + ), '', $rss)); + } /** diff --git a/lib/lib_rss.php b/lib/lib_rss.php index abb20f16a..5f460862e 100644 --- a/lib/lib_rss.php +++ b/lib/lib_rss.php @@ -253,68 +253,6 @@ function sanitizeHTML($data, $base = '') { return html_only_entity_decode($simplePie->sanitize->sanitize($data, SIMPLEPIE_CONSTRUCT_HTML, $base)); } -/* permet de récupérer le contenu d'un article pour un flux qui n'est pas complet */ -function get_content_by_parsing($url, $path, $attributes = array()) { - require_once(LIB_PATH . '/lib_phpQuery.php'); - $system_conf = Minz_Configuration::get('system'); - $limits = $system_conf->limits; - $feed_timeout = empty($attributes['timeout']) ? 0 : intval($attributes['timeout']); - - if ($system_conf->simplepie_syslog_enabled) { - syslog(LOG_INFO, 'FreshRSS GET ' . SimplePie_Misc::url_remove_credentials($url)); - } - - $ch = curl_init(); - curl_setopt_array($ch, array( - CURLOPT_URL => $url, - CURLOPT_REFERER => SimplePie_Misc::url_remove_credentials($url), - CURLOPT_HTTPHEADER => array('Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'), - CURLOPT_USERAGENT => FRESHRSS_USERAGENT, - CURLOPT_CONNECTTIMEOUT => $feed_timeout > 0 ? $feed_timeout : $limits['timeout'], - CURLOPT_TIMEOUT => $feed_timeout > 0 ? $feed_timeout : $limits['timeout'], - //CURLOPT_FAILONERROR => true; - CURLOPT_MAXREDIRS => 4, - CURLOPT_RETURNTRANSFER => true, - )); - if (version_compare(PHP_VERSION, '5.6.0') >= 0 || ini_get('open_basedir') == '') { - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); //Keep option separated for open_basedir PHP bug 65646 - } - if (defined('CURLOPT_ENCODING')) { - curl_setopt($ch, CURLOPT_ENCODING, ''); //Enable all encodings - } - curl_setopt_array($ch, $system_conf->curl_options); - if (isset($attributes['ssl_verify'])) { - curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $attributes['ssl_verify'] ? 2 : 0); - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $attributes['ssl_verify'] ? true : false); - } - $html = curl_exec($ch); - $c_status = curl_getinfo($ch, CURLINFO_HTTP_CODE); - $c_error = curl_error($ch); - curl_close($ch); - - if ($c_status != 200 || $c_error != '') { - Minz_Log::warning('Error fetching content: HTTP code ' . $c_status . ': ' . $c_error . ' ' . $url); - } - - if ($html) { - $doc = phpQuery::newDocument($html); - $content = $doc->find($path); - - foreach (pq('img[data-src]') as $img) { - $imgP = pq($img); - $dataSrc = $imgP->attr('data-src'); - if (strlen($dataSrc) > 4) { - $imgP->attr('src', $dataSrc); - $imgP->removeAttr('data-src'); - } - } - - return sanitizeHTML($content->__toString(), $url); - } else { - throw new Exception(); - } -} - /** * Add support of image lazy loading * Move content from src attribute to data-original @@ -542,7 +480,6 @@ function recursive_unlink($dir) { return rmdir($dir); } - /** * Remove queries where $get is appearing. * @param $get the get attribute which should be removed. @@ -559,29 +496,6 @@ function remove_query_by_get($get, $queries) { return $final_queries; } - -/** - * Add a value in an array and take care it is unique. - * @param $array the array in which we add the value. - * @param $value the value to add. - */ -function array_push_unique(&$array, $value) { - $found = array_search($value, $array) !== false; - if (!$found) { - $array[] = $value; - } -} - - -/** - * Remove a value from an array. - * @param $array the array from wich value is removed. - * @param $value the value to remove. - */ -function array_remove(&$array, $value) { - $array = array_diff($array, array($value)); -} - //RFC 4648 function base64url_encode($data) { return strtr(rtrim(base64_encode($data), '='), '+/', '-_'); diff --git a/p/api/fever.php b/p/api/fever.php index 6c9e2085d..d1482e8a1 100644 --- a/p/api/fever.php +++ b/p/api/fever.php @@ -30,31 +30,9 @@ Minz_Session::init('FreshRSS'); // ================================================================================================ -class FeverAPI_EntryDAO extends FreshRSS_EntryDAO +class FeverDAO extends Minz_ModelPdo { /** - * @return array - */ - public function countFever() - { - $values = array( - 'total' => 0, - 'min' => 0, - 'max' => 0, - ); - $sql = 'SELECT COUNT(id) as `total`, MIN(id) as `min`, MAX(id) as `max` FROM `' . $this->prefix . 'entry`'; - $stm = $this->bd->prepare($sql); - $stm->execute(); - $result = $stm->fetchAll(PDO::FETCH_ASSOC); - - if (!empty($result[0])) { - $values = $result[0]; - } - - return $values; - } - - /** * @param string $prefix * @param array $values * @param array $bindArray @@ -81,14 +59,15 @@ class FeverAPI_EntryDAO extends FreshRSS_EntryDAO { $values = array(); $order = ''; + $entryDAO = FreshRSS_Factory::createEntryDao(); $sql = 'SELECT id, guid, title, author, ' - . ($this->isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content') + . ($entryDAO->isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content') . ', link, date, is_read, is_favorite, id_feed, tags ' . 'FROM `' . $this->prefix . 'entry` WHERE'; if (!empty($entry_ids)) { - $bindEntryIds = $this->bindParamArray("id", $entry_ids, $values); + $bindEntryIds = $this->bindParamArray('id', $entry_ids, $values); $sql .= " id IN($bindEntryIds)"; } else if (!empty($max_id)) { $sql .= ' id < :id'; @@ -101,7 +80,7 @@ class FeverAPI_EntryDAO extends FreshRSS_EntryDAO } if (!empty($feed_ids)) { - $bindFeedIds = $this->bindParamArray("feed", $feed_ids, $values); + $bindFeedIds = $this->bindParamArray('feed', $feed_ids, $values); $sql .= " AND id_feed IN($bindFeedIds)"; } @@ -114,7 +93,7 @@ class FeverAPI_EntryDAO extends FreshRSS_EntryDAO $entries = array(); foreach ($result as $dao) { - $entries[] = self::daoToEntry($dao); + $entries[] = FreshRSS_EntryDAO::daoToEntry($dao); } return $entries; @@ -130,6 +109,9 @@ class FeverAPI const STATUS_OK = 1; const STATUS_ERR = 0; + private $entryDAO = null; + private $feedDAO = null; + /** * Authenticate the user * @@ -150,6 +132,8 @@ class FeverAPI $user_conf = get_user_configuration($username); if ($user_conf != null && $feverKey === $user_conf->feverKey) { FreshRSS_Context::$user_conf = $user_conf; + $this->entryDAO = FreshRSS_Factory::createEntryDao(); + $this->feedDAO = FreshRSS_Factory::createFeedDao(); return true; } Minz_Log::error('Fever API: Reset API password for user: ' . $username, API_LOG); @@ -176,30 +160,6 @@ class FeverAPI } /** - * @return FreshRSS_FeedDAO - */ - protected function getDaoForFeeds() - { - return new FreshRSS_FeedDAO(); - } - - /** - * @return FreshRSS_CategoryDAO - */ - protected function getDaoForCategories() - { - return new FreshRSS_CategoryDAO(); - } - - /** - * @return FeverAPI_EntryDAO - */ - protected function getDaoForEntries() - { - return new FeverAPI_EntryDAO(); - } - - /** * This does all the processing, since the fever api does not have a specific variable that specifies the operation * * @return array @@ -213,65 +173,65 @@ class FeverAPI throw new Exception('No user given or user is not allowed to access API'); } - if (isset($_REQUEST["groups"])) { - $response_arr["groups"] = $this->getGroups(); - $response_arr["feeds_groups"] = $this->getFeedsGroup(); + if (isset($_REQUEST['groups'])) { + $response_arr['groups'] = $this->getGroups(); + $response_arr['feeds_groups'] = $this->getFeedsGroup(); } - if (isset($_REQUEST["feeds"])) { - $response_arr["feeds"] = $this->getFeeds(); - $response_arr["feeds_groups"] = $this->getFeedsGroup(); + if (isset($_REQUEST['feeds'])) { + $response_arr['feeds'] = $this->getFeeds(); + $response_arr['feeds_groups'] = $this->getFeedsGroup(); } - if (isset($_REQUEST["favicons"])) { - $response_arr["favicons"] = $this->getFavicons(); + if (isset($_REQUEST['favicons'])) { + $response_arr['favicons'] = $this->getFavicons(); } - if (isset($_REQUEST["items"])) { - $response_arr["total_items"] = $this->getTotalItems(); - $response_arr["items"] = $this->getItems(); + if (isset($_REQUEST['items'])) { + $response_arr['total_items'] = $this->getTotalItems(); + $response_arr['items'] = $this->getItems(); } - if (isset($_REQUEST["links"])) { - $response_arr["links"] = $this->getLinks(); + if (isset($_REQUEST['links'])) { + $response_arr['links'] = $this->getLinks(); } - if (isset($_REQUEST["unread_item_ids"])) { - $response_arr["unread_item_ids"] = $this->getUnreadItemIds(); + if (isset($_REQUEST['unread_item_ids'])) { + $response_arr['unread_item_ids'] = $this->getUnreadItemIds(); } - if (isset($_REQUEST["saved_item_ids"])) { - $response_arr["saved_item_ids"] = $this->getSavedItemIds(); + if (isset($_REQUEST['saved_item_ids'])) { + $response_arr['saved_item_ids'] = $this->getSavedItemIds(); } - if (isset($_REQUEST["mark"], $_REQUEST["as"], $_REQUEST["id"]) && is_numeric($_REQUEST["id"])) { - $method_name = "set" . ucfirst($_REQUEST["mark"]) . "As" . ucfirst($_REQUEST["as"]); + if (isset($_REQUEST['mark'], $_REQUEST['as'], $_REQUEST['id']) && is_numeric($_REQUEST['id'])) { + $method_name = 'set' . ucfirst($_REQUEST['mark']) . 'As' . ucfirst($_REQUEST['as']); $allowedMethods = array( 'setFeedAsRead', 'setGroupAsRead', 'setItemAsRead', 'setItemAsSaved', 'setItemAsUnread', 'setItemAsUnsaved' ); if (in_array($method_name, $allowedMethods)) { - $id = intval($_REQUEST["id"]); - switch (strtolower($_REQUEST["mark"])) { + $id = intval($_REQUEST['id']); + switch (strtolower($_REQUEST['mark'])) { case 'item': $this->{$method_name}($id); break; case 'feed': case 'group': - $before = (isset($_REQUEST["before"])) ? $_REQUEST["before"] : null; + $before = isset($_REQUEST['before']) ? $_REQUEST['before'] : null; $this->{$method_name}($id, $before); break; } - switch ($_REQUEST["as"]) { - case "read": - case "unread": - $response_arr["unread_item_ids"] = $this->getUnreadItemIds(); + switch ($_REQUEST['as']) { + case 'read': + case 'unread': + $response_arr['unread_item_ids'] = $this->getUnreadItemIds(); break; case 'saved': case 'unsaved': - $response_arr["saved_item_ids"] = $this->getSavedItemIds(); + $response_arr['saved_item_ids'] = $this->getSavedItemIds(); break; } } @@ -308,8 +268,7 @@ class FeverAPI { $lastUpdate = 0; - $dao = $this->getDaoForFeeds(); - $entries = $dao->listFeedsOrderUpdate(-1, 1); + $entries = $this->feedDAO->listFeedsOrderUpdate(-1, 1); $feed = current($entries); if (!empty($feed)) { @@ -325,20 +284,18 @@ class FeverAPI protected function getFeeds() { $feeds = array(); - - $dao = $this->getDaoForFeeds(); - $myFeeds = $dao->listFeeds(); + $myFeeds = $this->feedDAO->listFeeds(); /** @var FreshRSS_Feed $feed */ foreach ($myFeeds as $feed) { $feeds[] = array( - "id" => $feed->id(), - "favicon_id" => $feed->id(), - "title" => $feed->name(), - "url" => $feed->url(), - "site_url" => $feed->website(), - "is_spark" => 0, // unsupported - "last_updated_on_time" => $feed->lastUpdate() + 'id' => $feed->id(), + 'favicon_id' => $feed->id(), + 'title' => $feed->name(), + 'url' => $feed->url(), + 'site_url' => $feed->website(), + 'is_spark' => 0, // unsupported + 'last_updated_on_time' => $feed->lastUpdate(), ); } @@ -352,14 +309,14 @@ class FeverAPI { $groups = array(); - $dao = $this->getDaoForCategories(); - $categories = $dao->listCategories(false, false); + $categoryDAO = new FreshRSS_CategoryDAO(); + $categories = $categoryDAO->listCategories(false, false); /** @var FreshRSS_Category $category */ foreach ($categories as $category) { $groups[] = array( 'id' => $category->id(), - 'title' => $category->name() + 'title' => $category->name(), ); } @@ -372,11 +329,8 @@ class FeverAPI protected function getFavicons() { $favicons = array(); - - $dao = $this->getDaoForFeeds(); - $myFeeds = $dao->listFeeds(); - $salt = FreshRSS_Context::$system_conf->salt; + $myFeeds = $this->feedDAO->listFeeds(); /** @var FreshRSS_Feed $feed */ foreach ($myFeeds as $feed) { @@ -388,8 +342,8 @@ class FeverAPI } $favicons[] = array( - "id" => $feed->id(), - "data" => image_type_to_mime_type(exif_imagetype($filename)) . ";base64," . base64_encode(file_get_contents($filename)) + 'id' => $feed->id(), + 'data' => image_type_to_mime_type(exif_imagetype($filename)) . ';base64,' . base64_encode(file_get_contents($filename)) ); } @@ -401,16 +355,7 @@ class FeverAPI */ protected function getTotalItems() { - $total_items = 0; - - $dao = $this->getDaoForEntries(); - $result = $dao->countFever(); - - if (!empty($result)) { - $total_items = $result['total']; - } - - return $total_items; + return $this->entryDAO->count(); } /** @@ -420,9 +365,7 @@ class FeverAPI { $groups = array(); $ids = array(); - - $dao = $this->getDaoForFeeds(); - $myFeeds = $dao->listFeeds(); + $myFeeds = $this->feedDAO->listFeeds(); /** @var FreshRSS_Feed $feed */ foreach ($myFeeds as $feed) { @@ -462,8 +405,7 @@ class FeverAPI */ protected function getUnreadItemIds() { - $dao = $this->getDaoForEntries(); - $entries = $dao->listIdsWhere('a', '', FreshRSS_Entry::STATE_NOT_READ, 'ASC', 0); + $entries = $this->entryDAO->listIdsWhere('a', '', FreshRSS_Entry::STATE_NOT_READ, 'ASC', 0); return $this->entriesToIdList($entries); } @@ -472,33 +414,28 @@ class FeverAPI */ protected function getSavedItemIds() { - $dao = $this->getDaoForEntries(); - $entries = $dao->listIdsWhere('a', '', FreshRSS_Entry::STATE_FAVORITE, 'ASC', 0); + $entries = $this->entryDAO->listIdsWhere('a', '', FreshRSS_Entry::STATE_FAVORITE, 'ASC', 0); return $this->entriesToIdList($entries); } protected function setItemAsRead($id) { - $dao = $this->getDaoForEntries(); - $dao->markRead($id, true); + return $this->entryDAO->markRead($id, true); } protected function setItemAsUnread($id) { - $dao = $this->getDaoForEntries(); - $dao->markRead($id, false); + return $this->entryDAO->markRead($id, false); } protected function setItemAsSaved($id) { - $dao = $this->getDaoForEntries(); - $dao->markFavorite($id, true); + return $this->entryDAO->markFavorite($id, true); } protected function setItemAsUnsaved($id) { - $dao = $this->getDaoForEntries(); - $dao->markFavorite($id, false); + return $this->entryDAO->markFavorite($id, false); } /** @@ -511,17 +448,17 @@ class FeverAPI $max_id = null; $since_id = null; - if (isset($_REQUEST["feed_ids"]) || isset($_REQUEST["group_ids"])) { - if (isset($_REQUEST["feed_ids"])) { - $feed_ids = explode(",", $_REQUEST["feed_ids"]); + if (isset($_REQUEST['feed_ids']) || isset($_REQUEST['group_ids'])) { + if (isset($_REQUEST['feed_ids'])) { + $feed_ids = explode(',', $_REQUEST['feed_ids']); } - $dao = $this->getDaoForCategories(); - if (isset($_REQUEST["group_ids"])) { - $group_ids = explode(",", $_REQUEST["group_ids"]); + if (isset($_REQUEST['group_ids'])) { + $categoryDAO = new FreshRSS_CategoryDAO(); + $group_ids = explode(',', $_REQUEST['group_ids']); foreach ($group_ids as $id) { /** @var FreshRSS_Category $category */ - $category = $dao->searchById($id); + $category = $categoryDAO->searchById($id); //TODO: Transform to SQL query without loop! Consider FreshRSS_CategoryDAO::listCategories(true) /** @var FreshRSS_Feed $feed */ foreach ($category->feeds() as $feed) { $feeds[] = $feed->id(); @@ -532,25 +469,25 @@ class FeverAPI } } - if (isset($_REQUEST["max_id"])) { + if (isset($_REQUEST['max_id'])) { // use the max_id argument to request the previous $item_limit items - if (is_numeric($_REQUEST["max_id"])) { - $max = ($_REQUEST["max_id"] > 0) ? intval($_REQUEST["max_id"]) : 0; + if (is_numeric($_REQUEST['max_id'])) { + $max = $_REQUEST['max_id'] > 0 ? intval($_REQUEST['max_id']) : 0; if ($max) { $max_id = $max; } } - } else if (isset($_REQUEST["with_ids"])) { - $entry_ids = explode(",", $_REQUEST["with_ids"]); + } else if (isset($_REQUEST['with_ids'])) { + $entry_ids = explode(',', $_REQUEST['with_ids']); } else { // use the since_id argument to request the next $item_limit items - $since_id = isset($_REQUEST["since_id"]) && is_numeric($_REQUEST["since_id"]) ? intval($_REQUEST["since_id"]) : 0; + $since_id = isset($_REQUEST['since_id']) && is_numeric($_REQUEST['since_id']) ? intval($_REQUEST['since_id']) : 0; } $items = array(); - $dao = $this->getDaoForEntries(); - $entries = $dao->findEntries($feed_ids, $entry_ids, $max_id, $since_id); + $feverDAO = new FeverDAO(); + $entries = $feverDAO->findEntries($feed_ids, $entry_ids, $max_id, $since_id); // Load list of extensions and enable the "system" ones. Minz_ExtensionManager::init(); @@ -562,15 +499,15 @@ class FeverAPI continue; } $items[] = array( - "id" => $entry->id(), - "feed_id" => $entry->feed(false), - "title" => $entry->title(), - "author" => $entry->author(), - "html" => $entry->content(), - "url" => $entry->link(), - "is_saved" => $entry->isFavorite() ? 1 : 0, - "is_read" => $entry->isRead() ? 1 : 0, - "created_on_time" => $entry->date(true) + 'id' => $entry->id(), + 'feed_id' => $entry->feed(false), + 'title' => $entry->title(), + 'author' => $entry->author(), + 'html' => $entry->content(), + 'url' => $entry->link(), + 'is_saved' => $entry->isFavorite() ? 1 : 0, + 'is_read' => $entry->isRead() ? 1 : 0, + 'created_on_time' => $entry->date(true), ); } @@ -585,43 +522,31 @@ class FeverAPI */ protected function convertBeforeToId($beforeTimestamp) { - // if before is zero, set it to now so feeds all items are read from before this point in time - if ($beforeTimestamp == 0) { - $before = time(); - } - $before = PHP_INT_MAX; - - return $before; + return $beforeTimestamp == 0 ? 0 : $beforeTimestamp . '000000'; } protected function setFeedAsRead($id, $before) { $before = $this->convertBeforeToId($before); - $dao = $this->getDaoForEntries(); - return $dao->markReadFeed($id, $before); + return $this->entryDAO->markReadFeed($id, $before); } protected function setGroupAsRead($id, $before) { - $dao = $this->getDaoForEntries(); + $before = $this->convertBeforeToId($before); // special case to mark all items as read - if ($id === 0) { - $result = $dao->countFever(); - - if (!empty($result)) { - return $dao->markReadEntries($result['max']); - } + if ($id == 0) { + return $this->entryDAO->markReadEntries($before); } - $before = $this->convertBeforeToId($before); - return $dao->markReadCat($id, $before); + return $this->entryDAO->markReadCat($id, $before); } } // ================================================================================================ // refresh is not allowed yet, probably we find a way to support it later -if (isset($_REQUEST["refresh"])) { +if (isset($_REQUEST['refresh'])) { Minz_Log::warning('Fever API: Refresh items - notImplemented()', API_LOG); header('HTTP/1.1 501 Not Implemented'); header('Content-Type: text/plain; charset=UTF-8'); @@ -631,7 +556,7 @@ if (isset($_REQUEST["refresh"])) { // Start the Fever API handling $handler = new FeverAPI(); -header("Content-Type: application/json; charset=UTF-8"); +header('Content-Type: application/json; charset=UTF-8'); if (!$handler->isAuthenticatedApiUser()) { echo $handler->wrap(FeverAPI::STATUS_ERR, array()); diff --git a/p/api/pshb.php b/p/api/pshb.php index 57a7bb0dd..ac78bfd74 100644 --- a/p/api/pshb.php +++ b/p/api/pshb.php @@ -116,6 +116,8 @@ if ($self !== base64url_decode($canonical64)) { $self = base64url_decode($canonical64); } +Minz_ExtensionManager::init(); + $nb = 0; foreach ($users as $userFilename) { $username = basename($userFilename, '.txt'); @@ -132,6 +134,10 @@ foreach ($users as $userFilename) { join_path(FRESHRSS_PATH, 'config-user.default.php')); new Minz_ModelPdo($username); //TODO: FIXME: Quick-fix while waiting for a better FreshRSS() constructor/init FreshRSS_Context::init(); + if (FreshRSS_Context::$user_conf != null) { + Minz_ExtensionManager::enableByList(FreshRSS_Context::$user_conf->extensions_enabled); + } + list($updated_feeds, $feed, $nb_new_articles) = FreshRSS_feed_Controller::actualizeFeed(0, $self, false, $simplePie); if ($updated_feeds > 0 || $feed != false) { $nb++; diff --git a/p/themes/base-theme/template.css b/p/themes/base-theme/template.css index 72487ca17..9b8ad902b 100644 --- a/p/themes/base-theme/template.css +++ b/p/themes/base-theme/template.css @@ -832,11 +832,15 @@ input:checked + .slide-container .properties { display: none; } -.enclosure > [download] { +.enclosure [download] { font-size: xx-large; margin-left: .8em; } +pre.enclosure-description { + white-space: pre-line; +} + /*=== MOBILE */ /*===========*/ @media(max-width: 840px) { |
