From 878e96202e8a22e4857b98e29b0a1fce68eccbc9 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sun, 15 Dec 2013 03:30:24 +0100 Subject: Grosse refactorisation pour permettre le chargement automatique des classes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit C'est parti de changements pour https://github.com/marienfressinaud/FreshRSS/issues/255 et finalement j'ai continué la refactorisation... Ajout de préfixes FreshRSS_ et Minz_ sur le modèle de SimplePie_. Toutes les classes sont maintenant en chargement automatique (devrait améliorer les performances en évitant de charger plein de classes inutilisées, et faciliter la maintenance). Suppression de set_include_path(). Si souhaité, certaines classes de Minz pourraient être déplacées dans un sous-répertoire, par exemple les exceptions. Tests et relecture nécessaires. --- app/Models/CategoryDAO.php | 252 ++++++++++++++++++++++++ app/Models/Configuration.php | 326 ++++++++++++++++++++++++++++++ app/Models/ConfigurationDAO.php | 156 +++++++++++++++ app/Models/EntryDAO.php | 425 ++++++++++++++++++++++++++++++++++++++++ app/Models/FeedDAO.php | 341 ++++++++++++++++++++++++++++++++ app/Models/Log.php | 26 +++ app/Models/LogDAO.php | 20 ++ app/Models/Themes.php | 88 +++++++++ 8 files changed, 1634 insertions(+) create mode 100644 app/Models/CategoryDAO.php create mode 100644 app/Models/Configuration.php create mode 100644 app/Models/ConfigurationDAO.php create mode 100644 app/Models/EntryDAO.php create mode 100644 app/Models/FeedDAO.php create mode 100644 app/Models/Log.php create mode 100644 app/Models/LogDAO.php create mode 100644 app/Models/Themes.php (limited to 'app/Models') diff --git a/app/Models/CategoryDAO.php b/app/Models/CategoryDAO.php new file mode 100644 index 000000000..793e593c3 --- /dev/null +++ b/app/Models/CategoryDAO.php @@ -0,0 +1,252 @@ +prefix . 'category` (name, color) VALUES(?, ?)'; + $stm = $this->bd->prepare ($sql); + + $values = array ( + substr($valuesTmp['name'], 0, 255), + substr($valuesTmp['color'], 0, 7), + ); + + if ($stm && $stm->execute ($values)) { + return $this->bd->lastInsertId(); + } else { + $info = $stm->errorInfo(); + Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); + return false; + } + } + + public function updateCategory ($id, $valuesTmp) { + $sql = 'UPDATE `' . $this->prefix . 'category` SET name=?, color=? WHERE id=?'; + $stm = $this->bd->prepare ($sql); + + $values = array ( + $valuesTmp['name'], + $valuesTmp['color'], + $id + ); + + if ($stm && $stm->execute ($values)) { + return $stm->rowCount(); + } else { + $info = $stm->errorInfo(); + Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); + return false; + } + } + + public function deleteCategory ($id) { + $sql = 'DELETE FROM `' . $this->prefix . 'category` WHERE id=?'; + $stm = $this->bd->prepare ($sql); + + $values = array ($id); + + if ($stm && $stm->execute ($values)) { + return $stm->rowCount(); + } else { + $info = $stm->errorInfo(); + Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); + return false; + } + } + + public function searchById ($id) { + $sql = 'SELECT * FROM `' . $this->prefix . 'category` WHERE id=?'; + $stm = $this->bd->prepare ($sql); + + $values = array ($id); + + $stm->execute ($values); + $res = $stm->fetchAll (PDO::FETCH_ASSOC); + $cat = HelperCategory::daoToCategory ($res); + + if (isset ($cat[0])) { + return $cat[0]; + } else { + return false; + } + } + public function searchByName ($name) { + $sql = 'SELECT * FROM `' . $this->prefix . 'category` WHERE name=?'; + $stm = $this->bd->prepare ($sql); + + $values = array ($name); + + $stm->execute ($values); + $res = $stm->fetchAll (PDO::FETCH_ASSOC); + $cat = HelperCategory::daoToCategory ($res); + + if (isset ($cat[0])) { + return $cat[0]; + } else { + return false; + } + } + + public function listCategories ($prePopulateFeeds = true, $details = false) { + if ($prePopulateFeeds) { + $sql = 'SELECT c.id AS c_id, c.name AS c_name, ' + . ($details ? 'c.color AS c_color, ' : '') + . ($details ? 'f.* ' : 'f.id, f.name, f.website, f.priority, f.error, f.cache_nbEntries, f.cache_nbUnreads ') + . 'FROM `' . $this->prefix . 'category` c ' + . 'LEFT OUTER JOIN `' . $this->prefix . 'feed` f ON f.category = c.id ' + . 'GROUP BY f.id ' + . 'ORDER BY c.name, f.name'; + $stm = $this->bd->prepare ($sql); + $stm->execute (); + return HelperCategory::daoToCategoryPrepopulated ($stm->fetchAll (PDO::FETCH_ASSOC)); + } else { + $sql = 'SELECT * FROM `' . $this->prefix . 'category` ORDER BY name'; + $stm = $this->bd->prepare ($sql); + $stm->execute (); + return HelperCategory::daoToCategory ($stm->fetchAll (PDO::FETCH_ASSOC)); + } + } + + public function getDefault () { + $sql = 'SELECT * FROM `' . $this->prefix . 'category` WHERE id=1'; + $stm = $this->bd->prepare ($sql); + + $stm->execute (); + $res = $stm->fetchAll (PDO::FETCH_ASSOC); + $cat = HelperCategory::daoToCategory ($res); + + if (isset ($cat[0])) { + return $cat[0]; + } else { + return false; + } + } + public function checkDefault () { + $def_cat = $this->searchById (1); + + if ($def_cat === false) { + $cat = new FreshRSS_Category (Minz_Translate::t ('default_category')); + $cat->_id (1); + + $values = array ( + 'id' => $cat->id (), + 'name' => $cat->name (), + 'color' => $cat->color () + ); + + $this->addCategory ($values); + } + } + + public function count () { + $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'category`'; + $stm = $this->bd->prepare ($sql); + $stm->execute (); + $res = $stm->fetchAll (PDO::FETCH_ASSOC); + + return $res[0]['count']; + } + + public function countFeed ($id) { + $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'feed` WHERE category=?'; + $stm = $this->bd->prepare ($sql); + $values = array ($id); + $stm->execute ($values); + $res = $stm->fetchAll (PDO::FETCH_ASSOC); + + return $res[0]['count']; + } + + public function countNotRead ($id) { + $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id WHERE category=? AND e.is_read=0'; + $stm = $this->bd->prepare ($sql); + $values = array ($id); + $stm->execute ($values); + $res = $stm->fetchAll (PDO::FETCH_ASSOC); + + return $res[0]['count']; + } +} + +class HelperCategory { + public static function findFeed($categories, $feed_id) { + foreach ($categories as $category) { + foreach ($category->feeds () as $feed) { + if ($feed->id () === $feed_id) { + return $feed; + } + } + } + return null; + } + + public static function CountUnreads($categories, $minPriority = 0) { + $n = 0; + foreach ($categories as $category) { + foreach ($category->feeds () as $feed) { + if ($feed->priority () >= $minPriority) { + $n += $feed->nbNotRead(); + } + } + } + return $n; + } + + public static function daoToCategoryPrepopulated ($listDAO) { + $list = array (); + + if (!is_array ($listDAO)) { + $listDAO = array ($listDAO); + } + + $previousLine = null; + $feedsDao = array(); + foreach ($listDAO as $line) { + if ($previousLine['c_id'] != null && $line['c_id'] !== $previousLine['c_id']) { + // End of the current category, we add it to the $list + $cat = new FreshRSS_Category ( + $previousLine['c_name'], + isset($previousLine['c_color']) ? $previousLine['c_color'] : '', + HelperFeed::daoToFeed ($feedsDao, $previousLine['c_id']) + ); + $cat->_id ($previousLine['c_id']); + $list[$previousLine['c_id']] = $cat; + + $feedsDao = array(); //Prepare for next category + } + + $previousLine = $line; + $feedsDao[] = $line; + } + + // add the last category + if ($previousLine != null) { + $cat = new FreshRSS_Category ( + $previousLine['c_name'], + isset($previousLine['c_color']) ? $previousLine['c_color'] : '', + HelperFeed::daoToFeed ($feedsDao, $previousLine['c_id']) + ); + $cat->_id ($previousLine['c_id']); + $list[$previousLine['c_id']] = $cat; + } + + return $list; + } + + public static function daoToCategory ($listDAO) { + $list = array (); + + if (!is_array ($listDAO)) { + $listDAO = array ($listDAO); + } + + foreach ($listDAO as $key => $dao) { + $cat = new FreshRSS_Category ( + $dao['name'], + $dao['color'] + ); + $cat->_id ($dao['id']); + $list[$key] = $cat; + } + + return $list; + } +} diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php new file mode 100644 index 000000000..7ef76b522 --- /dev/null +++ b/app/Models/Configuration.php @@ -0,0 +1,326 @@ + 'English', + 'fr' => 'Français', + ); + private $language; + private $posts_per_page; + private $view_mode; + private $default_view; + private $display_posts; + private $onread_jump_next; + private $lazyload; + private $sort_order; + private $old_entries; + private $shortcuts = array (); + private $mail_login = ''; + private $mark_when = array (); + private $sharing = array (); + private $theme; + private $anon_access; + private $token; + private $auto_load_more; + private $topline_read; + private $topline_favorite; + private $topline_date; + private $topline_link; + private $bottomline_read; + private $bottomline_favorite; + private $bottomline_sharing; + private $bottomline_tags; + private $bottomline_date; + private $bottomline_link; + + public function __construct () { + $confDAO = new FreshRSS_ConfigurationDAO (); + $this->_language ($confDAO->language); + $this->_postsPerPage ($confDAO->posts_per_page); + $this->_viewMode ($confDAO->view_mode); + $this->_defaultView ($confDAO->default_view); + $this->_displayPosts ($confDAO->display_posts); + $this->_onread_jump_next ($confDAO->onread_jump_next); + $this->_lazyload ($confDAO->lazyload); + $this->_sortOrder ($confDAO->sort_order); + $this->_oldEntries ($confDAO->old_entries); + $this->_shortcuts ($confDAO->shortcuts); + $this->_mailLogin ($confDAO->mail_login); + $this->_markWhen ($confDAO->mark_when); + $this->_sharing ($confDAO->sharing); + $this->_theme ($confDAO->theme); + FreshRSS_Themes::setThemeId ($confDAO->theme); + $this->_anonAccess ($confDAO->anon_access); + $this->_token ($confDAO->token); + $this->_autoLoadMore ($confDAO->auto_load_more); + $this->_topline_read ($confDAO->topline_read); + $this->_topline_favorite ($confDAO->topline_favorite); + $this->_topline_date ($confDAO->topline_date); + $this->_topline_link ($confDAO->topline_link); + $this->_bottomline_read ($confDAO->bottomline_read); + $this->_bottomline_favorite ($confDAO->bottomline_favorite); + $this->_bottomline_sharing ($confDAO->bottomline_sharing); + $this->_bottomline_tags ($confDAO->bottomline_tags); + $this->_bottomline_date ($confDAO->bottomline_date); + $this->_bottomline_link ($confDAO->bottomline_link); + } + + public function availableLanguages () { + return $this->available_languages; + } + public function language () { + return $this->language; + } + public function postsPerPage () { + return $this->posts_per_page; + } + public function viewMode () { + return $this->view_mode; + } + public function defaultView () { + return $this->default_view; + } + public function displayPosts () { + return $this->display_posts; + } + public function onread_jump_next () { + return $this->onread_jump_next; + } + public function lazyload () { + return $this->lazyload; + } + public function sortOrder () { + return $this->sort_order; + } + public function oldEntries () { + return $this->old_entries; + } + public function shortcuts () { + return $this->shortcuts; + } + public function mailLogin () { + return $this->mail_login; + } + public function markWhen () { + return $this->mark_when; + } + public function markWhenArticle () { + return $this->mark_when['article']; + } + public function markWhenSite () { + return $this->mark_when['site']; + } + public function markWhenScroll () { + return $this->mark_when['scroll']; + } + public function markUponReception () { + return $this->mark_when['reception']; + } + public function sharing ($key = false) { + if ($key === false) { + return $this->sharing; + } elseif (isset ($this->sharing[$key])) { + return $this->sharing[$key]; + } + return false; + } + public function theme () { + return $this->theme; + } + public function anonAccess () { + return $this->anon_access; + } + public function token () { + return $this->token; + } + public function autoLoadMore () { + return $this->auto_load_more; + } + public function toplineRead () { + return $this->topline_read; + } + public function toplineFavorite () { + return $this->topline_favorite; + } + public function toplineDate () { + return $this->topline_date; + } + public function toplineLink () { + return $this->topline_link; + } + public function bottomlineRead () { + return $this->bottomline_read; + } + public function bottomlineFavorite () { + return $this->bottomline_favorite; + } + public function bottomlineSharing () { + return $this->bottomline_sharing; + } + public function bottomlineTags () { + return $this->bottomline_tags; + } + public function bottomlineDate () { + return $this->bottomline_date; + } + public function bottomlineLink () { + return $this->bottomline_link; + } + + public function _language ($value) { + if (!isset ($this->available_languages[$value])) { + $value = 'en'; + } + $this->language = $value; + } + public function _postsPerPage ($value) { + $value = intval($value); + $this->posts_per_page = $value > 0 ? $value : 10; + } + public function _viewMode ($value) { + if ($value == 'global' || $value == 'reader') { + $this->view_mode = $value; + } else { + $this->view_mode = 'normal'; + } + } + public function _defaultView ($value) { + if ($value == 'not_read') { + $this->default_view = 'not_read'; + } else { + $this->default_view = 'all'; + } + } + public function _displayPosts ($value) { + if ($value == 'yes') { + $this->display_posts = 'yes'; + } else { + $this->display_posts = 'no'; + } + } + public function _onread_jump_next ($value) { + if ($value == 'no') { + $this->onread_jump_next = 'no'; + } else { + $this->onread_jump_next = 'yes'; + } + } + public function _lazyload ($value) { + if ($value == 'no') { + $this->lazyload = 'no'; + } else { + $this->lazyload = 'yes'; + } + } + public function _sortOrder ($value) { + $this->sort_order = $value === 'ASC' ? 'ASC' : 'DESC'; + } + public function _oldEntries ($value) { + if (ctype_digit ($value) && $value > 0) { + $this->old_entries = $value; + } else { + $this->old_entries = 3; + } + } + public function _shortcuts ($values) { + foreach ($values as $key => $value) { + $this->shortcuts[$key] = $value; + } + } + public function _mailLogin ($value) { + if (filter_var ($value, FILTER_VALIDATE_EMAIL)) { + $this->mail_login = $value; + } elseif ($value == false) { + $this->mail_login = false; + } + } + public function _markWhen ($values) { + if(!isset($values['article'])) { + $values['article'] = 'yes'; + } + if(!isset($values['site'])) { + $values['site'] = 'yes'; + } + if(!isset($values['scroll'])) { + $values['scroll'] = 'yes'; + } + if(!isset($values['reception'])) { + $values['reception'] = 'no'; + } + + $this->mark_when['article'] = $values['article']; + $this->mark_when['site'] = $values['site']; + $this->mark_when['scroll'] = $values['scroll']; + $this->mark_when['reception'] = $values['reception']; + } + public function _sharing ($values) { + $are_url = array ('shaarli', 'poche', 'diaspora'); + foreach ($values as $key => $value) { + if (in_array($key, $are_url)) { + $is_url = ( + filter_var ($value, FILTER_VALIDATE_URL) || + (version_compare(PHP_VERSION, '5.3.3', '<') && + (strpos($value, '-') > 0) && + ($value === filter_var($value, FILTER_SANITIZE_URL))) + ); //PHP bug #51192 + + if (!$is_url) { + $value = ''; + } + } elseif(!is_bool ($value)) { + $value = true; + } + + $this->sharing[$key] = $value; + } + } + public function _theme ($value) { + $this->theme = $value; + } + public function _anonAccess ($value) { + if ($value == 'yes') { + $this->anon_access = 'yes'; + } else { + $this->anon_access = 'no'; + } + } + public function _token ($value) { + $this->token = $value; + } + public function _autoLoadMore ($value) { + if ($value == 'yes') { + $this->auto_load_more = 'yes'; + } else { + $this->auto_load_more = 'no'; + } + } + public function _topline_read ($value) { + $this->topline_read = $value === 'yes'; + } + public function _topline_favorite ($value) { + $this->topline_favorite = $value === 'yes'; + } + public function _topline_date ($value) { + $this->topline_date = $value === 'yes'; + } + public function _topline_link ($value) { + $this->topline_link = $value === 'yes'; + } + public function _bottomline_read ($value) { + $this->bottomline_read = $value === 'yes'; + } + public function _bottomline_favorite ($value) { + $this->bottomline_favorite = $value === 'yes'; + } + public function _bottomline_sharing ($value) { + $this->bottomline_sharing = $value === 'yes'; + } + public function _bottomline_tags ($value) { + $this->bottomline_tags = $value === 'yes'; + } + public function _bottomline_date ($value) { + $this->bottomline_date = $value === 'yes'; + } + public function _bottomline_link ($value) { + $this->bottomline_link = $value === 'yes'; + } +} diff --git a/app/Models/ConfigurationDAO.php b/app/Models/ConfigurationDAO.php new file mode 100644 index 000000000..fec58d234 --- /dev/null +++ b/app/Models/ConfigurationDAO.php @@ -0,0 +1,156 @@ + 'r', + 'mark_favorite' => 'f', + 'go_website' => 'space', + 'next_entry' => 'j', + 'prev_entry' => 'k', + 'collapse_entry' => 'c', + 'load_more' => 'm' + ); + public $mail_login = ''; + public $mark_when = array ( + 'article' => 'yes', + 'site' => 'yes', + 'scroll' => 'no', + 'reception' => 'no' + ); + public $sharing = array ( + 'shaarli' => '', + 'poche' => '', + 'diaspora' => '', + 'twitter' => true, + 'g+' => true, + 'facebook' => true, + 'email' => true, + 'print' => true + ); + public $theme = 'default'; + public $anon_access = 'no'; + public $token = ''; + public $auto_load_more = 'no'; + public $topline_read = 'yes'; + public $topline_favorite = 'yes'; + public $topline_date = 'yes'; + public $topline_link = 'yes'; + public $bottomline_read = 'yes'; + public $bottomline_favorite = 'yes'; + public $bottomline_sharing = 'yes'; + public $bottomline_tags = 'yes'; + public $bottomline_date = 'yes'; + public $bottomline_link = 'yes'; + + public function __construct ($nameFile = '') { + if (empty($nameFile)) { + $nameFile = DATA_PATH . '/' . Minz_Configuration::currentUser () . '_user.php'; + } + parent::__construct ($nameFile); + + // TODO : simplifier ce code, une boucle for() devrait suffire ! + if (isset ($this->array['language'])) { + $this->language = $this->array['language']; + } + if (isset ($this->array['posts_per_page'])) { + $this->posts_per_page = $this->array['posts_per_page']; + } + if (isset ($this->array['view_mode'])) { + $this->view_mode = $this->array['view_mode']; + } + if (isset ($this->array['default_view'])) { + $this->default_view = $this->array['default_view']; + } + if (isset ($this->array['display_posts'])) { + $this->display_posts = $this->array['display_posts']; + } + if (isset ($this->array['onread_jump_next'])) { + $this->onread_jump_next = $this->array['onread_jump_next']; + } + if (isset ($this->array['lazyload'])) { + $this->lazyload = $this->array['lazyload']; + } + if (isset ($this->array['sort_order'])) { + $this->sort_order = $this->array['sort_order']; + } + if (isset ($this->array['old_entries'])) { + $this->old_entries = $this->array['old_entries']; + } + if (isset ($this->array['shortcuts'])) { + $this->shortcuts = array_merge ( + $this->shortcuts, $this->array['shortcuts'] + ); + } + if (isset ($this->array['mail_login'])) { + $this->mail_login = $this->array['mail_login']; + } + if (isset ($this->array['mark_when'])) { + $this->mark_when = $this->array['mark_when']; + } + if (isset ($this->array['sharing'])) { + $this->sharing = array_merge ( + $this->sharing, $this->array['sharing'] + ); + } + if (isset ($this->array['theme'])) { + $this->theme = $this->array['theme']; + } + if (isset ($this->array['anon_access'])) { + $this->anon_access = $this->array['anon_access']; + } + if (isset ($this->array['token'])) { + $this->token = $this->array['token']; + } + if (isset ($this->array['auto_load_more'])) { + $this->auto_load_more = $this->array['auto_load_more']; + } + + if (isset ($this->array['topline_read'])) { + $this->topline_read = $this->array['topline_read']; + } + if (isset ($this->array['topline_favorite'])) { + $this->topline_favorite = $this->array['topline_favorite']; + } + if (isset ($this->array['topline_date'])) { + $this->topline_date = $this->array['topline_date']; + } + if (isset ($this->array['topline_link'])) { + $this->topline_link = $this->array['topline_link']; + } + if (isset ($this->array['bottomline_read'])) { + $this->bottomline_read = $this->array['bottomline_read']; + } + if (isset ($this->array['bottomline_favorite'])) { + $this->bottomline_favorite = $this->array['bottomline_favorite']; + } + if (isset ($this->array['bottomline_sharing'])) { + $this->bottomline_sharing = $this->array['bottomline_sharing']; + } + if (isset ($this->array['bottomline_tags'])) { + $this->bottomline_tags = $this->array['bottomline_tags']; + } + if (isset ($this->array['bottomline_date'])) { + $this->bottomline_date = $this->array['bottomline_date']; + } + if (isset ($this->array['bottomline_link'])) { + $this->bottomline_link = $this->array['bottomline_link']; + } + } + + public function update ($values) { + foreach ($values as $key => $value) { + $this->array[$key] = $value; + } + + $this->writeFile($this->array); + invalidateHttpCache(); + } +} diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php new file mode 100644 index 000000000..8c18150b6 --- /dev/null +++ b/app/Models/EntryDAO.php @@ -0,0 +1,425 @@ +prefix . 'entry`(id, guid, title, author, content_bin, link, date, is_read, is_favorite, id_feed, tags) ' + . 'VALUES(?, ?, ?, ?, COMPRESS(?), ?, ?, ?, ?, ?, ?)'; + $stm = $this->bd->prepare ($sql); + + $values = array ( + $valuesTmp['id'], + substr($valuesTmp['guid'], 0, 760), + substr($valuesTmp['title'], 0, 255), + substr($valuesTmp['author'], 0, 255), + $valuesTmp['content'], + substr($valuesTmp['link'], 0, 1023), + $valuesTmp['date'], + $valuesTmp['is_read'], + $valuesTmp['is_favorite'], + $valuesTmp['id_feed'], + substr($valuesTmp['tags'], 0, 1023), + ); + + if ($stm && $stm->execute ($values)) { + return $this->bd->lastInsertId(); + } else { + $info = $stm->errorInfo(); + if ((int)($info[0] / 1000) !== 23) { //Filter out "SQLSTATE Class code 23: Constraint Violation" because of expected duplicate entries + Minz_Log::record ('SQL error ' . $info[0] . ': ' . $info[1] . ' ' . $info[2] + . ' while adding entry in feed ' . $valuesTmp['id_feed'] . ' with title: ' . $valuesTmp['title'], Minz_Log::ERROR); + } /*else { + Minz_Log::record ('SQL error ' . $info[0] . ': ' . $info[1] . ' ' . $info[2] + . ' while adding entry in feed ' . $valuesTmp['id_feed'] . ' with title: ' . $valuesTmp['title'], Minz_Log::DEBUG); + }*/ + return false; + } + } + + public function markFavorite ($id, $is_favorite = true) { + $sql = 'UPDATE `' . $this->prefix . 'entry` e ' + . 'SET e.is_favorite = ? ' + . 'WHERE e.id=?'; + $values = array ($is_favorite ? 1 : 0, $id); + $stm = $this->bd->prepare ($sql); + if ($stm && $stm->execute ($values)) { + return $stm->rowCount(); + } else { + $info = $stm->errorInfo(); + Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); + return false; + } + } + public function markRead ($id, $is_read = true) { + $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id ' + . 'SET e.is_read = ?,' + . 'f.cache_nbUnreads=f.cache_nbUnreads' . ($is_read ? '-' : '+') . '1 ' + . 'WHERE e.id=?'; + $values = array ($is_read ? 1 : 0, $id); + $stm = $this->bd->prepare ($sql); + if ($stm && $stm->execute ($values)) { + return $stm->rowCount(); + } else { + $info = $stm->errorInfo(); + Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); + return false; + } + } + public function markReadEntries ($idMax = 0, $favorites = false) { + if ($idMax === 0) { + $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id ' + . 'SET e.is_read = 1, f.cache_nbUnreads=0 ' + . 'WHERE e.is_read = 0 AND '; + if ($favorites) { + $sql .= 'e.is_favorite = 1'; + } else { + $sql .= 'f.priority > 0'; + } + $stm = $this->bd->prepare ($sql); + if ($stm && $stm->execute ()) { + return $stm->rowCount(); + } else { + $info = $stm->errorInfo(); + Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); + return false; + } + } else { + $this->bd->beginTransaction (); + + $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id ' + . 'SET e.is_read = 1 ' + . 'WHERE e.is_read = 0 AND e.id <= ? AND '; + if ($favorites) { + $sql .= 'e.is_favorite = 1'; + } else { + $sql .= 'f.priority > 0'; + } + $values = array ($idMax); + $stm = $this->bd->prepare ($sql); + if (!($stm && $stm->execute ($values))) { + $info = $stm->errorInfo(); + Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); + $this->bd->rollBack (); + return false; + } + $affected = $stm->rowCount(); + + if ($affected > 0) { + $sql = 'UPDATE `' . $this->prefix . 'feed` f ' + . 'LEFT OUTER JOIN (' + . 'SELECT e.id_feed, ' + . 'COUNT(*) AS nbUnreads ' + . 'FROM `' . $this->prefix . 'entry` e ' + . 'WHERE e.is_read = 0 ' + . 'GROUP BY e.id_feed' + . ') x ON x.id_feed=f.id ' + . 'SET f.cache_nbUnreads=COALESCE(x.nbUnreads, 0)'; + $stm = $this->bd->prepare ($sql); + if (!($stm && $stm->execute ())) { + $info = $stm->errorInfo(); + Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); + $this->bd->rollBack (); + return false; + } + } + + $this->bd->commit (); + return $affected; + } + } + public function markReadCat ($id, $idMax = 0) { + if ($idMax === 0) { + $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id ' + . 'SET e.is_read = 1, f.cache_nbUnreads=0 ' + . 'WHERE f.category = ? AND e.is_read = 0'; + $values = array ($id); + $stm = $this->bd->prepare ($sql); + if ($stm && $stm->execute ($values)) { + return $stm->rowCount(); + } else { + $info = $stm->errorInfo(); + Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); + return false; + } + } else { + $this->bd->beginTransaction (); + + $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id ' + . 'SET e.is_read = 1 ' + . 'WHERE f.category = ? AND e.is_read = 0 AND e.id <= ?'; + $values = array ($id, $idMax); + $stm = $this->bd->prepare ($sql); + if (!($stm && $stm->execute ($values))) { + $info = $stm->errorInfo(); + Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); + $this->bd->rollBack (); + return false; + } + $affected = $stm->rowCount(); + + if ($affected > 0) { + $sql = 'UPDATE `' . $this->prefix . 'feed` f ' + . 'LEFT OUTER JOIN (' + . 'SELECT e.id_feed, ' + . 'COUNT(*) AS nbUnreads ' + . 'FROM `' . $this->prefix . 'entry` e ' + . 'WHERE e.is_read = 0 ' + . 'GROUP BY e.id_feed' + . ') x ON x.id_feed=f.id ' + . 'SET f.cache_nbUnreads=COALESCE(x.nbUnreads, 0) ' + . 'WHERE f.category = ?'; + $values = array ($id); + $stm = $this->bd->prepare ($sql); + if (!($stm && $stm->execute ($values))) { + $info = $stm->errorInfo(); + Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); + $this->bd->rollBack (); + return false; + } + } + + $this->bd->commit (); + return $affected; + } + } + public function markReadFeed ($id, $idMax = 0) { + if ($idMax === 0) { + $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id ' + . 'SET e.is_read = 1, f.cache_nbUnreads=0 ' + . 'WHERE f.id=? AND e.is_read = 0'; + $values = array ($id); + $stm = $this->bd->prepare ($sql); + if ($stm && $stm->execute ($values)) { + return $stm->rowCount(); + } else { + $info = $stm->errorInfo(); + Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); + return false; + } + } else { + $this->bd->beginTransaction (); + + $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id ' + . 'SET e.is_read = 1 ' + . 'WHERE f.id=? AND e.is_read = 0 AND e.id <= ?'; + $values = array ($id, $idMax); + $stm = $this->bd->prepare ($sql); + if (!($stm && $stm->execute ($values))) { + $info = $stm->errorInfo(); + Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); + $this->bd->rollBack (); + return false; + } + $affected = $stm->rowCount(); + + if ($affected > 0) { + $sql = 'UPDATE `' . $this->prefix . 'feed` f ' + . 'SET f.cache_nbUnreads=f.cache_nbUnreads-' . $affected + . ' WHERE f.id=?'; + $values = array ($id); + $stm = $this->bd->prepare ($sql); + if (!($stm && $stm->execute ($values))) { + $info = $stm->errorInfo(); + Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); + $this->bd->rollBack (); + return false; + } + } + + $this->bd->commit (); + return $affected; + } + } + + public function searchByGuid ($feed_id, $id) { + // un guid est unique pour un flux donné + $sql = 'SELECT id, guid, title, author, UNCOMPRESS(content_bin) AS content, link, date, is_read, is_favorite, id_feed, tags ' + . 'FROM `' . $this->prefix . 'entry` WHERE id_feed=? AND guid=?'; + $stm = $this->bd->prepare ($sql); + + $values = array ( + $feed_id, + $id + ); + + $stm->execute ($values); + $res = $stm->fetchAll (PDO::FETCH_ASSOC); + $entries = HelperEntry::daoToEntry ($res); + return isset ($entries[0]) ? $entries[0] : false; + } + + public function searchById ($id) { + $sql = 'SELECT id, guid, title, author, UNCOMPRESS(content_bin) AS content, link, date, is_read, is_favorite, id_feed, tags ' + . 'FROM `' . $this->prefix . 'entry` WHERE id=?'; + $stm = $this->bd->prepare ($sql); + + $values = array ($id); + + $stm->execute ($values); + $res = $stm->fetchAll (PDO::FETCH_ASSOC); + $entries = HelperEntry::daoToEntry ($res); + return isset ($entries[0]) ? $entries[0] : false; + } + + public function listWhere($type = 'a', $id = '', $state = 'all', $order = 'DESC', $limit = 1, $firstId = -1, $filter = '') { + $where = ''; + $values = array(); + switch ($type) { + case 'a': + $where .= 'priority > 0 '; + break; + case 's': + $where .= 'is_favorite = 1 '; + break; + case 'c': + $where .= 'category = ? '; + $values[] = intval($id); + break; + case 'f': + $where .= 'id_feed = ? '; + $values[] = intval($id); + break; + default: + throw new FreshRSS_EntriesGetter_Exception ('Bad type in Entry->listByType: [' . $type . ']!'); + } + switch ($state) { + case 'all': + break; + case 'not_read': + $where .= 'AND is_read = 0 '; + break; + case 'read': + $where .= 'AND is_read = 1 '; + break; + default: + throw new FreshRSS_EntriesGetter_Exception ('Bad state in Entry->listByType: [' . $state . ']!'); + } + switch ($order) { + case 'DESC': + case 'ASC': + break; + default: + throw new FreshRSS_EntriesGetter_Exception ('Bad order in Entry->listByType: [' . $order . ']!'); + } + if ($firstId > 0) { + $where .= 'AND e.id ' . ($order === 'DESC' ? '<=' : '>=') . $firstId . ' '; + } + $terms = array_unique(explode(' ', trim($filter))); + sort($terms); //Put #tags first + $having = ''; + foreach ($terms as $word) { + if (!empty($word)) { + if ($word[0] === '#' && isset($word[1])) { + $having .= 'AND tags LIKE ? '; + $values[] = '%' . $word .'%'; + } elseif (!empty($word)) { + $having .= 'AND (e.title LIKE ? OR content LIKE ?) '; + $values[] = '%' . $word .'%'; + $values[] = '%' . $word .'%'; + } + } + } + + $sql = 'SELECT e.id, e.guid, e.title, e.author, UNCOMPRESS(e.content_bin) AS content, e.link, e.date, e.is_read, e.is_favorite, e.id_feed, e.tags ' + . 'FROM `' . $this->prefix . 'entry` e ' + . 'INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id WHERE ' . $where + . (empty($having) ? '' : 'HAVING' . substr($having, 3)) + . 'ORDER BY e.id ' . $order; + + if ($limit > 0) { + $sql .= ' LIMIT ' . $limit; //TODO: See http://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/ + } + + $stm = $this->bd->prepare ($sql); + $stm->execute ($values); + + return HelperEntry::daoToEntry ($stm->fetchAll (PDO::FETCH_ASSOC)); + } + + public function listLastGuidsByFeed($id, $n) { + $sql = 'SELECT guid FROM `' . $this->prefix . 'entry` WHERE id_feed=? ORDER BY id DESC LIMIT ' . intval($n); + $stm = $this->bd->prepare ($sql); + $values = array ($id); + $stm->execute ($values); + return $stm->fetchAll (PDO::FETCH_COLUMN, 0); + } + + 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'; + $stm = $this->bd->prepare ($sql); + $stm->execute (); + $res = $stm->fetchAll (PDO::FETCH_COLUMN, 0); + $all = empty($res[0]) ? 0 : $res[0]; + $unread = empty($res[1]) ? 0 : $res[1]; + 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'; + if ($minPriority !== null) { + $sql = ' WHERE priority > ' . intval($minPriority); + } + $stm = $this->bd->prepare ($sql); + $stm->execute (); + $res = $stm->fetchAll (PDO::FETCH_COLUMN, 0); + 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'; + if ($minPriority !== null) { + $sql = ' AND priority > ' . intval($minPriority); + } + $stm = $this->bd->prepare ($sql); + $stm->execute (); + $res = $stm->fetchAll (PDO::FETCH_COLUMN, 0); + return $res[0]; + } + + public function countUnreadReadFavorites () { + $sql = 'SELECT COUNT(id) FROM `' . $this->prefix . 'entry` WHERE is_favorite=1' + . ' UNION SELECT COUNT(id) FROM `' . $this->prefix . 'entry` WHERE is_favorite=1 AND is_read = 0'; + $stm = $this->bd->prepare ($sql); + $stm->execute (); + $res = $stm->fetchAll (PDO::FETCH_COLUMN, 0); + $all = empty($res[0]) ? 0 : $res[0]; + $unread = empty($res[1]) ? 0 : $res[1]; + return array('all' => $all, 'unread' => $unread, 'read' => $all - $unread); + } + + public function optimizeTable() { + $sql = 'OPTIMIZE TABLE `' . $this->prefix . 'entry`'; + $stm = $this->bd->prepare ($sql); + $stm->execute (); + } +} + +class HelperEntry { + public static function daoToEntry ($listDAO) { + $list = array (); + + if (!is_array ($listDAO)) { + $listDAO = array ($listDAO); + } + + foreach ($listDAO as $key => $dao) { + $entry = new FreshRSS_Entry ( + $dao['id_feed'], + $dao['guid'], + $dao['title'], + $dao['author'], + $dao['content'], + $dao['link'], + $dao['date'], + $dao['is_read'], + $dao['is_favorite'], + $dao['tags'] + ); + if (isset ($dao['id'])) { + $entry->_id ($dao['id']); + } + $list[] = $entry; + } + + unset ($listDAO); + + return $list; + } +} diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php new file mode 100644 index 000000000..8f59b1c76 --- /dev/null +++ b/app/Models/FeedDAO.php @@ -0,0 +1,341 @@ +prefix . 'feed` (url, category, name, website, description, lastUpdate, priority, httpAuth, error, keep_history) VALUES(?, ?, ?, ?, ?, ?, 10, ?, 0, 0)'; + $stm = $this->bd->prepare ($sql); + + $values = array ( + substr($valuesTmp['url'], 0, 511), + $valuesTmp['category'], + substr($valuesTmp['name'], 0, 255), + substr($valuesTmp['website'], 0, 255), + substr($valuesTmp['description'], 0, 1023), + $valuesTmp['lastUpdate'], + base64_encode ($valuesTmp['httpAuth']), + ); + + if ($stm && $stm->execute ($values)) { + return $this->bd->lastInsertId(); + } else { + $info = $stm->errorInfo(); + Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); + return false; + } + } + + public function updateFeed ($id, $valuesTmp) { + $set = ''; + foreach ($valuesTmp as $key => $v) { + $set .= $key . '=?, '; + + if ($key == 'httpAuth') { + $valuesTmp[$key] = base64_encode ($v); + } + } + $set = substr ($set, 0, -2); + + $sql = 'UPDATE `' . $this->prefix . 'feed` SET ' . $set . ' WHERE id=?'; + $stm = $this->bd->prepare ($sql); + + foreach ($valuesTmp as $v) { + $values[] = $v; + } + $values[] = $id; + + if ($stm && $stm->execute ($values)) { + return $stm->rowCount(); + } else { + $info = $stm->errorInfo(); + Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); + return false; + } + } + + public function updateLastUpdate ($id, $inError = 0) { + $sql = 'UPDATE `' . $this->prefix . 'feed` f ' //2 sub-requests with FOREIGN KEY(e.id_feed), INDEX(e.is_read) faster than 1 request with GROUP BY or CASE + . 'SET f.cache_nbEntries=(SELECT COUNT(e1.id) FROM `' . $this->prefix . 'entry` e1 WHERE e1.id_feed=f.id),' + . 'f.cache_nbUnreads=(SELECT COUNT(e2.id) FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed=f.id AND e2.is_read=0),' + . 'lastUpdate=?, error=? ' + . 'WHERE f.id=?'; + + $stm = $this->bd->prepare ($sql); + + $values = array ( + time (), + $inError, + $id, + ); + + if ($stm && $stm->execute ($values)) { + return $stm->rowCount(); + } else { + $info = $stm->errorInfo(); + Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); + return false; + } + } + + public function changeCategory ($idOldCat, $idNewCat) { + $catDAO = new FreshRSS_CategoryDAO (); + $newCat = $catDAO->searchById ($idNewCat); + if (!$newCat) { + $newCat = $catDAO->getDefault (); + } + + $sql = 'UPDATE `' . $this->prefix . 'feed` SET category=? WHERE category=?'; + $stm = $this->bd->prepare ($sql); + + $values = array ( + $newCat->id (), + $idOldCat + ); + + if ($stm && $stm->execute ($values)) { + return $stm->rowCount(); + } else { + $info = $stm->errorInfo(); + Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); + return false; + } + } + + public function deleteFeed ($id) { + /*//For MYISAM (MySQL 5.5-) without FOREIGN KEY + $sql = 'DELETE FROM `' . $this->prefix . 'entry` WHERE id_feed=?'; + $stm = $this->bd->prepare ($sql); + $values = array ($id); + if (!($stm && $stm->execute ($values))) { + $info = $stm->errorInfo(); + Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); + return false; + }*/ + + $sql = 'DELETE FROM `' . $this->prefix . 'feed` WHERE id=?'; + $stm = $this->bd->prepare ($sql); + + $values = array ($id); + + if ($stm && $stm->execute ($values)) { + return $stm->rowCount(); + } else { + $info = $stm->errorInfo(); + Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); + return false; + } + } + public function deleteFeedByCategory ($id) { + /*//For MYISAM (MySQL 5.5-) without FOREIGN KEY + $sql = 'DELETE FROM `' . $this->prefix . 'entry` e ' + . 'INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id ' + . 'WHERE f.category=?'; + $stm = $this->bd->prepare ($sql); + $values = array ($id); + if (!($stm && $stm->execute ($values))) { + $info = $stm->errorInfo(); + Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); + return false; + }*/ + + $sql = 'DELETE FROM `' . $this->prefix . 'feed` WHERE category=?'; + $stm = $this->bd->prepare ($sql); + + $values = array ($id); + + if ($stm && $stm->execute ($values)) { + return $stm->rowCount(); + } else { + $info = $stm->errorInfo(); + Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); + return false; + } + } + + public function searchById ($id) { + $sql = 'SELECT * FROM `' . $this->prefix . 'feed` WHERE id=?'; + $stm = $this->bd->prepare ($sql); + + $values = array ($id); + + $stm->execute ($values); + $res = $stm->fetchAll (PDO::FETCH_ASSOC); + $feed = HelperFeed::daoToFeed ($res); + + if (isset ($feed[$id])) { + return $feed[$id]; + } else { + return false; + } + } + public function searchByUrl ($url) { + $sql = 'SELECT * FROM `' . $this->prefix . 'feed` WHERE url=?'; + $stm = $this->bd->prepare ($sql); + + $values = array ($url); + + $stm->execute ($values); + $res = $stm->fetchAll (PDO::FETCH_ASSOC); + $feed = current (HelperFeed::daoToFeed ($res)); + + if (isset ($feed)) { + return $feed; + } else { + return false; + } + } + + public function listFeeds () { + $sql = 'SELECT * FROM `' . $this->prefix . 'feed` ORDER BY name'; + $stm = $this->bd->prepare ($sql); + $stm->execute (); + + return HelperFeed::daoToFeed ($stm->fetchAll (PDO::FETCH_ASSOC)); + } + + public function listFeedsOrderUpdate () { + $sql = 'SELECT * FROM `' . $this->prefix . 'feed` ORDER BY lastUpdate'; + $stm = $this->bd->prepare ($sql); + $stm->execute (); + + return HelperFeed::daoToFeed ($stm->fetchAll (PDO::FETCH_ASSOC)); + } + + public function listByCategory ($cat) { + $sql = 'SELECT * FROM `' . $this->prefix . 'feed` WHERE category=? ORDER BY name'; + $stm = $this->bd->prepare ($sql); + + $values = array ($cat); + + $stm->execute ($values); + + return HelperFeed::daoToFeed ($stm->fetchAll (PDO::FETCH_ASSOC)); + } + + public function countEntries ($id) { + $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'entry` WHERE id_feed=?'; + $stm = $this->bd->prepare ($sql); + $values = array ($id); + $stm->execute ($values); + $res = $stm->fetchAll (PDO::FETCH_ASSOC); + + return $res[0]['count']; + } + public function countNotRead ($id) { + $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'entry` WHERE id_feed=? AND is_read=0'; + $stm = $this->bd->prepare ($sql); + $values = array ($id); + $stm->execute ($values); + $res = $stm->fetchAll (PDO::FETCH_ASSOC); + + return $res[0]['count']; + } + public function updateCachedValues () { //For one single feed, call updateLastUpdate($id) + $sql = 'UPDATE `' . $this->prefix . 'feed` f ' + . 'INNER JOIN (' + . 'SELECT e.id_feed, ' + . 'COUNT(CASE WHEN e.is_read = 0 THEN 1 END) AS nbUnreads, ' + . 'COUNT(e.id) AS nbEntries ' + . 'FROM `' . $this->prefix . 'entry` e ' + . 'GROUP BY e.id_feed' + . ') x ON x.id_feed=f.id ' + . 'SET f.cache_nbEntries=x.nbEntries, f.cache_nbUnreads=x.nbUnreads'; + $stm = $this->bd->prepare ($sql); + + $values = array ($feed_id); + + if ($stm && $stm->execute ($values)) { + return $stm->rowCount(); + } else { + $info = $stm->errorInfo(); + Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); + return false; + } + } + + public function truncate ($id) { + $sql = 'DELETE e.* FROM `' . $this->prefix . 'entry` e WHERE e.id_feed=?'; + $stm = $this->bd->prepare($sql); + $values = array($id); + $this->bd->beginTransaction (); + if (!($stm && $stm->execute ($values))) { + $info = $stm->errorInfo(); + Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); + $this->bd->rollBack (); + return false; + } + $affected = $stm->rowCount(); + + $sql = 'UPDATE `' . $this->prefix . 'feed` f ' + . 'SET f.cache_nbEntries=0, f.cache_nbUnreads=0 WHERE f.id=?'; + $values = array ($id); + $stm = $this->bd->prepare ($sql); + if (!($stm && $stm->execute ($values))) { + $info = $stm->errorInfo(); + Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); + $this->bd->rollBack (); + return false; + } + + $this->bd->commit (); + return $affected; + } + + public function cleanOldEntries ($id, $date_min, $keep = 15) { //Remember to call updateLastUpdate($id) just after + $sql = 'DELETE e.* FROM `' . $this->prefix . 'entry` e ' + . 'WHERE e.id_feed = :id_feed AND e.id <= :id_max AND e.is_favorite = 0 AND e.id NOT IN ' + . '(SELECT id FROM (SELECT e2.id FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed = :id_feed ORDER BY id DESC LIMIT :keep) keep)'; //Double select because of: MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery' + $stm = $this->bd->prepare ($sql); + + $id_max = intval($date_min) . '000000'; + + $stm->bindParam(':id_feed', $id, PDO::PARAM_INT); + $stm->bindParam(':id_max', $id_max, PDO::PARAM_INT); + $stm->bindParam(':keep', $keep, PDO::PARAM_INT); + + if ($stm && $stm->execute ()) { + return $stm->rowCount(); + } else { + $info = $stm->errorInfo(); + Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); + return false; + } + } +} + +class HelperFeed { + public static function daoToFeed ($listDAO, $catID = null) { + $list = array (); + + if (!is_array ($listDAO)) { + $listDAO = array ($listDAO); + } + + foreach ($listDAO as $key => $dao) { + if (!isset ($dao['name'])) { + continue; + } + if (isset ($dao['id'])) { + $key = $dao['id']; + } + + $myFeed = new FreshRSS_Feed (isset($dao['url']) ? $dao['url'] : '', false); + $myFeed->_category ($catID === null ? $dao['category'] : $catID); + $myFeed->_name ($dao['name']); + $myFeed->_website ($dao['website'], false); + $myFeed->_description (isset($dao['description']) ? $dao['description'] : ''); + $myFeed->_lastUpdate (isset($dao['lastUpdate']) ? $dao['lastUpdate'] : 0); + $myFeed->_priority ($dao['priority']); + $myFeed->_pathEntries (isset($dao['pathEntries']) ? $dao['pathEntries'] : ''); + $myFeed->_httpAuth (isset($dao['httpAuth']) ? base64_decode ($dao['httpAuth']) : ''); + $myFeed->_error ($dao['error']); + $myFeed->_keepHistory (isset($dao['keep_history']) ? $dao['keep_history'] : ''); + $myFeed->_nbNotRead ($dao['cache_nbUnreads']); + $myFeed->_nbEntries ($dao['cache_nbEntries']); + if (isset ($dao['id'])) { + $myFeed->_id ($dao['id']); + } + $list[$key] = $myFeed; + } + + return $list; + } +} diff --git a/app/Models/Log.php b/app/Models/Log.php new file mode 100644 index 000000000..d2794458b --- /dev/null +++ b/app/Models/Log.php @@ -0,0 +1,26 @@ +date; + } + public function level () { + return $this->level; + } + public function info () { + return $this->information; + } + public function _date ($date) { + $this->date = $date; + } + public function _level ($level) { + $this->level = $level; + } + public function _info ($information) { + $this->information = $information; + } +} diff --git a/app/Models/LogDAO.php b/app/Models/LogDAO.php new file mode 100644 index 000000000..bf043fd6d --- /dev/null +++ b/app/Models/LogDAO.php @@ -0,0 +1,20 @@ +readLine ()) !== false) { + if (preg_match ('/^\[([^\[]+)\] \[([^\[]+)\] --- (.*)$/', $line, $matches)) { + $myLog = new FreshRSS_Log (); + $myLog->_date ($matches[1]); + $myLog->_level ($matches[2]); + $myLog->_info ($matches[3]); + $logs[] = $myLog; + } + } + return $logs; + } +} diff --git a/app/Models/Themes.php b/app/Models/Themes.php new file mode 100644 index 000000000..a52812339 --- /dev/null +++ b/app/Models/Themes.php @@ -0,0 +1,88 @@ + '✚', + 'all' => '☰', + 'bookmark' => '★', + 'category' => '☷', + 'category-white' => '☷', + 'close' => '❌', + 'configure' => '⚙', + 'down' => '▽', + 'favorite' => '★', + 'help' => 'ⓘ', + 'link' => '↗', + 'login' => '🔒', + 'logout' => '🔓', + 'next' => '⏩', + 'non-starred' => '☆', + 'prev' => '⏪', + 'read' => '☑', + 'unread' => '☐', + 'refresh' => '🔃', //↻ + 'search' => '🔍', + 'share' => '♺', + 'starred' => '★', + 'tag' => '⚐', + 'up' => '△', + ); + if (!isset($alts[$name])) { + return ''; + } + + $url = $name . '.svg'; + $url = isset(self::$themeIcons[$url]) ? (self::$themeIconsUrl . $url) : + (self::$defaultIconsUrl . $url); + + return $urlOnly ? Minz_Url::display($url) : + '' . $alts[$name] . ''; + } +} -- cgit v1.2.3 From 7e64cda41548500c25825cca29bb7e0167249b83 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sun, 15 Dec 2013 04:07:12 +0100 Subject: Date minimum pour afficher les articles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implémente décision https://github.com/marienfressinaud/FreshRSS/issues/323 --- app/Models/EntryDAO.php | 7 +++++-- app/controllers/feedController.php | 6 +++--- app/controllers/indexController.php | 8 ++++++-- 3 files changed, 14 insertions(+), 7 deletions(-) (limited to 'app/Models') diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index 8c18150b6..b61b97624 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -259,7 +259,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { return isset ($entries[0]) ? $entries[0] : false; } - public function listWhere($type = 'a', $id = '', $state = 'all', $order = 'DESC', $limit = 1, $firstId = -1, $filter = '') { + public function listWhere($type = 'a', $id = '', $state = 'all', $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0) { $where = ''; $values = array(); switch ($type) { @@ -299,9 +299,12 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { default: throw new FreshRSS_EntriesGetter_Exception ('Bad order in Entry->listByType: [' . $order . ']!'); } - if ($firstId > 0) { + if ($firstId !== '') { $where .= 'AND e.id ' . ($order === 'DESC' ? '<=' : '>=') . $firstId . ' '; } + if ($date_min > 0) { + $where .= 'AND e.id >= ' . $date_min . '000000 '; + } $terms = array_unique(explode(' ', trim($filter))); sort($terms); //Put #tags first $having = ''; diff --git a/app/controllers/feedController.php b/app/controllers/feedController.php index e4014c326..a85877724 100755 --- a/app/controllers/feedController.php +++ b/app/controllers/feedController.php @@ -96,7 +96,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController { // on calcule la date des articles les plus anciens qu'on accepte $nb_month_old = $this->view->conf->oldEntries (); - $date_min = time () - (60 * 60 * 24 * 30 * $nb_month_old); + $date_min = time () - (3600 * 24 * 30 * $nb_month_old); $transactionStarted = true; $feedDAO->beginTransaction (); @@ -196,7 +196,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController { // on calcule la date des articles les plus anciens qu'on accepte $nb_month_old = $this->view->conf->oldEntries (); - $date_min = time () - (60 * 60 * 24 * 30 * $nb_month_old); + $date_min = time () - (3600 * 24 * 30 * $nb_month_old); $i = 0; $flux_update = 0; @@ -310,7 +310,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController { // on calcule la date des articles les plus anciens qu'on accepte $nb_month_old = $this->view->conf->oldEntries (); - $date_min = time () - (60 * 60 * 24 * 30 * $nb_month_old); + $date_min = time () - (3600 * 24 * 30 * $nb_month_old); // la variable $error permet de savoir si une erreur est survenue // Le but est de ne pas arrêter l'import même en cas d'erreur diff --git a/app/controllers/indexController.php b/app/controllers/indexController.php index 16a053ba3..92070590a 100755 --- a/app/controllers/indexController.php +++ b/app/controllers/indexController.php @@ -124,15 +124,19 @@ class FreshRSS_index_Controller extends Minz_ActionController { } } + // on calcule la date des articles les plus anciens qu'on affiche + $nb_month_old = $this->view->conf->oldEntries (); + $date_min = time () - (3600 * 24 * 30 * $nb_month_old); + try { - $entries = $this->entryDAO->listWhere($getType, $getId, $state, $order, $nb + 1, $first, $filter); + $entries = $this->entryDAO->listWhere($getType, $getId, $state, $order, $nb + 1, $first, $filter, $date_min); // Si on a récupéré aucun article "non lus" // on essaye de récupérer tous les articles if ($state === 'not_read' && empty($entries)) { //TODO: Remove in v0.8 Minz_Log::record ('Conflicting information about nbNotRead!', Minz_Log::DEBUG); $this->view->state = 'all'; - $entries = $this->entryDAO->listWhere($getType, $getId, 'all', $order, $nb, $first, $filter); + $entries = $this->entryDAO->listWhere($getType, $getId, 'all', $order, $nb, $first, $filter, $date_min); } if (count($entries) <= $nb) { -- cgit v1.2.3 From 74bceb2e2cdf0e3da5fb36990f3ee54e745e3d09 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sun, 15 Dec 2013 04:20:23 +0100 Subject: Date minimum : cas des favoris et de l'historique complet Suite de https://github.com/marienfressinaud/FreshRSS/issues/323 --- app/Models/EntryDAO.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/Models') diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index b61b97624..1bce6cbf2 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -303,7 +303,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { $where .= 'AND e.id ' . ($order === 'DESC' ? '<=' : '>=') . $firstId . ' '; } if ($date_min > 0) { - $where .= 'AND e.id >= ' . $date_min . '000000 '; + $where .= 'AND (e.id >= ' . $date_min . '000000 OR e.is_favorite = 1 OR f.keep_history = 1) '; } $terms = array_unique(explode(' ', trim($filter))); sort($terms); //Put #tags first -- cgit v1.2.3 From 4ee4f16ffe06e247d2cb79a2054ab5d5315d82b2 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sun, 15 Dec 2013 11:24:14 +0100 Subject: Problème de casse renommage répertoire MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Models/Category.php | 85 +++++++++++ app/Models/Days.php | 7 + app/Models/Entry.php | 193 +++++++++++++++++++++++++ app/Models/Feed.php | 319 ++++++++++++++++++++++++++++++++++++++++++ lib/Minz/ActionController.php | 42 ++++++ lib/Minz/Configuration.php | 262 ++++++++++++++++++++++++++++++++++ lib/Minz/Dispatcher.php | 138 ++++++++++++++++++ lib/Minz/Error.php | 94 +++++++++++++ lib/Minz/FrontController.php | 97 +++++++++++++ lib/Minz/Helper.php | 22 +++ lib/Minz/Model.php | 12 ++ lib/Minz/Paginator.php | 196 ++++++++++++++++++++++++++ lib/Minz/Request.php | 197 ++++++++++++++++++++++++++ lib/Minz/Response.php | 60 ++++++++ lib/Minz/Router.php | 209 +++++++++++++++++++++++++++ lib/Minz/Session.php | 78 +++++++++++ lib/Minz/Translate.php | 71 ++++++++++ lib/Minz/Url.php | 129 +++++++++++++++++ lib/Minz/View.php | 241 +++++++++++++++++++++++++++++++ 19 files changed, 2452 insertions(+) create mode 100644 app/Models/Category.php create mode 100644 app/Models/Days.php create mode 100644 app/Models/Entry.php create mode 100644 app/Models/Feed.php create mode 100644 lib/Minz/ActionController.php create mode 100644 lib/Minz/Configuration.php create mode 100644 lib/Minz/Dispatcher.php create mode 100644 lib/Minz/Error.php create mode 100644 lib/Minz/FrontController.php create mode 100644 lib/Minz/Helper.php create mode 100644 lib/Minz/Model.php create mode 100644 lib/Minz/Paginator.php create mode 100644 lib/Minz/Request.php create mode 100644 lib/Minz/Response.php create mode 100644 lib/Minz/Router.php create mode 100644 lib/Minz/Session.php create mode 100644 lib/Minz/Translate.php create mode 100644 lib/Minz/Url.php create mode 100644 lib/Minz/View.php (limited to 'app/Models') diff --git a/app/Models/Category.php b/app/Models/Category.php new file mode 100644 index 000000000..e70d1303f --- /dev/null +++ b/app/Models/Category.php @@ -0,0 +1,85 @@ +_name ($name); + $this->_color ($color); + if (isset ($feeds)) { + $this->_feeds ($feeds); + $this->nbFeed = 0; + $this->nbNotRead = 0; + foreach ($feeds as $feed) { + $this->nbFeed++; + $this->nbNotRead += $feed->nbNotRead (); + } + } + } + + public function id () { + return $this->id; + } + public function name () { + return $this->name; + } + public function color () { + return $this->color; + } + public function nbFeed () { + if ($this->nbFeed < 0) { + $catDAO = new FreshRSS_CategoryDAO (); + $this->nbFeed = $catDAO->countFeed ($this->id ()); + } + + return $this->nbFeed; + } + public function nbNotRead () { + if ($this->nbNotRead < 0) { + $catDAO = new FreshRSS_CategoryDAO (); + $this->nbNotRead = $catDAO->countNotRead ($this->id ()); + } + + return $this->nbNotRead; + } + public function feeds () { + if (is_null ($this->feeds)) { + $feedDAO = new FreshRSS_FeedDAO (); + $this->feeds = $feedDAO->listByCategory ($this->id ()); + $this->nbFeed = 0; + $this->nbNotRead = 0; + foreach ($this->feeds as $feed) { + $this->nbFeed++; + $this->nbNotRead += $feed->nbNotRead (); + } + } + + return $this->feeds; + } + + public function _id ($value) { + $this->id = $value; + } + public function _name ($value) { + $this->name = $value; + } + public function _color ($value) { + if (preg_match ('/^#([0-9a-f]{3}|[0-9a-f]{6})$/i', $value)) { + $this->color = $value; + } else { + $this->color = '#0062BE'; + } + } + public function _feeds ($values) { + if (!is_array ($values)) { + $values = array ($values); + } + + $this->feeds = $values; + } +} diff --git a/app/Models/Days.php b/app/Models/Days.php new file mode 100644 index 000000000..2d770c30b --- /dev/null +++ b/app/Models/Days.php @@ -0,0 +1,7 @@ +_guid ($guid); + $this->_title ($title); + $this->_author ($author); + $this->_content ($content); + $this->_link ($link); + $this->_date ($pubdate); + $this->_isRead ($is_read); + $this->_isFavorite ($is_favorite); + $this->_feed ($feed); + $this->_tags (preg_split('/[\s#]/', $tags)); + } + + public function id () { + return $this->id; + } + public function guid () { + return $this->guid; + } + public function title () { + return $this->title; + } + public function author () { + if (is_null ($this->author)) { + return ''; + } else { + return $this->author; + } + } + public function content () { + return $this->content; + } + public function link () { + return $this->link; + } + public function date ($raw = false) { + if ($raw) { + return $this->date; + } else { + return timestamptodate ($this->date); + } + } + public function dateAdded ($raw = false) { + $date = intval(substr($this->id, 0, -6)); + if ($raw) { + return $date; + } else { + return timestamptodate ($date); + } + } + public function isRead () { + return $this->is_read; + } + public function isFavorite () { + return $this->is_favorite; + } + public function feed ($object = false) { + if ($object) { + $feedDAO = new FreshRSS_FeedDAO (); + return $feedDAO->searchById ($this->feed); + } else { + return $this->feed; + } + } + public function tags ($inString = false) { + if ($inString) { + return empty ($this->tags) ? '' : '#' . implode(' #', $this->tags); + } else { + return $this->tags; + } + } + + public function _id ($value) { + $this->id = $value; + } + public function _guid ($value) { + $this->guid = $value; + } + public function _title ($value) { + $this->title = $value; + } + public function _author ($value) { + $this->author = $value; + } + public function _content ($value) { + $this->content = $value; + } + public function _link ($value) { + $this->link = $value; + } + public function _date ($value) { + if (ctype_digit ($value)) { + $this->date = intval ($value); + } else { + $this->date = time (); + } + } + public function _isRead ($value) { + $this->is_read = $value; + } + public function _isFavorite ($value) { + $this->is_favorite = $value; + } + public function _feed ($value) { + $this->feed = $value; + } + public function _tags ($value) { + if (!is_array ($value)) { + $value = array ($value); + } + + foreach ($value as $key => $t) { + if (!$t) { + unset ($value[$key]); + } + } + + $this->tags = $value; + } + + public function isDay ($day) { + $date = $this->dateAdded(true); + $today = @strtotime('today'); + $yesterday = $today - 86400; + + if ($day === FreshRSS_Days::TODAY && + $date >= $today && $date < $today + 86400) { + return true; + } elseif ($day === FreshRSS_Days::YESTERDAY && + $date >= $yesterday && $date < $yesterday + 86400) { + return true; + } elseif ($day === FreshRSS_Days::BEFORE_YESTERDAY && $date < $yesterday) { + return true; + } else { + return false; + } + } + + public function loadCompleteContent($pathEntries) { + // Gestion du contenu + // On cherche à récupérer les articles en entier... même si le flux ne le propose pas + if ($pathEntries) { + $entryDAO = new FreshRSS_EntryDAO(); + $entry = $entryDAO->searchByGuid($this->feed, $this->guid); + + if($entry) { + // l'article existe déjà en BDD, en se contente de recharger ce contenu + $this->content = $entry->content(); + } else { + try { + // l'article n'est pas en BDD, on va le chercher sur le site + $this->content = get_content_by_parsing( + $this->link(), $pathEntries + ); + } catch (Exception $e) { + // rien à faire, on garde l'ancien contenu (requête a échoué) + } + } + } + } + + public function toArray () { + return array ( + 'id' => $this->id (), + 'guid' => $this->guid (), + 'title' => $this->title (), + 'author' => $this->author (), + 'content' => $this->content (), + 'link' => $this->link (), + 'date' => $this->date (true), + 'is_read' => $this->isRead (), + 'is_favorite' => $this->isFavorite (), + 'id_feed' => $this->feed (), + 'tags' => $this->tags (true), + ); + } +} diff --git a/app/Models/Feed.php b/app/Models/Feed.php new file mode 100644 index 000000000..e63ac8c7a --- /dev/null +++ b/app/Models/Feed.php @@ -0,0 +1,319 @@ +_url ($url); + } else { + $this->url = $url; + } + } + + public function id () { + return $this->id; + } + public function url () { + return $this->url; + } + public function category () { + return $this->category; + } + public function entries () { + if (!is_null ($this->entries)) { + return $this->entries; + } else { + return array (); + } + } + public function name () { + return $this->name; + } + public function website () { + return $this->website; + } + public function description () { + return $this->description; + } + public function lastUpdate () { + return $this->lastUpdate; + } + public function priority () { + return $this->priority; + } + public function pathEntries () { + return $this->pathEntries; + } + public function httpAuth ($raw = true) { + if ($raw) { + return $this->httpAuth; + } else { + $pos_colon = strpos ($this->httpAuth, ':'); + $user = substr ($this->httpAuth, 0, $pos_colon); + $pass = substr ($this->httpAuth, $pos_colon + 1); + + return array ( + 'username' => $user, + 'password' => $pass + ); + } + } + public function inError () { + return $this->error; + } + public function keepHistory () { + return $this->keep_history; + } + public function nbEntries () { + if ($this->nbEntries < 0) { + $feedDAO = new FreshRSS_FeedDAO (); + $this->nbEntries = $feedDAO->countEntries ($this->id ()); + } + + return $this->nbEntries; + } + public function nbNotRead () { + if ($this->nbNotRead < 0) { + $feedDAO = new FreshRSS_FeedDAO (); + $this->nbNotRead = $feedDAO->countNotRead ($this->id ()); + } + + return $this->nbNotRead; + } + public function faviconPrepare() { + $file = DATA_PATH . '/favicons/' . $this->id () . '.txt'; + if (!file_exists ($file)) { + $t = $this->website; + if (empty($t)) { + $t = $this->url; + } + file_put_contents($file, $t); + } + } + public static function faviconDelete($id) { + $path = DATA_PATH . '/favicons/' . $id; + @unlink($path . '.ico'); + @unlink($path . '.txt'); + } + public function favicon () { + return Minz_Url::display ('/f.php?' . $this->id ()); + } + + public function _id ($value) { + $this->id = $value; + } + public function _url ($value, $validate=true) { + if ($validate) { + $value = checkUrl($value); + } + if (empty ($value)) { + throw new FreshRSS_BadUrl_Exception ($value); + } + $this->url = $value; + } + public function _category ($value) { + $this->category = $value; + } + public function _name ($value) { + if (is_null ($value)) { + $value = ''; + } + $this->name = $value; + } + public function _website ($value, $validate=true) { + if ($validate) { + $value = checkUrl($value); + } + if (empty ($value)) { + $value = ''; + } + $this->website = $value; + } + public function _description ($value) { + if (is_null ($value)) { + $value = ''; + } + $this->description = $value; + } + public function _lastUpdate ($value) { + $this->lastUpdate = $value; + } + public function _priority ($value) { + $this->priority = ctype_digit ($value) ? intval ($value) : 10; + } + public function _pathEntries ($value) { + $this->pathEntries = $value; + } + public function _httpAuth ($value) { + $this->httpAuth = $value; + } + public function _error ($value) { + if ($value) { + $value = true; + } else { + $value = false; + } + $this->error = $value; + } + public function _keepHistory ($value) { + if ($value) { + $value = true; + } else { + $value = false; + } + $this->keep_history = $value; + } + public function _nbNotRead ($value) { + $this->nbNotRead = ctype_digit ($value) ? intval ($value) : -1; + } + public function _nbEntries ($value) { + $this->nbEntries = ctype_digit ($value) ? intval ($value) : -1; + } + + public function load () { + if (!is_null ($this->url)) { + if (CACHE_PATH === false) { + throw new Minz_FileNotExistException ( + 'CACHE_PATH', + Minz_Exception::ERROR + ); + } else { + $feed = new SimplePie (); + $feed->set_useragent(Minz_Translate::t ('freshrss') . '/' . FRESHRSS_VERSION . ' (' . PHP_OS . '; ' . FRESHRSS_WEBSITE . ') ' . SIMPLEPIE_NAME . '/' . SIMPLEPIE_VERSION); + $url = htmlspecialchars_decode ($this->url, ENT_QUOTES); + if ($this->httpAuth != '') { + $url = preg_replace ('#((.+)://)(.+)#', '${1}' . $this->httpAuth . '@${3}', $url); + } + + $feed->set_feed_url ($url); + $feed->set_cache_location (CACHE_PATH); + $feed->set_cache_duration(1500); + $feed->strip_htmltags (array ( + 'base', 'blink', 'body', 'doctype', 'embed', + 'font', 'form', 'frame', 'frameset', 'html', + 'input', 'marquee', 'meta', 'noscript', + 'object', 'param', 'plaintext', 'script', 'style', + )); + $feed->strip_attributes(array_merge($feed->strip_attributes, array( + 'autoplay', 'onload', 'onunload', 'onclick', 'ondblclick', 'onmousedown', 'onmouseup', + 'onmouseover', 'onmousemove', 'onmouseout', 'onfocus', 'onblur', + 'onkeypress', 'onkeydown', 'onkeyup', 'onselect', 'onchange', 'seamless'))); + $feed->add_attributes(array( + 'img' => array('lazyload' => ''), //http://www.w3.org/TR/resource-priorities/ + 'audio' => array('preload' => 'none'), + 'iframe' => array('postpone' => '', 'sandbox' => 'allow-scripts allow-same-origin'), + 'video' => array('postpone' => '', 'preload' => 'none'), + )); + $feed->set_url_replacements(array( + 'a' => 'href', + 'area' => 'href', + 'audio' => 'src', + 'blockquote' => 'cite', + 'del' => 'cite', + 'form' => 'action', + 'iframe' => 'src', + 'img' => array( + 'longdesc', + 'src' + ), + 'input' => 'src', + 'ins' => 'cite', + 'q' => 'cite', + 'source' => 'src', + 'track' => 'src', + 'video' => array( + 'poster', + 'src', + ), + )); + $feed->init (); + + if ($feed->error ()) { + throw new FreshRSS_Feed_Exception ($feed->error . ' [' . $url . ']'); + } + + // si on a utilisé l'auto-discover, notre url va avoir changé + $subscribe_url = $feed->subscribe_url (); + if (!is_null ($subscribe_url) && $subscribe_url != $this->url) { + if ($this->httpAuth != '') { + // on enlève les id si authentification HTTP + $subscribe_url = preg_replace ('#((.+)://)((.+)@)(.+)#', '${1}${5}', $subscribe_url); + } + $this->_url ($subscribe_url); + } + + $title = $feed->get_title (); + $this->_name (!is_null ($title) ? $title : $this->url); + + $this->_website ($feed->get_link ()); + $this->_description ($feed->get_description ()); + + // et on charge les articles du flux + $this->loadEntries ($feed); + } + } + } + private function loadEntries ($feed) { + $entries = array (); + + foreach ($feed->get_items () as $item) { + $title = html_only_entity_decode (strip_tags ($item->get_title ())); + $author = $item->get_author (); + $link = $item->get_permalink (); + $date = @strtotime ($item->get_date ()); + + // gestion des tags (catégorie == tag) + $tags_tmp = $item->get_categories (); + $tags = array (); + if (!is_null ($tags_tmp)) { + foreach ($tags_tmp as $tag) { + $tags[] = html_only_entity_decode ($tag->get_label ()); + } + } + + $content = html_only_entity_decode ($item->get_content ()); + + $elinks = array(); + foreach ($item->get_enclosures() as $enclosure) { + $elink = $enclosure->get_link(); + if (array_key_exists($elink, $elinks)) continue; + $elinks[$elink] = '1'; + $mime = strtolower($enclosure->get_type()); + if (strpos($mime, 'image/') === 0) { + $content .= '
'; + } + } + + $entry = new FreshRSS_Entry ( + $this->id (), + $item->get_id (), + !is_null ($title) ? $title : '', + !is_null ($author) ? html_only_entity_decode ($author->name) : '', + !is_null ($content) ? $content : '', + !is_null ($link) ? $link : '', + $date ? $date : time () + ); + $entry->_tags ($tags); + // permet de récupérer le contenu des flux tronqués + $entry->loadCompleteContent($this->pathEntries()); + + $entries[] = $entry; + } + + $this->entries = $entries; + } +} diff --git a/lib/Minz/ActionController.php b/lib/Minz/ActionController.php new file mode 100644 index 000000000..409d9611f --- /dev/null +++ b/lib/Minz/ActionController.php @@ -0,0 +1,42 @@ + +*/ + +/** + * La classe ActionController représente le contrôleur de l'application + */ +class Minz_ActionController { + protected $router; + protected $view; + + /** + * Constructeur + * @param $controller nom du controller + * @param $action nom de l'action à lancer + */ + public function __construct ($router) { + $this->router = $router; + $this->view = new Minz_View (); + $this->view->attributeParams (); + } + + /** + * Getteur + */ + public function view () { + return $this->view; + } + + /** + * Méthodes à redéfinir (ou non) par héritage + * firstAction est la première méthode exécutée par le Dispatcher + * lastAction est la dernière + */ + public function init () { } + public function firstAction () { } + public function lastAction () { } +} + + diff --git a/lib/Minz/Configuration.php b/lib/Minz/Configuration.php new file mode 100644 index 000000000..9fc913964 --- /dev/null +++ b/lib/Minz/Configuration.php @@ -0,0 +1,262 @@ + +*/ + +/** + * La classe Configuration permet de gérer la configuration de l'application + */ +class Minz_Configuration { + const CONF_PATH_NAME = '/application.ini'; + + /** + * VERSION est la version actuelle de MINZ + */ + const VERSION = '1.3.1.freshrss'; // version spéciale FreshRSS + + /** + * valeurs possibles pour l'"environment" + * SILENT rend l'application muette (pas de log) + * PRODUCTION est recommandée pour une appli en production + * (log les erreurs critiques) + * DEVELOPMENT log toutes les erreurs + */ + const SILENT = 0; + const PRODUCTION = 1; + const DEVELOPMENT = 2; + + /** + * définition des variables de configuration + * $sel_application une chaîne de caractères aléatoires (obligatoire) + * $environment gère le niveau d'affichage pour log et erreurs + * $use_url_rewriting indique si on utilise l'url_rewriting + * $base_url le chemin de base pour accéder à l'application + * $title le nom de l'application + * $language la langue par défaut de l'application + * $cacheEnabled permet de savoir si le cache doit être activé + * $delayCache la limite de cache + * $db paramètres pour la base de données (tableau) + * - host le serveur de la base + * - user nom d'utilisateur + * - password mot de passe de l'utilisateur + * - base le nom de la base de données + */ + private static $sel_application = ''; + private static $environment = Minz_Configuration::PRODUCTION; + private static $base_url = ''; + private static $use_url_rewriting = false; + private static $title = ''; + private static $language = 'en'; + private static $cache_enabled = false; + private static $delay_cache = 3600; + private static $default_user = ''; + private static $current_user = ''; + + private static $db = array ( + 'host' => false, + 'user' => false, + 'password' => false, + 'base' => false + ); + + /* + * Getteurs + */ + public static function selApplication () { + return self::$sel_application; + } + public static function environment () { + return self::$environment; + } + public static function baseUrl () { + return self::$base_url; + } + public static function useUrlRewriting () { + return self::$use_url_rewriting; + } + public static function title () { + return stripslashes(self::$title); + } + public static function language () { + return self::$language; + } + public static function cacheEnabled () { + return self::$cache_enabled; + } + public static function delayCache () { + return self::$delay_cache; + } + public static function dataBase () { + return self::$db; + } + public static function defaultUser () { + return self::$default_user; + } + public static function currentUser () { + return self::$current_user; + } + + /** + * Initialise les variables de configuration + * @exception Minz_FileNotExistException si le CONF_PATH_NAME n'existe pas + * @exception Minz_BadConfigurationException si CONF_PATH_NAME mal formaté + */ + public static function init () { + try { + self::parseFile (); + self::setReporting (); + } catch (Minz_FileNotExistException $e) { + throw $e; + } catch (Minz_BadConfigurationException $e) { + throw $e; + } + } + + /** + * Parse un fichier de configuration de type ".ini" + * @exception Minz_FileNotExistException si le CONF_PATH_NAME n'existe pas + * @exception Minz_BadConfigurationException si CONF_PATH_NAME mal formaté + */ + private static function parseFile () { + if (!file_exists (DATA_PATH . self::CONF_PATH_NAME)) { + throw new Minz_FileNotExistException ( + DATA_PATH . self::CONF_PATH_NAME, + Minz_Exception::ERROR + ); + } + + $ini_array = parse_ini_file ( + DATA_PATH . self::CONF_PATH_NAME, + true + ); + + if (!$ini_array) { + throw new Minz_PermissionDeniedException ( + DATA_PATH . self::CONF_PATH_NAME, + Minz_Exception::ERROR + ); + } + + // [general] est obligatoire + if (!isset ($ini_array['general'])) { + throw new Minz_BadConfigurationException ( + '[general]', + Minz_Exception::ERROR + ); + } + $general = $ini_array['general']; + + + // sel_application est obligatoire + if (!isset ($general['sel_application'])) { + throw new Minz_BadConfigurationException ( + 'sel_application', + Minz_Exception::ERROR + ); + } + self::$sel_application = $general['sel_application']; + + if (isset ($general['environment'])) { + switch ($general['environment']) { + case 'silent': + self::$environment = Minz_Configuration::SILENT; + break; + case 'development': + self::$environment = Minz_Configuration::DEVELOPMENT; + break; + case 'production': + self::$environment = Minz_Configuration::PRODUCTION; + break; + default: + throw new Minz_BadConfigurationException ( + 'environment', + Minz_Exception::ERROR + ); + } + + } + if (isset ($general['base_url'])) { + self::$base_url = $general['base_url']; + } + if (isset ($general['use_url_rewriting'])) { + self::$use_url_rewriting = $general['use_url_rewriting']; + } + + if (isset ($general['title'])) { + self::$title = $general['title']; + } + if (isset ($general['language'])) { + self::$language = $general['language']; + } + if (isset ($general['cache_enabled'])) { + self::$cache_enabled = $general['cache_enabled']; + if (CACHE_PATH === false && self::$cache_enabled) { + throw new FileNotExistException ( + 'CACHE_PATH', + Minz_Exception::ERROR + ); + } + } + if (isset ($general['delay_cache'])) { + self::$delay_cache = $general['delay_cache']; + } + if (isset ($general['default_user'])) { + self::$default_user = $general['default_user']; + self::$current_user = self::$default_user; + } + + // Base de données + $db = false; + if (isset ($ini_array['db'])) { + $db = $ini_array['db']; + } + if ($db) { + if (!isset ($db['host'])) { + throw new Minz_BadConfigurationException ( + 'host', + Minz_Exception::ERROR + ); + } + if (!isset ($db['user'])) { + throw new Minz_BadConfigurationException ( + 'user', + Minz_Exception::ERROR + ); + } + if (!isset ($db['password'])) { + throw new Minz_BadConfigurationException ( + 'password', + Minz_Exception::ERROR + ); + } + if (!isset ($db['base'])) { + throw new Minz_BadConfigurationException ( + 'base', + Minz_Exception::ERROR + ); + } + + self::$db['type'] = isset ($db['type']) ? $db['type'] : 'mysql'; + self::$db['host'] = $db['host']; + self::$db['user'] = $db['user']; + self::$db['password'] = $db['password']; + self::$db['base'] = $db['base']; + self::$db['prefix'] = isset ($db['prefix']) ? $db['prefix'] : ''; + } + } + + private static function setReporting () { + if (self::environment () == self::DEVELOPMENT) { + error_reporting (E_ALL); + ini_set ('display_errors','On'); + ini_set('log_errors', 'On'); + } elseif (self::environment () == self::PRODUCTION) { + error_reporting(E_ALL); + ini_set('display_errors','Off'); + ini_set('log_errors', 'On'); + } else { + error_reporting(0); + } + } +} diff --git a/lib/Minz/Dispatcher.php b/lib/Minz/Dispatcher.php new file mode 100644 index 000000000..2898b5f00 --- /dev/null +++ b/lib/Minz/Dispatcher.php @@ -0,0 +1,138 @@ + +*/ + +/** + * Le Dispatcher s'occupe d'initialiser le Controller et d'executer l'action + * déterminée dans la Request + * C'est un singleton + */ +class Minz_Dispatcher { + const CONTROLLERS_PATH_NAME = '/Controllers'; + + /* singleton */ + private static $instance = null; + + private $router; + private $controller; + + /** + * Récupère l'instance du Dispatcher + */ + public static function getInstance ($router) { + if (is_null (self::$instance)) { + self::$instance = new Minz_Dispatcher ($router); + } + return self::$instance; + } + + /** + * Constructeur + */ + private function __construct ($router) { + $this->router = $router; + } + + /** + * Lance le controller indiqué dans Request + * Remplit le body de Response à partir de la Vue + * @exception Minz_Exception + */ + public function run () { + $cache = new Minz_Cache(); + // Le ob_start est dupliqué : sans ça il y a un bug sous Firefox + // ici on l'appelle avec 'ob_gzhandler', après sans. + // Vraisemblablement la compression fonctionne mais c'est sale + // J'ignore les effets de bord :( + ob_start ('ob_gzhandler'); + + if (Minz_Cache::isEnabled () && !$cache->expired ()) { + ob_start (); + $cache->render (); + $text = ob_get_clean(); + } else { + while (Minz_Request::$reseted) { + Minz_Request::$reseted = false; + + try { + $this->createController ('FreshRSS_' . Minz_Request::controllerName () . '_Controller'); + $this->controller->init (); + $this->controller->firstAction (); + $this->launchAction ( + Minz_Request::actionName () + . 'Action' + ); + $this->controller->lastAction (); + + if (!Minz_Request::$reseted) { + ob_start (); + $this->controller->view ()->build (); + $text = ob_get_clean(); + } + } catch (Minz_Exception $e) { + throw $e; + } + } + + if (Minz_Cache::isEnabled ()) { + $cache->cache ($text); + } + } + + Minz_Response::setBody ($text); + } + + /** + * Instancie le Controller + * @param $controller_name le nom du controller à instancier + * @exception ControllerNotExistException le controller n'existe pas + * @exception ControllerNotActionControllerException controller n'est + * > pas une instance de ActionController + */ + private function createController ($controller_name) { + $filename = APP_PATH . self::CONTROLLERS_PATH_NAME . '/' + . $controller_name . '.php'; + + if (!class_exists ($controller_name)) { + throw new Minz_ControllerNotExistException ( + $controller_name, + Minz_Exception::ERROR + ); + } + $this->controller = new $controller_name ($this->router); + + if (! ($this->controller instanceof Minz_ActionController)) { + throw new Minz_ControllerNotActionControllerException ( + $controller_name, + Minz_Exception::ERROR + ); + } + } + + /** + * Lance l'action sur le controller du dispatcher + * @param $action_name le nom de l'action + * @exception ActionException si on ne peut pas exécuter l'action sur + * le controller + */ + private function launchAction ($action_name) { + if (!Minz_Request::$reseted) { + if (!is_callable (array ( + $this->controller, + $action_name + ))) { + throw new Minz_ActionException ( + get_class ($this->controller), + $action_name, + Minz_Exception::ERROR + ); + } + call_user_func (array ( + $this->controller, + $action_name + )); + } + } +} diff --git a/lib/Minz/Error.php b/lib/Minz/Error.php new file mode 100644 index 000000000..1ad0d313c --- /dev/null +++ b/lib/Minz/Error.php @@ -0,0 +1,94 @@ + +*/ + +/** + * La classe Error permet de lancer des erreurs HTTP + */ +class Minz_Error { + public function __construct () { } + + /** + * Permet de lancer une erreur + * @param $code le type de l'erreur, par défaut 404 (page not found) + * @param $logs logs d'erreurs découpés de la forme + * > $logs['error'] + * > $logs['warning'] + * > $logs['notice'] + * @param $redirect indique s'il faut forcer la redirection (les logs ne seront pas transmis) + */ + public static function error ($code = 404, $logs = array (), $redirect = false) { + $logs = self::processLogs ($logs); + $error_filename = APP_PATH . '/Controllers/ErrorController.php'; + + if (file_exists ($error_filename)) { + $params = array ( + 'code' => $code, + 'logs' => $logs + ); + + Minz_Response::setHeader ($code); + if ($redirect) { + Minz_Request::forward (array ( + 'c' => 'error' + ), true); + } else { + Minz_Request::forward (array ( + 'c' => 'error', + 'params' => $params + ), false); + } + } else { + $text = '

An error occured

'."\n"; + + if (!empty ($logs)) { + $text .= '
    '."\n"; + foreach ($logs as $log) { + $text .= '
  • ' . $log . '
  • '."\n"; + } + $text .= '
'."\n"; + } + + Minz_Response::setHeader ($code); + Minz_Response::setBody ($text); + Minz_Response::send (); + exit (); + } + } + + /** + * Permet de retourner les logs de façon à n'avoir que + * ceux que l'on veut réellement + * @param $logs les logs rangés par catégories (error, warning, notice) + * @return la liste des logs, sans catégorie, + * > en fonction de l'environment + */ + private static function processLogs ($logs) { + $env = Minz_Configuration::environment (); + $logs_ok = array (); + $error = array (); + $warning = array (); + $notice = array (); + + if (isset ($logs['error'])) { + $error = $logs['error']; + } + if (isset ($logs['warning'])) { + $warning = $logs['warning']; + } + if (isset ($logs['notice'])) { + $notice = $logs['notice']; + } + + if ($env == Minz_Configuration::PRODUCTION) { + $logs_ok = $error; + } + if ($env == Minz_Configuration::DEVELOPMENT) { + $logs_ok = array_merge ($error, $warning, $notice); + } + + return $logs_ok; + } +} diff --git a/lib/Minz/FrontController.php b/lib/Minz/FrontController.php new file mode 100644 index 000000000..eb9835fe5 --- /dev/null +++ b/lib/Minz/FrontController.php @@ -0,0 +1,97 @@ +. +# +# ***** END LICENSE BLOCK ***** + +/** + * La classe FrontController est le Dispatcher du framework, elle lance l'application + * Elle est appelée en général dans le fichier index.php à la racine du serveur + */ +class Minz_FrontController { + protected $dispatcher; + protected $router; + + /** + * Constructeur + * Initialise le router et le dispatcher + */ + public function __construct () { + if (LOG_PATH === false) { + $this->killApp ('Path doesn\'t exist : LOG_PATH'); + } + + try { + Minz_Configuration::init (); + + Minz_Request::init (); + + $this->router = new Minz_Router (); + $this->router->init (); + } catch (Minz_RouteNotFoundException $e) { + Minz_Log::record ($e->getMessage (), Minz_Log::ERROR); + Minz_Error::error ( + 404, + array ('error' => array ($e->getMessage ())) + ); + } catch (Minz_Exception $e) { + Minz_Log::record ($e->getMessage (), Minz_Log::ERROR); + $this->killApp ($e->getMessage ()); + } + + $this->dispatcher = Minz_Dispatcher::getInstance ($this->router); + } + + /** + * Démarre l'application (lance le dispatcher et renvoie la réponse + */ + public function run () { + try { + $this->dispatcher->run (); + Minz_Response::send (); + } catch (Minz_Exception $e) { + try { + Minz_Log::record ($e->getMessage (), Minz_Log::ERROR); + } catch (Minz_PermissionDeniedException $e) { + $this->killApp ($e->getMessage ()); + } + + if ($e instanceof Minz_FileNotExistException || + $e instanceof Minz_ControllerNotExistException || + $e instanceof Minz_ControllerNotActionControllerException || + $e instanceof Minz_ActionException) { + Minz_Error::error ( + 404, + array ('error' => array ($e->getMessage ())), + true + ); + } else { + $this->killApp (); + } + } + } + + /** + * Permet d'arrêter le programme en urgence + */ + private function killApp ($txt = '') { + if ($txt == '') { + $txt = 'See logs files'; + } + exit ('### Application problem ###
'."\n".$txt); + } +} diff --git a/lib/Minz/Helper.php b/lib/Minz/Helper.php new file mode 100644 index 000000000..b058211d3 --- /dev/null +++ b/lib/Minz/Helper.php @@ -0,0 +1,22 @@ + +*/ + +/** + * La classe Helper représente une aide pour des tâches récurrentes + */ +class Minz_Helper { + /** + * Annule les effets des magic_quotes pour une variable donnée + * @param $var variable à traiter (tableau ou simple variable) + */ + public static function stripslashes_r ($var) { + if (is_array ($var)){ + return array_map (array ('Helper', 'stripslashes_r'), $var); + } else { + return stripslashes($var); + } + } +} diff --git a/lib/Minz/Model.php b/lib/Minz/Model.php new file mode 100644 index 000000000..adbaba942 --- /dev/null +++ b/lib/Minz/Model.php @@ -0,0 +1,12 @@ + +*/ + +/** + * La classe Model représente un modèle de l'application (représentation MVC) + */ +class Minz_Model { + +} diff --git a/lib/Minz/Paginator.php b/lib/Minz/Paginator.php new file mode 100644 index 000000000..5858e76a5 --- /dev/null +++ b/lib/Minz/Paginator.php @@ -0,0 +1,196 @@ + +*/ + +/** + * La classe Paginator permet de gérer la pagination de l'application facilement + */ +class Minz_Paginator { + /** + * $items tableau des éléments à afficher/gérer + */ + private $items = array (); + + /** + * $nbItemsPerPage le nombre d'éléments par page + */ + private $nbItemsPerPage = 10; + + /** + * $currentPage page actuelle à gérer + */ + private $currentPage = 1; + + /** + * $nbPage le nombre de pages de pagination + */ + private $nbPage = 1; + + /** + * $nbItems le nombre d'éléments + */ + private $nbItems = 0; + + /** + * Constructeur + * @param $items les éléments à gérer + */ + public function __construct ($items) { + $this->_items ($items); + $this->_nbItems (count ($this->items (true))); + $this->_nbItemsPerPage ($this->nbItemsPerPage); + $this->_currentPage ($this->currentPage); + } + + /** + * Permet d'afficher la pagination + * @param $view nom du fichier de vue situé dans /app/views/helpers/ + * @param $getteur variable de type $_GET[] permettant de retrouver la page + */ + public function render ($view, $getteur) { + $view = APP_PATH . '/views/helpers/'.$view; + + if (file_exists ($view)) { + include ($view); + } + } + + /** + * Permet de retrouver la page d'un élément donné + * @param $item l'élément à retrouver + * @return la page à laquelle se trouve l'élément (false si non trouvé) + */ + public function pageByItem ($item) { + $page = false; + $i = 0; + + do { + if ($item == $this->items[$i]) { + $page = ceil (($i + 1) / $this->nbItemsPerPage); + } + + $i++; + } while (!$page && $i < $this->nbItems ()); + + return $page; + } + + /** + * Permet de retrouver la position d'un élément donné (à partir de 0) + * @param $item l'élément à retrouver + * @return la position à laquelle se trouve l'élément (false si non trouvé) + */ + public function positionByItem ($item) { + $find = false; + $i = 0; + + do { + if ($item == $this->items[$i]) { + $find = true; + } else { + $i++; + } + } while (!$find && $i < $this->nbItems ()); + + return $i; + } + + /** + * Permet de récupérer un item par sa position + * @param $pos la position de l'élément + * @return l'item situé à $pos (dernier item si $pos<0, 1er si $pos>=count($items)) + */ + public function itemByPosition ($pos) { + if ($pos < 0) { + $pos = $this->nbItems () - 1; + } + if ($pos >= count($this->items)) { + $pos = 0; + } + + return $this->items[$pos]; + } + + /** + * GETTEURS + */ + /** + * @param $all si à true, retourne tous les éléments sans prendre en compte la pagination + */ + public function items ($all = false) { + $array = array (); + $nbItems = $this->nbItems (); + + if ($nbItems <= $this->nbItemsPerPage || $all) { + $array = $this->items; + } else { + $begin = ($this->currentPage - 1) * $this->nbItemsPerPage; + $counter = 0; + $i = 0; + + foreach ($this->items as $key => $item) { + if ($i >= $begin) { + $array[$key] = $item; + $counter++; + } + if ($counter >= $this->nbItemsPerPage) { + break; + } + $i++; + } + } + + return $array; + } + public function nbItemsPerPage () { + return $this->nbItemsPerPage; + } + public function currentPage () { + return $this->currentPage; + } + public function nbPage () { + return $this->nbPage; + } + public function nbItems () { + return $this->nbItems; + } + + /** + * SETTEURS + */ + public function _items ($items) { + if (is_array ($items)) { + $this->items = $items; + } + + $this->_nbPage (); + } + public function _nbItemsPerPage ($nbItemsPerPage) { + if ($nbItemsPerPage > $this->nbItems ()) { + $nbItemsPerPage = $this->nbItems (); + } + if ($nbItemsPerPage < 0) { + $nbItemsPerPage = 0; + } + + $this->nbItemsPerPage = $nbItemsPerPage; + $this->_nbPage (); + } + public function _currentPage ($page) { + if($page < 1 || ($page > $this->nbPage && $this->nbPage > 0)) { + throw new CurrentPagePaginationException ($page); + } + + $this->currentPage = $page; + } + private function _nbPage () { + if ($this->nbItemsPerPage > 0) { + $this->nbPage = ceil ($this->nbItems () / $this->nbItemsPerPage); + } + } + public function _nbItems ($value) { + $this->nbItems = $value; + } +} diff --git a/lib/Minz/Request.php b/lib/Minz/Request.php new file mode 100644 index 000000000..c8ffa4a42 --- /dev/null +++ b/lib/Minz/Request.php @@ -0,0 +1,197 @@ + +*/ + +/** + * Request représente la requête http + */ +class Minz_Request { + private static $controller_name = ''; + private static $action_name = ''; + private static $params = array (); + + private static $default_controller_name = 'index'; + private static $default_action_name = 'index'; + + public static $reseted = true; + + /** + * Getteurs + */ + public static function controllerName () { + return self::$controller_name; + } + public static function actionName () { + return self::$action_name; + } + public static function params () { + return self::$params; + } + static function htmlspecialchars_utf8 ($p) { + return htmlspecialchars($p, ENT_QUOTES, 'UTF-8'); + } + public static function param ($key, $default = false, $specialchars = false) { + if (isset (self::$params[$key])) { + $p = self::$params[$key]; + if(is_object($p) || $specialchars) { + return $p; + } elseif(is_array($p)) { + return array_map('self::htmlspecialchars_utf8', $p); + } else { + return self::htmlspecialchars_utf8($p); + } + } else { + return $default; + } + } + public static function defaultControllerName () { + return self::$default_controller_name; + } + public static function defaultActionName () { + return self::$default_action_name; + } + + /** + * Setteurs + */ + public static function _controllerName ($controller_name) { + self::$controller_name = $controller_name; + } + public static function _actionName ($action_name) { + self::$action_name = $action_name; + } + public static function _params ($params) { + if (!is_array($params)) { + $params = array ($params); + } + + self::$params = $params; + } + public static function _param ($key, $value = false) { + if ($value === false) { + unset (self::$params[$key]); + } else { + self::$params[$key] = $value; + } + } + + /** + * Initialise la Request + */ + public static function init () { + self::magicQuotesOff (); + } + + /** + * Retourn le nom de domaine du site + */ + public static function getDomainName () { + return $_SERVER['HTTP_HOST']; + } + + /** + * Détermine la base de l'url + * @return la base de l'url + */ + public static function getBaseUrl () { + return Minz_Configuration::baseUrl (); + } + + /** + * Récupère l'URI de la requête + * @return l'URI + */ + public static function getURI () { + if (isset ($_SERVER['REQUEST_URI'])) { + $base_url = self::getBaseUrl (); + $uri = $_SERVER['REQUEST_URI']; + + $len_base_url = strlen ($base_url); + $real_uri = substr ($uri, $len_base_url); + } else { + $real_uri = ''; + } + + return $real_uri; + } + + /** + * Relance une requête + * @param $url l'url vers laquelle est relancée la requête + * @param $redirect si vrai, force la redirection http + * > sinon, le dispatcher recharge en interne + */ + public static function forward ($url = array (), $redirect = false) { + $url = Minz_Url::checkUrl ($url); + + if ($redirect) { + header ('Location: ' . Minz_Url::display ($url, 'php')); + exit (); + } else { + self::$reseted = true; + + self::_controllerName ($url['c']); + self::_actionName ($url['a']); + self::_params (array_merge ( + self::$params, + $url['params'] + )); + } + } + + /** + * Permet de récupérer une variable de type $_GET + * @param $param nom de la variable + * @param $default valeur par défaut à attribuer à la variable + * @return $_GET[$param] + * $_GET si $param = false + * $default si $_GET[$param] n'existe pas + */ + public static function fetchGET ($param = false, $default = false) { + if ($param === false) { + return $_GET; + } elseif (isset ($_GET[$param])) { + return $_GET[$param]; + } else { + return $default; + } + } + + /** + * Permet de récupérer une variable de type $_POST + * @param $param nom de la variable + * @param $default valeur par défaut à attribuer à la variable + * @return $_POST[$param] + * $_POST si $param = false + * $default si $_POST[$param] n'existe pas + */ + public static function fetchPOST ($param = false, $default = false) { + if ($param === false) { + return $_POST; + } elseif (isset ($_POST[$param])) { + return $_POST[$param]; + } else { + return $default; + } + } + + /** + * Méthode désactivant les magic_quotes pour les variables + * $_GET + * $_POST + * $_COOKIE + */ + private static function magicQuotesOff () { + if (get_magic_quotes_gpc ()) { + $_GET = Minz_Helper::stripslashes_r ($_GET); + $_POST = Minz_Helper::stripslashes_r ($_POST); + $_COOKIE = Minz_Helper::stripslashes_r ($_COOKIE); + } + } + + public static function isPost () { + return !empty ($_POST) || !empty ($_FILES); + } +} diff --git a/lib/Minz/Response.php b/lib/Minz/Response.php new file mode 100644 index 000000000..f8ea3d946 --- /dev/null +++ b/lib/Minz/Response.php @@ -0,0 +1,60 @@ + +*/ + +/** + * Response représente la requête http renvoyée à l'utilisateur + */ +class Minz_Response { + private static $header = 'HTTP/1.0 200 OK'; + private static $body = ''; + + /** + * Mets à jour le body de la Response + * @param $text le texte à incorporer dans le body + */ + public static function setBody ($text) { + self::$body = $text; + } + + /** + * Mets à jour le header de la Response + * @param $code le code HTTP, valeurs possibles + * - 200 (OK) + * - 403 (Forbidden) + * - 404 (Forbidden) + * - 500 (Forbidden) -> par défaut si $code erroné + * - 503 (Forbidden) + */ + public static function setHeader ($code) { + switch ($code) { + case 200 : + self::$header = 'HTTP/1.0 200 OK'; + break; + case 403 : + self::$header = 'HTTP/1.0 403 Forbidden'; + break; + case 404 : + self::$header = 'HTTP/1.0 404 Not Found'; + break; + case 500 : + self::$header = 'HTTP/1.0 500 Internal Server Error'; + break; + case 503 : + self::$header = 'HTTP/1.0 503 Service Unavailable'; + break; + default : + self::$header = 'HTTP/1.0 500 Internal Server Error'; + } + } + + /** + * Envoie la Response à l'utilisateur + */ + public static function send () { + header (self::$header); + echo self::$body; + } +} diff --git a/lib/Minz/Router.php b/lib/Minz/Router.php new file mode 100644 index 000000000..1ccd72597 --- /dev/null +++ b/lib/Minz/Router.php @@ -0,0 +1,209 @@ + +*/ + +/** + * La classe Router gère le routage de l'application + * Les routes sont définies dans APP_PATH.'/configuration/routes.php' + */ +class Minz_Router { + const ROUTES_PATH_NAME = '/configuration/routes.php'; + + private $routes = array (); + + /** + * Constructeur + * @exception FileNotExistException si ROUTES_PATH_NAME n'existe pas + * et que l'on utilise l'url rewriting + */ + public function __construct () { + if (Minz_Configuration::useUrlRewriting ()) { + if (file_exists (APP_PATH . self::ROUTES_PATH_NAME)) { + $routes = include ( + APP_PATH . self::ROUTES_PATH_NAME + ); + + if (!is_array ($routes)) { + $routes = array (); + } + + $this->routes = array_map ( + array ('Url', 'checkUrl'), + $routes + ); + } else { + throw new Minz_FileNotExistException ( + self::ROUTES_PATH_NAME, + Minz_Exception::ERROR + ); + } + } + } + + /** + * Initialise le Router en déterminant le couple Controller / Action + * Mets à jour la Request + * @exception RouteNotFoundException si l'uri n'est pas présente dans + * > la table de routage + */ + public function init () { + $url = array (); + + if (Minz_Configuration::useUrlRewriting ()) { + try { + $url = $this->buildWithRewriting (); + } catch (Minz_RouteNotFoundException $e) { + throw $e; + } + } else { + $url = $this->buildWithoutRewriting (); + } + + $url['params'] = array_merge ( + $url['params'], + Minz_Request::fetchPOST () + ); + + Minz_Request::forward ($url); + } + + /** + * Retourne un tableau représentant l'url passée par la barre d'adresses + * Ne se base PAS sur la table de routage + * @return tableau représentant l'url + */ + public function buildWithoutRewriting () { + $url = array (); + + $url['c'] = Minz_Request::fetchGET ( + 'c', + Minz_Request::defaultControllerName () + ); + $url['a'] = Minz_Request::fetchGET ( + 'a', + Minz_Request::defaultActionName () + ); + $url['params'] = Minz_Request::fetchGET (); + + // post-traitement + unset ($url['params']['c']); + unset ($url['params']['a']); + + return $url; + } + + /** + * Retourne un tableau représentant l'url passée par la barre d'adresses + * Se base sur la table de routage + * @return tableau représentant l'url + * @exception RouteNotFoundException si l'uri n'est pas présente dans + * > la table de routage + */ + public function buildWithRewriting () { + $url = array (); + $uri = Minz_Request::getURI (); + $find = false; + + foreach ($this->routes as $route) { + $regex = '*^' . $route['route'] . '$*'; + if (preg_match ($regex, $uri, $matches)) { + $url['c'] = $route['controller']; + $url['a'] = $route['action']; + $url['params'] = $this->getParams ( + $route['params'], + $matches + ); + $find = true; + break; + } + } + + if (!$find && $uri != '/') { + throw new Minz_RouteNotFoundException ( + $uri, + Minz_Exception::ERROR + ); + } + + // post-traitement + $url = Minz_Url::checkUrl ($url); + + return $url; + } + + /** + * Retourne l'uri d'une url en se basant sur la table de routage + * @param l'url sous forme de tableau + * @return l'uri formatée (string) selon une route trouvée + */ + public function printUriRewrited ($url) { + $route = $this->searchRoute ($url); + + if ($route !== false) { + return $this->replaceParams ($route, $url['params']); + } + + return ''; + } + + /** + * Recherche la route correspondante à une url + * @param l'url sous forme de tableau + * @return la route telle que spécifiée dans la table de routage, + * false si pas trouvée + */ + public function searchRoute ($url) { + foreach ($this->routes as $route) { + if ($route['controller'] == $url['c'] + && $route['action'] == $url['a']) { + // calcule la différence des tableaux de params + $params = array_flip ($route['params']); + $difference_params = array_diff_key ( + $params, + $url['params'] + ); + + // vérifie que pas de différence + // et le cas où $params est vide et pas $url['params'] + if (empty ($difference_params) + && (!empty ($params) || empty ($url['params']))) { + return $route; + } + } + } + + return false; + } + + /** + * Récupère un tableau dont + * - les clés sont définies dans $params_route + * - les valeurs sont situées dans $matches + * Le tableau $matches est décalé de +1 par rapport à $params_route + */ + private function getParams($params_route, $matches) { + $params = array (); + + for ($i = 0; $i < count ($params_route); $i++) { + $param = $params_route[$i]; + $params[$param] = $matches[$i + 1]; + } + + return $params; + } + + /** + * Remplace les éléments de la route par les valeurs contenues dans $params + */ + private function replaceParams ($route, $params_replace) { + $uri = $route['route']; + $params = array(); + foreach($route['params'] as $param) { + $uri = preg_replace('#\((.+)\)#U', $params_replace[$param], $uri, 1); + } + + return stripslashes($uri); + } +} diff --git a/lib/Minz/Session.php b/lib/Minz/Session.php new file mode 100644 index 000000000..878caa556 --- /dev/null +++ b/lib/Minz/Session.php @@ -0,0 +1,78 @@ + + */ + +/** + * La classe Translate se charge de la traduction + * Utilise les fichiers du répertoire /app/i18n/ + */ +class Minz_Translate { + /** + * $language est la langue à afficher + */ + private static $language; + + /** + * $translates est le tableau de correspondance + * $key => $traduction + */ + private static $translates = array (); + + /** + * Inclus le fichier de langue qui va bien + * l'enregistre dans $translates + */ + public static function init () { + $l = Minz_Configuration::language (); + self::$language = Minz_Session::param ('language', $l); + + $l_path = APP_PATH . '/i18n/' . self::$language . '.php'; + + if (file_exists ($l_path)) { + self::$translates = include ($l_path); + } + } + + /** + * Alias de init + */ + public static function reset () { + self::init (); + } + + /** + * Traduit une clé en sa valeur du tableau $translates + * @param $key la clé à traduire + * @return la valeur correspondante à la clé + * > si non présente dans le tableau, on retourne la clé elle-même + */ + public static function t ($key) { + $translate = $key; + + if (isset (self::$translates[$key])) { + $translate = self::$translates[$key]; + } + + $args = func_get_args (); + unset($args[0]); + + return vsprintf ($translate, $args); + } + + /** + * Retourne la langue utilisée actuellement + * @return la langue + */ + public static function language () { + return self::$language; + } +} diff --git a/lib/Minz/Url.php b/lib/Minz/Url.php new file mode 100644 index 000000000..30f7f6231 --- /dev/null +++ b/lib/Minz/Url.php @@ -0,0 +1,129 @@ +printUriRewrited ($url); + } else { + $url_string .= self::printUri ($url, $encodage); + } + } else { + $url_string .= $url; + } + + return $url_string; + } + + /** + * Construit l'URI d'une URL sans url rewriting + * @param l'url sous forme de tableau + * @param $encodage pour indiquer comment encoder les & (& ou & pour html) + * @return l'uri sous la forme ?key=value&key2=value2 + */ + private static function printUri ($url, $encodage) { + $uri = ''; + $separator = '/?'; + + if($encodage == 'html') { + $and = '&'; + } else { + $and = '&'; + } + + if (isset ($url['c']) + && $url['c'] != Minz_Request::defaultControllerName ()) { + $uri .= $separator . 'c=' . $url['c']; + $separator = $and; + } + + if (isset ($url['a']) + && $url['a'] != Minz_Request::defaultActionName ()) { + $uri .= $separator . 'a=' . $url['a']; + $separator = $and; + } + + if (isset ($url['params'])) { + foreach ($url['params'] as $key => $param) { + $uri .= $separator . $key . '=' . $param; + $separator = $and; + } + } + + return $uri; + } + + /** + * Vérifie que les éléments du tableau représentant une url soit ok + * @param l'url sous forme de tableau (sinon renverra directement $url) + * @return l'url vérifié + */ + public static function checkUrl ($url) { + $url_checked = $url; + + if (is_array ($url)) { + if (!isset ($url['c'])) { + $url_checked['c'] = Minz_Request::defaultControllerName (); + } + if (!isset ($url['a'])) { + $url_checked['a'] = Minz_Request::defaultActionName (); + } + if (!isset ($url['params'])) { + $url_checked['params'] = array (); + } + } + + return $url_checked; + } +} + +function _url ($controller, $action) { + $nb_args = func_num_args (); + + if($nb_args < 2 || $nb_args % 2 != 0) { + return false; + } + + $args = func_get_args (); + $params = array (); + for($i = 2; $i < $nb_args; $i = $i + 2) { + $params[$args[$i]] = $args[$i + 1]; + } + + return Minz_Url::display (array ('c' => $controller, 'a' => $action, 'params' => $params)); +} diff --git a/lib/Minz/View.php b/lib/Minz/View.php new file mode 100644 index 000000000..c8d0aefed --- /dev/null +++ b/lib/Minz/View.php @@ -0,0 +1,241 @@ + +*/ + +/** + * La classe View représente la vue de l'application + */ +class Minz_View { + const VIEWS_PATH_NAME = '/views'; + const LAYOUT_PATH_NAME = '/layout'; + const LAYOUT_FILENAME = '/layout.phtml'; + + private $view_filename = ''; + private $use_layout = false; + + private static $title = ''; + private static $styles = array (); + private static $scripts = array (); + + private static $params = array (); + + /** + * Constructeur + * Détermine si on utilise un layout ou non + */ + public function __construct () { + $this->view_filename = APP_PATH + . self::VIEWS_PATH_NAME . '/' + . Minz_Request::controllerName () . '/' + . Minz_Request::actionName () . '.phtml'; + + if (file_exists (APP_PATH + . self::LAYOUT_PATH_NAME + . self::LAYOUT_FILENAME)) { + $this->use_layout = true; + } + + self::$title = Minz_Configuration::title (); + } + + /** + * Construit la vue + */ + public function build () { + if ($this->use_layout) { + $this->buildLayout (); + } else { + $this->render (); + } + } + + /** + * Construit le layout + */ + public function buildLayout () { + include ( + APP_PATH + . self::LAYOUT_PATH_NAME + . self::LAYOUT_FILENAME + ); + } + + /** + * Affiche la Vue en elle-même + */ + public function render () { + if (file_exists ($this->view_filename)) { + include ($this->view_filename); + } else { + Minz_Log::record ('File doesn\'t exist : `' + . $this->view_filename . '`', + Minz_Log::NOTICE); + } + } + + /** + * Ajoute un élément du layout + * @param $part l'élément partial à ajouter + */ + public function partial ($part) { + $fic_partial = APP_PATH + . self::LAYOUT_PATH_NAME . '/' + . $part . '.phtml'; + + if (file_exists ($fic_partial)) { + include ($fic_partial); + } else { + Minz_Log::record ('File doesn\'t exist : `' + . $fic_partial . '`', + Minz_Log::WARNING); + } + } + + /** + * Affiche un élément graphique situé dans APP./views/helpers/ + * @param $helper l'élément à afficher + */ + public function renderHelper ($helper) { + $fic_helper = APP_PATH + . '/views/helpers/' + . $helper . '.phtml'; + + if (file_exists ($fic_helper)) { + include ($fic_helper); + } else { + Minz_Log::record ('File doesn\'t exist : `' + . $fic_helper . '`', + Minz_Log::WARNING); + } + } + + /** + * Permet de choisir si on souhaite utiliser le layout + * @param $use true si on souhaite utiliser le layout, false sinon + */ + public function _useLayout ($use) { + $this->use_layout = $use; + } + + /** + * Gestion du titre + */ + public static function title () { + return self::$title; + } + public static function headTitle () { + return '' . self::$title . '' . "\n"; + } + public static function _title ($title) { + self::$title = $title; + } + public static function prependTitle ($title) { + self::$title = $title . self::$title; + } + public static function appendTitle ($title) { + self::$title = self::$title . $title; + } + + /** + * Gestion des feuilles de style + */ + public static function headStyle () { + $styles = ''; + + foreach(self::$styles as $style) { + $cond = $style['cond']; + if ($cond) { + $styles .= ''; + } + + $styles .= "\n"; + } + + return $styles; + } + public static function prependStyle ($url, $media = 'all', $cond = false) { + array_unshift (self::$styles, array ( + 'url' => $url, + 'media' => $media, + 'cond' => $cond + )); + } + public static function appendStyle ($url, $media = 'all', $cond = false) { + self::$styles[] = array ( + 'url' => $url, + 'media' => $media, + 'cond' => $cond + ); + } + + /** + * Gestion des scripts JS + */ + public static function headScript () { + $scripts = ''; + + foreach (self::$scripts as $script) { + $cond = $script['cond']; + if ($cond) { + $scripts .= ''; + } + + $scripts .= "\n"; + } + + return $scripts; + } + public static function prependScript ($url, $cond = false, $defer = true, $async = true) { + array_unshift(self::$scripts, array ( + 'url' => $url, + 'cond' => $cond, + 'defer' => $defer, + 'async' => $async, + )); + } + public static function appendScript ($url, $cond = false, $defer = true, $async = true) { + self::$scripts[] = array ( + 'url' => $url, + 'cond' => $cond, + 'defer' => $defer, + 'async' => $async, + ); + } + + /** + * Gestion des paramètres ajoutés à la vue + */ + public static function _param ($key, $value) { + self::$params[$key] = $value; + } + public function attributeParams () { + foreach (Minz_View::$params as $key => $value) { + $this->$key = $value; + } + } +} + + -- cgit v1.2.3 From a2421185d0bc9a0e177b6ecbf98bb17086d43386 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sun, 15 Dec 2013 16:01:37 +0100 Subject: SQL : Petite amélioration de la requête principale MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit En particulier pour les favoris, où cela évite une jointure. --- app/Models/EntryDAO.php | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) (limited to 'app/Models') diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index 1bce6cbf2..f80fe9b77 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -261,20 +261,23 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { public function listWhere($type = 'a', $id = '', $state = 'all', $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0) { $where = ''; + $joinFeed = false; $values = array(); switch ($type) { case 'a': - $where .= 'priority > 0 '; + $where .= 'f.priority > 0 '; + $joinFeed = true; break; case 's': - $where .= 'is_favorite = 1 '; + $where .= 'e.is_favorite = 1 '; break; case 'c': - $where .= 'category = ? '; + $where .= 'f.category = ? '; $values[] = intval($id); + $joinFeed = true; break; case 'f': - $where .= 'id_feed = ? '; + $where .= 'e.id_feed = ? '; $values[] = intval($id); break; default: @@ -284,10 +287,10 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { case 'all': break; case 'not_read': - $where .= 'AND is_read = 0 '; + $where .= 'AND e.is_read = 0 '; break; case 'read': - $where .= 'AND is_read = 1 '; + $where .= 'AND e.is_read = 1 '; break; default: throw new FreshRSS_EntriesGetter_Exception ('Bad state in Entry->listByType: [' . $state . ']!'); @@ -302,8 +305,9 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { if ($firstId !== '') { $where .= 'AND e.id ' . ($order === 'DESC' ? '<=' : '>=') . $firstId . ' '; } - if ($date_min > 0) { + if (($date_min > 0) && ($type !== 's')) { $where .= 'AND (e.id >= ' . $date_min . '000000 OR e.is_favorite = 1 OR f.keep_history = 1) '; + $joinFeed = true; } $terms = array_unique(explode(' ', trim($filter))); sort($terms); //Put #tags first @@ -311,7 +315,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { foreach ($terms as $word) { if (!empty($word)) { if ($word[0] === '#' && isset($word[1])) { - $having .= 'AND tags LIKE ? '; + $having .= 'AND e.tags LIKE ? '; $values[] = '%' . $word .'%'; } elseif (!empty($word)) { $having .= 'AND (e.title LIKE ? OR content LIKE ?) '; @@ -323,7 +327,8 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { $sql = 'SELECT e.id, e.guid, e.title, e.author, UNCOMPRESS(e.content_bin) AS content, e.link, e.date, e.is_read, e.is_favorite, e.id_feed, e.tags ' . 'FROM `' . $this->prefix . 'entry` e ' - . 'INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id WHERE ' . $where + . ($joinFeed ? 'INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id ' : '') + . 'WHERE ' . $where . (empty($having) ? '' : 'HAVING' . substr($having, 3)) . 'ORDER BY e.id ' . $order; -- cgit v1.2.3 From 529d6bcd15f7351cb7bdcf2f74c6a44930b0de55 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Mon, 16 Dec 2013 00:50:24 +0100 Subject: SQL : performances MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tentative de reformulation de la requête principale pour améliorer les performances. Utilisation d'une sous-jointure qui retourne uniquement e.id. Sur mon serveur avec 13000 articles, la requête de la page d'accueil sans article non lu mettait 1.38s avant le patch, contre 0.08s après (en désactivant bien sûr le cache SQL). Il faudra re-tester et tenter d'autres optimisations (notamment sur les index) avec un nombre d'articles plus important. Avant : SELECT SQL_NO_CACHE e.id, e.guid, e.title, e.author, UNCOMPRESS(e.content_bin) AS content, e.link, e.date, e.is_read, e.is_favorite, e.id_feed, e.tags FROM `freshrss_alex_entry` e INNER JOIN `freshrss_alex_feed` f ON e.id_feed = f.id WHERE f.priority > 0 AND (e.id >= 1371597014000000 OR e.is_favorite = 1 OR f.keep_history = 1) ORDER BY e.id DESC LIMIT 33; Après : SELECT SQL_NO_CACHE e.id, e.guid, e.title, e.author, UNCOMPRESS(e.content_bin) AS content, e.link, e.date, e.is_read, e.is_favorite, e.id_feed, e.tags FROM `freshrss_alex_entry` e INNER JOIN (SELECT e1.id FROM `freshrss_alex_entry` e1 INNER JOIN `freshrss_alex_feed` f ON e1.id_feed = f.id WHERE f.priority > 0 AND (e1.id >= 1371597014000000 OR e1.is_favorite = 1 OR f.keep_history = 1) ORDER BY e1.id DESC LIMIT 33) e2 ON e2.id = e.id ORDER BY e.id DESC; --- app/Controllers/indexController.php | 4 +++- app/Models/EntryDAO.php | 30 +++++++++++++++--------------- 2 files changed, 18 insertions(+), 16 deletions(-) (limited to 'app/Models') diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php index 92070590a..cc474302e 100755 --- a/app/Controllers/indexController.php +++ b/app/Controllers/indexController.php @@ -124,9 +124,11 @@ class FreshRSS_index_Controller extends Minz_ActionController { } } + $today = @strtotime('today'); + // on calcule la date des articles les plus anciens qu'on affiche $nb_month_old = $this->view->conf->oldEntries (); - $date_min = time () - (3600 * 24 * 30 * $nb_month_old); + $date_min = $today - (3600 * 24 * 30 * $nb_month_old); //Do not use a fast changing value such as time() to allow SQL caching try { $entries = $this->entryDAO->listWhere($getType, $getId, $state, $order, $nb + 1, $first, $filter, $date_min); diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index f80fe9b77..5a34573db 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -269,7 +269,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { $joinFeed = true; break; case 's': - $where .= 'e.is_favorite = 1 '; + $where .= 'e1.is_favorite = 1 '; break; case 'c': $where .= 'f.category = ? '; @@ -277,7 +277,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { $joinFeed = true; break; case 'f': - $where .= 'e.id_feed = ? '; + $where .= 'e1.id_feed = ? '; $values[] = intval($id); break; default: @@ -287,10 +287,10 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { case 'all': break; case 'not_read': - $where .= 'AND e.is_read = 0 '; + $where .= 'AND e1.is_read = 0 '; break; case 'read': - $where .= 'AND e.is_read = 1 '; + $where .= 'AND e1.is_read = 1 '; break; default: throw new FreshRSS_EntriesGetter_Exception ('Bad state in Entry->listByType: [' . $state . ']!'); @@ -303,10 +303,10 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { throw new FreshRSS_EntriesGetter_Exception ('Bad order in Entry->listByType: [' . $order . ']!'); } if ($firstId !== '') { - $where .= 'AND e.id ' . ($order === 'DESC' ? '<=' : '>=') . $firstId . ' '; + $where .= 'AND e1.id ' . ($order === 'DESC' ? '<=' : '>=') . $firstId . ' '; } if (($date_min > 0) && ($type !== 's')) { - $where .= 'AND (e.id >= ' . $date_min . '000000 OR e.is_favorite = 1 OR f.keep_history = 1) '; + $where .= 'AND (e1.id >= ' . $date_min . '000000 OR e1.is_favorite = 1 OR f.keep_history = 1) '; $joinFeed = true; } $terms = array_unique(explode(' ', trim($filter))); @@ -315,10 +315,10 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { foreach ($terms as $word) { if (!empty($word)) { if ($word[0] === '#' && isset($word[1])) { - $having .= 'AND e.tags LIKE ? '; + $having .= 'AND e1.tags LIKE ? '; $values[] = '%' . $word .'%'; } elseif (!empty($word)) { - $having .= 'AND (e.title LIKE ? OR content LIKE ?) '; + $having .= 'AND (e1.title LIKE ? OR content LIKE ?) '; $values[] = '%' . $word .'%'; $values[] = '%' . $word .'%'; } @@ -327,15 +327,15 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { $sql = 'SELECT e.id, e.guid, e.title, e.author, UNCOMPRESS(e.content_bin) AS content, e.link, e.date, e.is_read, e.is_favorite, e.id_feed, e.tags ' . 'FROM `' . $this->prefix . 'entry` e ' - . ($joinFeed ? 'INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id ' : '') - . 'WHERE ' . $where - . (empty($having) ? '' : 'HAVING' . substr($having, 3)) + . 'INNER JOIN (SELECT e1.id FROM `' . $this->prefix . 'entry` e1 ' + . ($joinFeed ? 'INNER JOIN `' . $this->prefix . 'feed` f ON e1.id_feed = f.id ' : '') + . 'WHERE ' . $where + . (empty($having) ? '' : 'HAVING' . substr($having, 3)) + . 'ORDER BY e1.id ' . $order + . ($limit > 0 ? ' LIMIT ' . $limit : '') //TODO: See http://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/ + . ') e2 ON e2.id = e.id ' . 'ORDER BY e.id ' . $order; - if ($limit > 0) { - $sql .= ' LIMIT ' . $limit; //TODO: See http://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/ - } - $stm = $this->bd->prepare ($sql); $stm->execute ($values); -- cgit v1.2.3 From 847de9b3292ad854b281d7e12cc36ac93e745139 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Mon, 16 Dec 2013 00:54:13 +0100 Subject: PHP : performances fonction isDay MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Amélioration des performances de Entry->isDay() --- app/Controllers/indexController.php | 1 + app/Models/Entry.php | 27 +++++++++++++-------------- app/layout/nav_menu.phtml | 2 +- app/views/helpers/view/normal_view.phtml | 7 ++++--- 4 files changed, 19 insertions(+), 18 deletions(-) (limited to 'app/Models') diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php index cc474302e..22c5683ea 100755 --- a/app/Controllers/indexController.php +++ b/app/Controllers/indexController.php @@ -125,6 +125,7 @@ class FreshRSS_index_Controller extends Minz_ActionController { } $today = @strtotime('today'); + $this->view->today = $today; // on calcule la date des articles les plus anciens qu'on affiche $nb_month_old = $this->view->conf->oldEntries (); diff --git a/app/Models/Entry.php b/app/Models/Entry.php index ba0fb48f4..983f94727 100644 --- a/app/Models/Entry.php +++ b/app/Models/Entry.php @@ -134,21 +134,20 @@ class FreshRSS_Entry extends Minz_Model { $this->tags = $value; } - public function isDay ($day) { + public function isDay ($day, $today) { $date = $this->dateAdded(true); - $today = @strtotime('today'); - $yesterday = $today - 86400; - - if ($day === FreshRSS_Days::TODAY && - $date >= $today && $date < $today + 86400) { - return true; - } elseif ($day === FreshRSS_Days::YESTERDAY && - $date >= $yesterday && $date < $yesterday + 86400) { - return true; - } elseif ($day === FreshRSS_Days::BEFORE_YESTERDAY && $date < $yesterday) { - return true; - } else { - return false; + switch ($day) { + case FreshRSS_Days::TODAY: + $tomorrow = $today + 86400; + return $date >= $today && $date < $tomorrow; + case FreshRSS_Days::YESTERDAY: + $yesterday = $today - 86400; + return $date >= $yesterday && $date < $today; + case FreshRSS_Days::BEFORE_YESTERDAY: + $yesterday = $today - 86400; + return $date < $yesterday; + default: + return false; } } diff --git a/app/layout/nav_menu.phtml b/app/layout/nav_menu.phtml index 92a987aed..045f391f9 100644 --- a/app/layout/nav_menu.phtml +++ b/app/layout/nav_menu.phtml @@ -72,7 +72,7 @@
  • today; $one_week = $today - 604800; ?>
  • diff --git a/app/views/helpers/view/normal_view.phtml b/app/views/helpers/view/normal_view.phtml index 094017957..d5328651d 100644 --- a/app/views/helpers/view/normal_view.phtml +++ b/app/views/helpers/view/normal_view.phtml @@ -21,24 +21,25 @@ if (!empty($this->entries)) { $facebook = $this->conf->sharing ('facebook'); $email = $this->conf->sharing ('email'); $print = $this->conf->sharing ('print'); + $today = $this->today; ?> entries as $item) { ?> - isDay (FreshRSS_Days::TODAY)) { ?> + isDay (FreshRSS_Days::TODAY, $today)) { ?>
    - currentName; ?>
    - isDay (FreshRSS_Days::YESTERDAY)) { ?> + isDay (FreshRSS_Days::YESTERDAY, $today)) { ?>
    - currentName; ?>
    - isDay (FreshRSS_Days::BEFORE_YESTERDAY)) { ?> + isDay (FreshRSS_Days::BEFORE_YESTERDAY, $today)) { ?>
    currentName; ?> -- cgit v1.2.3 From 8abeeaf65e79a464aae6d40f8868ecd29889df4c Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Mon, 16 Dec 2013 17:45:57 +0100 Subject: SQL : correction recherche MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Oups, mon précédent changement SQL avait cassé la recherche. Patch rapide en attendant une ré-optimisation en particulier pour le cas de recherche sur plusieurs mots --- app/Models/EntryDAO.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'app/Models') diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index 5a34573db..c4eb0a84a 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -311,14 +311,14 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { } $terms = array_unique(explode(' ', trim($filter))); sort($terms); //Put #tags first - $having = ''; + $search = ''; foreach ($terms as $word) { if (!empty($word)) { if ($word[0] === '#' && isset($word[1])) { - $having .= 'AND e1.tags LIKE ? '; + $search .= 'AND e1.tags LIKE ? '; $values[] = '%' . $word .'%'; } elseif (!empty($word)) { - $having .= 'AND (e1.title LIKE ? OR content LIKE ?) '; + $search .= 'AND (e1.title LIKE ? OR UNCOMPRESS(e1.content_bin) LIKE ?) '; $values[] = '%' . $word .'%'; $values[] = '%' . $word .'%'; } @@ -330,7 +330,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { . 'INNER JOIN (SELECT e1.id FROM `' . $this->prefix . 'entry` e1 ' . ($joinFeed ? 'INNER JOIN `' . $this->prefix . 'feed` f ON e1.id_feed = f.id ' : '') . 'WHERE ' . $where - . (empty($having) ? '' : 'HAVING' . substr($having, 3)) + . $search . 'ORDER BY e1.id ' . $order . ($limit > 0 ? ' LIMIT ' . $limit : '') //TODO: See http://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/ . ') e2 ON e2.id = e.id ' -- cgit v1.2.3 From a1f8bade6176f03a6e2399d5de24975bb47d09d6 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Mon, 16 Dec 2013 21:58:47 +0100 Subject: SQL : petits changement recherche MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Traite mieux les caractères spéciaux. Permet par exemple une recherche sur des mots contenant des apostrophes, ou le signe pourcentage, etc. Il faudra toujours essayer d'améliorer la recherche en particulier lorsque plusieurs mots sont fournis --- app/Models/EntryDAO.php | 28 +++++++++++++++++----------- lib/Minz/Request.php | 2 +- 2 files changed, 18 insertions(+), 12 deletions(-) (limited to 'app/Models') diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index c4eb0a84a..2af511527 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -309,18 +309,24 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { $where .= 'AND (e1.id >= ' . $date_min . '000000 OR e1.is_favorite = 1 OR f.keep_history = 1) '; $joinFeed = true; } - $terms = array_unique(explode(' ', trim($filter))); - sort($terms); //Put #tags first $search = ''; - foreach ($terms as $word) { - if (!empty($word)) { - if ($word[0] === '#' && isset($word[1])) { - $search .= 'AND e1.tags LIKE ? '; - $values[] = '%' . $word .'%'; - } elseif (!empty($word)) { - $search .= 'AND (e1.title LIKE ? OR UNCOMPRESS(e1.content_bin) LIKE ?) '; - $values[] = '%' . $word .'%'; - $values[] = '%' . $word .'%'; + if ($filter !== '') { + $filter = trim($filter); + $filter = addcslashes($filter, '\\%_'); + $terms = array_unique(explode(' ', $filter)); + sort($terms); //Put #tags first + foreach ($terms as $word) { + $word = trim($word); + if (strlen($word) > 0) { + if ($word[0] === '#') { + if (isset($word[1])) { + $search .= 'AND e1.tags LIKE ? '; + $values[] = '%' . $word .'%'; + } + } else { + $search .= 'AND CONCAT(e1.title, UNCOMPRESS(e1.content_bin)) LIKE ? '; + $values[] = '%' . $word .'%'; + } } } } diff --git a/lib/Minz/Request.php b/lib/Minz/Request.php index c8ffa4a42..fb48bd7a2 100644 --- a/lib/Minz/Request.php +++ b/lib/Minz/Request.php @@ -30,7 +30,7 @@ class Minz_Request { return self::$params; } static function htmlspecialchars_utf8 ($p) { - return htmlspecialchars($p, ENT_QUOTES, 'UTF-8'); + return htmlspecialchars($p, ENT_COMPAT, 'UTF-8'); } public static function param ($key, $default = false, $specialchars = false) { if (isset (self::$params[$key])) { -- cgit v1.2.3 From ba71d7747a8b0847ad59ff56870378b341c5171d Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Mon, 16 Dec 2013 22:03:16 +0100 Subject: SQL : permet recherche du caractère dièse MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Models/EntryDAO.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'app/Models') diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index 2af511527..d1f595d42 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -318,11 +318,9 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { foreach ($terms as $word) { $word = trim($word); if (strlen($word) > 0) { - if ($word[0] === '#') { - if (isset($word[1])) { - $search .= 'AND e1.tags LIKE ? '; - $values[] = '%' . $word .'%'; - } + if ($word[0] === '#' && isset($word[1])) { + $search .= 'AND e1.tags LIKE ? '; + $values[] = '%' . $word .'%'; } else { $search .= 'AND CONCAT(e1.title, UNCOMPRESS(e1.content_bin)) LIKE ? '; $values[] = '%' . $word .'%'; -- cgit v1.2.3 From e29be10556e1994083bce7398eee0a3edc017a9d Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Mon, 16 Dec 2013 22:22:56 +0100 Subject: Recherches spéciales intitle: ou inurl: ou author: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Permet de chercher en utilisant intitle: ou inurl: ou author: comme dans certains moteurs de recherche. Pour l'instant, un seul de ces mots clefs à la fois peut être spécifié en tout début de chaîne de recherche et sera appliqué à l'ensemble du reste de la recherche. NB: À ajouter à la documentation, wiki --- app/Models/EntryDAO.php | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) (limited to 'app/Models') diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index d1f595d42..c9989622a 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -313,17 +313,46 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { if ($filter !== '') { $filter = trim($filter); $filter = addcslashes($filter, '\\%_'); + if (stripos($filter, 'intitle:') === 0) { + $filter = substr($filter, strlen('intitle:')); + $intitle = true; + } else { + $intitle = false; + } + if (stripos($filter, 'inurl:') === 0) { + $filter = substr($filter, strlen('inurl:')); + $inurl = true; + } else { + $inurl = false; + } + if (stripos($filter, 'author:') === 0) { + $filter = substr($filter, strlen('author:')); + $author = true; + } else { + $author = false; + } $terms = array_unique(explode(' ', $filter)); sort($terms); //Put #tags first foreach ($terms as $word) { $word = trim($word); if (strlen($word) > 0) { - if ($word[0] === '#' && isset($word[1])) { - $search .= 'AND e1.tags LIKE ? '; + if ($intitle) { + $search .= 'AND e1.title LIKE ? '; $values[] = '%' . $word .'%'; - } else { - $search .= 'AND CONCAT(e1.title, UNCOMPRESS(e1.content_bin)) LIKE ? '; + } elseif ($inurl) { + $search .= 'AND CONCAT(e1.link, e1.guid) LIKE ? '; $values[] = '%' . $word .'%'; + } elseif ($author) { + $search .= 'AND e1.author LIKE ? '; + $values[] = '%' . $word .'%'; + } else { + if ($word[0] === '#' && isset($word[1])) { + $search .= 'AND e1.tags LIKE ? '; + $values[] = '%' . $word .'%'; + } else { + $search .= 'AND CONCAT(e1.title, UNCOMPRESS(e1.content_bin)) LIKE ? '; + $values[] = '%' . $word .'%'; + } } } } -- cgit v1.2.3 From 6b7d96d0ea97579720ee6d560224cd80c2329d07 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Thu, 19 Dec 2013 21:19:45 +0100 Subject: Refactorisation : correction classes oubliées MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Controllers/indexController.php | 6 +++--- app/Models/CategoryDAO.php | 16 +++++++--------- app/Models/EntryDAO.php | 8 +++----- app/Models/FeedDAO.php | 12 +++++------- app/views/helpers/view/normal_view.phtml | 2 +- app/views/helpers/view/reader_view.phtml | 2 +- 6 files changed, 20 insertions(+), 26 deletions(-) (limited to 'app/Models') diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php index 22c5683ea..e3c253518 100755 --- a/app/Controllers/indexController.php +++ b/app/Controllers/indexController.php @@ -77,7 +77,7 @@ class FreshRSS_index_Controller extends Minz_ActionController { return; } - $this->view->nb_not_read = HelperCategory::CountUnreads($this->view->cat_aside, 1); + $this->view->nb_not_read = FreshRSS_CategoryDAO::CountUnreads($this->view->cat_aside, 1); // mise à jour des titres $this->view->rss_title = $this->view->currentName . ' | ' . Minz_View::title(); @@ -112,7 +112,7 @@ class FreshRSS_index_Controller extends Minz_ActionController { $hasUnread = (!isset($this->view->cat_aside[$getId])) || ($this->view->cat_aside[$getId]->nbNotRead() > 0); break; case 'f': - $myFeed = HelperCategory::findFeed($this->view->cat_aside, $getId); + $myFeed = FreshRSS_CategoryDAO::findFeed($this->view->cat_aside, $getId); $hasUnread = ($myFeed === null) || ($myFeed->nbNotRead() > 0); break; default: @@ -188,7 +188,7 @@ class FreshRSS_index_Controller extends Minz_ActionController { return false; } case 'f': - $feed = HelperCategory::findFeed($this->view->cat_aside, $getId); + $feed = FreshRSS_CategoryDAO::findFeed($this->view->cat_aside, $getId); if (empty($feed)) { $feed = $this->feedDAO->searchById ($getId); } diff --git a/app/Models/CategoryDAO.php b/app/Models/CategoryDAO.php index 793e593c3..3a810e9f0 100644 --- a/app/Models/CategoryDAO.php +++ b/app/Models/CategoryDAO.php @@ -60,7 +60,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo { $stm->execute ($values); $res = $stm->fetchAll (PDO::FETCH_ASSOC); - $cat = HelperCategory::daoToCategory ($res); + $cat = self::daoToCategory ($res); if (isset ($cat[0])) { return $cat[0]; @@ -76,7 +76,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo { $stm->execute ($values); $res = $stm->fetchAll (PDO::FETCH_ASSOC); - $cat = HelperCategory::daoToCategory ($res); + $cat = self::daoToCategory ($res); if (isset ($cat[0])) { return $cat[0]; @@ -96,12 +96,12 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo { . 'ORDER BY c.name, f.name'; $stm = $this->bd->prepare ($sql); $stm->execute (); - return HelperCategory::daoToCategoryPrepopulated ($stm->fetchAll (PDO::FETCH_ASSOC)); + return self::daoToCategoryPrepopulated ($stm->fetchAll (PDO::FETCH_ASSOC)); } else { $sql = 'SELECT * FROM `' . $this->prefix . 'category` ORDER BY name'; $stm = $this->bd->prepare ($sql); $stm->execute (); - return HelperCategory::daoToCategory ($stm->fetchAll (PDO::FETCH_ASSOC)); + return self::daoToCategory ($stm->fetchAll (PDO::FETCH_ASSOC)); } } @@ -111,7 +111,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo { $stm->execute (); $res = $stm->fetchAll (PDO::FETCH_ASSOC); - $cat = HelperCategory::daoToCategory ($res); + $cat = self::daoToCategory ($res); if (isset ($cat[0])) { return $cat[0]; @@ -164,9 +164,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo { return $res[0]['count']; } -} -class HelperCategory { public static function findFeed($categories, $feed_id) { foreach ($categories as $category) { foreach ($category->feeds () as $feed) { @@ -205,7 +203,7 @@ class HelperCategory { $cat = new FreshRSS_Category ( $previousLine['c_name'], isset($previousLine['c_color']) ? $previousLine['c_color'] : '', - HelperFeed::daoToFeed ($feedsDao, $previousLine['c_id']) + FreshRSS_FeedDAO::daoToFeed ($feedsDao, $previousLine['c_id']) ); $cat->_id ($previousLine['c_id']); $list[$previousLine['c_id']] = $cat; @@ -222,7 +220,7 @@ class HelperCategory { $cat = new FreshRSS_Category ( $previousLine['c_name'], isset($previousLine['c_color']) ? $previousLine['c_color'] : '', - HelperFeed::daoToFeed ($feedsDao, $previousLine['c_id']) + FreshRSS_FeedDAO::daoToFeed ($feedsDao, $previousLine['c_id']) ); $cat->_id ($previousLine['c_id']); $list[$previousLine['c_id']] = $cat; diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index c9989622a..d8bc869ae 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -242,7 +242,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { $stm->execute ($values); $res = $stm->fetchAll (PDO::FETCH_ASSOC); - $entries = HelperEntry::daoToEntry ($res); + $entries = self::daoToEntry ($res); return isset ($entries[0]) ? $entries[0] : false; } @@ -255,7 +255,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { $stm->execute ($values); $res = $stm->fetchAll (PDO::FETCH_ASSOC); - $entries = HelperEntry::daoToEntry ($res); + $entries = self::daoToEntry ($res); return isset ($entries[0]) ? $entries[0] : false; } @@ -372,7 +372,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { $stm = $this->bd->prepare ($sql); $stm->execute ($values); - return HelperEntry::daoToEntry ($stm->fetchAll (PDO::FETCH_ASSOC)); + return self::daoToEntry ($stm->fetchAll (PDO::FETCH_ASSOC)); } public function listLastGuidsByFeed($id, $n) { @@ -430,9 +430,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { $stm = $this->bd->prepare ($sql); $stm->execute (); } -} -class HelperEntry { public static function daoToEntry ($listDAO) { $list = array (); diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php index 8f59b1c76..9ebea4a47 100644 --- a/app/Models/FeedDAO.php +++ b/app/Models/FeedDAO.php @@ -158,7 +158,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { $stm->execute ($values); $res = $stm->fetchAll (PDO::FETCH_ASSOC); - $feed = HelperFeed::daoToFeed ($res); + $feed = self::daoToFeed ($res); if (isset ($feed[$id])) { return $feed[$id]; @@ -174,7 +174,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { $stm->execute ($values); $res = $stm->fetchAll (PDO::FETCH_ASSOC); - $feed = current (HelperFeed::daoToFeed ($res)); + $feed = current (self::daoToFeed ($res)); if (isset ($feed)) { return $feed; @@ -188,7 +188,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { $stm = $this->bd->prepare ($sql); $stm->execute (); - return HelperFeed::daoToFeed ($stm->fetchAll (PDO::FETCH_ASSOC)); + return self::daoToFeed ($stm->fetchAll (PDO::FETCH_ASSOC)); } public function listFeedsOrderUpdate () { @@ -196,7 +196,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { $stm = $this->bd->prepare ($sql); $stm->execute (); - return HelperFeed::daoToFeed ($stm->fetchAll (PDO::FETCH_ASSOC)); + return self::daoToFeed ($stm->fetchAll (PDO::FETCH_ASSOC)); } public function listByCategory ($cat) { @@ -207,7 +207,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { $stm->execute ($values); - return HelperFeed::daoToFeed ($stm->fetchAll (PDO::FETCH_ASSOC)); + return self::daoToFeed ($stm->fetchAll (PDO::FETCH_ASSOC)); } public function countEntries ($id) { @@ -299,9 +299,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { return false; } } -} -class HelperFeed { public static function daoToFeed ($listDAO, $catID = null) { $list = array (); diff --git a/app/views/helpers/view/normal_view.phtml b/app/views/helpers/view/normal_view.phtml index d5328651d..f21a2bdd9 100644 --- a/app/views/helpers/view/normal_view.phtml +++ b/app/views/helpers/view/normal_view.phtml @@ -62,7 +62,7 @@ if (!empty($this->entries)) { ?>cat_aside, $item->feed ()); //We most likely already have the feed object in cache + $feed = FreshRSS_CategoryDAO::findFeed($this->cat_aside, $item->feed ()); //We most likely already have the feed object in cache if (empty($feed)) $feed = $item->feed (true); ?>
  • ✇ name(); ?>
  • diff --git a/app/views/helpers/view/reader_view.phtml b/app/views/helpers/view/reader_view.phtml index 29b2be04c..e28af6ade 100644 --- a/app/views/helpers/view/reader_view.phtml +++ b/app/views/helpers/view/reader_view.phtml @@ -11,7 +11,7 @@ if (!empty($this->entries)) {
    cat_aside, $item->feed ()); //We most likely already have the feed object in cache + $feed = FreshRSS_CategoryDAO::findFeed($this->cat_aside, $item->feed ()); //We most likely already have the feed object in cache if (empty($feed)) $feed = $item->feed (true); ?> -- cgit v1.2.3 From 4bcfd591f0192aaa17f934e9f56291c453815134 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sun, 22 Dec 2013 15:00:54 +0100 Subject: Chargement automatique activé par défaut MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clôture https://github.com/marienfressinaud/FreshRSS/issues/308 --- app/Models/ConfigurationDAO.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/Models') diff --git a/app/Models/ConfigurationDAO.php b/app/Models/ConfigurationDAO.php index fec58d234..57fc98047 100644 --- a/app/Models/ConfigurationDAO.php +++ b/app/Models/ConfigurationDAO.php @@ -38,7 +38,7 @@ class FreshRSS_ConfigurationDAO extends Minz_ModelArray { public $theme = 'default'; public $anon_access = 'no'; public $token = ''; - public $auto_load_more = 'no'; + public $auto_load_more = 'yes'; public $topline_read = 'yes'; public $topline_favorite = 'yes'; public $topline_date = 'yes'; -- cgit v1.2.3 From 7b7acf5c8738e949109672748dbd9f39a6e5a2c4 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Mon, 23 Dec 2013 13:35:54 +0100 Subject: Synchronisation quelques lignes blanches --- README.md | 4 ++-- app/Models/CategoryDAO.php | 1 + app/Models/Configuration.php | 1 + app/Models/ConfigurationDAO.php | 1 + app/Models/Entry.php | 1 + app/Models/EntryDAO.php | 1 + app/Models/Feed.php | 1 + app/Models/FeedDAO.php | 1 + app/Models/LogDAO.php | 1 + public/install.php | 1 + 10 files changed, 11 insertions(+), 2 deletions(-) (limited to 'app/Models') diff --git a/README.md b/README.md index a45c8e67c..87ed9de99 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,8 @@ Il se veut léger et facile à prendre en main tout en étant un outil puissant * Site officiel : http://marienfressinaud.github.io/FreshRSS/ * Démo : http://marienfressinaud.fr/projets/freshrss/ * Développeur : Marien Fressinaud -* Version actuelle : 0.7-dev -* Date de publication 2013-12-xx +* Version actuelle : 0.7-beta +* Date de publication 2014-01-xx * License [GNU AGPL 3](http://www.gnu.org/licenses/agpl-3.0.html) ![Logo de FreshRSS](http://marienfressinaud.fr/data/images/freshrss/freshrss_title.png) diff --git a/app/Models/CategoryDAO.php b/app/Models/CategoryDAO.php index 3a810e9f0..6b07ab063 100644 --- a/app/Models/CategoryDAO.php +++ b/app/Models/CategoryDAO.php @@ -1,4 +1,5 @@ prefix . 'category` (name, color) VALUES(?, ?)'; diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php index 7ef76b522..d5f69601f 100644 --- a/app/Models/Configuration.php +++ b/app/Models/Configuration.php @@ -1,4 +1,5 @@ 'English', diff --git a/app/Models/ConfigurationDAO.php b/app/Models/ConfigurationDAO.php index 57fc98047..0eebf2d90 100644 --- a/app/Models/ConfigurationDAO.php +++ b/app/Models/ConfigurationDAO.php @@ -1,4 +1,5 @@ prefix . 'entry`(id, guid, title, author, content_bin, link, date, is_read, is_favorite, id_feed, tags) ' diff --git a/app/Models/Feed.php b/app/Models/Feed.php index e63ac8c7a..70efb0fa3 100644 --- a/app/Models/Feed.php +++ b/app/Models/Feed.php @@ -1,4 +1,5 @@ prefix . 'feed` (url, category, name, website, description, lastUpdate, priority, httpAuth, error, keep_history) VALUES(?, ?, ?, ?, ?, ?, 10, ?, 0, 0)'; diff --git a/app/Models/LogDAO.php b/app/Models/LogDAO.php index bf043fd6d..06855ec66 100644 --- a/app/Models/LogDAO.php +++ b/app/Models/LogDAO.php @@ -1,4 +1,5 @@ Date: Tue, 24 Dec 2013 01:21:11 +0100 Subject: Permet de configurer plus finement le nombre d’articles minimum à conserver par flux MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG | 1 + app/Controllers/feedController.php | 20 ++++++++------------ app/Models/EntryDAO.php | 2 +- app/Models/Feed.php | 17 +++++------------ app/Models/FeedDAO.php | 4 ++-- app/i18n/en.php | 7 +++++-- app/i18n/fr.php | 8 ++++++-- app/views/configure/display.phtml | 29 +++++++++++++++++++++-------- app/views/configure/feed.phtml | 22 ++++++++++++++++++---- public/install.php | 2 +- 10 files changed, 68 insertions(+), 44 deletions(-) (limited to 'app/Models') diff --git a/CHANGELOG b/CHANGELOG index af6936204..0c816dbd7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,7 @@ * Améliorations partage vers Shaarli, Poche, Diaspora*, Facebook, Twitter, Google+, courriel * Permet la suppression de tous les articles d’un flux * Option pour marquer les articles comme lus dès la réception + * Permet de configurer plus finement le nombre d’articles minimum à conserver par flux * Permet de modifier la description et l’adresse d’un flux RSS ainsi que le site Web associé * Nouveau raccourci pour ouvrir/fermer un article (‘c’ par défaut) * Bouton pour effacer les logs diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index 27b76dd42..e7d9c97c3 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -102,14 +102,11 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $feedDAO->beginTransaction (); // on ajoute les articles en masse sans vérification foreach ($entries as $entry) { - if ($entry->date (true) >= $date_min || - $feed->keepHistory ()) { - $values = $entry->toArray (); - $values['id_feed'] = $feed->id (); - $values['id'] = min(time(), $entry->date (true)) . uSecString(); - $values['is_read'] = $is_read; - $entryDAO->addEntry ($values); - } + $values = $entry->toArray (); + $values['id_feed'] = $feed->id (); + $values['id'] = min(time(), $entry->date (true)) . uSecString(); + $values['is_read'] = $is_read; + $entryDAO->addEntry ($values); } $feedDAO->updateLastUpdate ($feed->id ()); $feedDAO->commit (); @@ -217,8 +214,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $feedDAO->beginTransaction (); foreach ($entries as $entry) { if ((!isset ($existingGuids[$entry->guid ()])) && - ($entry->date (true) >= $date_min || - $feed->keepHistory ())) { + ($entry->date (true) >= $date_min)) { $values = $entry->toArray (); //Use declared date at first import, otherwise use discovery date $values['id'] = empty($existingGuids) ? min(time(), $entry->date (true)) . uSecString() : uTimeString(); @@ -227,8 +223,8 @@ class FreshRSS_feed_Controller extends Minz_ActionController { } } - if ((!$feed->keepHistory()) && (rand(0, 30) === 1)) { - $nb = $feedDAO->cleanOldEntries ($feed->id (), $date_min, count($entries) + 10); + if (($feed->keepHistory() >= 0) && (rand(0, 30) === 1)) { + $nb = $feedDAO->cleanOldEntries ($feed->id (), $date_min, max($feed->keepHistory(), count($entries) + 10)); if ($nb > 0) { Minz_Log::record ($nb . ' old entries cleaned in feed ' . $feed->id (), Minz_Log::DEBUG); } diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index c6bd5c404..f0207e96d 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -307,7 +307,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { $where .= 'AND e1.id ' . ($order === 'DESC' ? '<=' : '>=') . $firstId . ' '; } if (($date_min > 0) && ($type !== 's')) { - $where .= 'AND (e1.id >= ' . $date_min . '000000 OR e1.is_favorite = 1 OR f.keep_history = 1) '; + $where .= 'AND (e1.id >= ' . $date_min . '000000 OR e1.is_favorite = 1 OR f.keep_history <> 0) '; $joinFeed = true; } $search = ''; diff --git a/app/Models/Feed.php b/app/Models/Feed.php index 70efb0fa3..5bdf5e6d7 100644 --- a/app/Models/Feed.php +++ b/app/Models/Feed.php @@ -15,7 +15,7 @@ class FreshRSS_Feed extends Minz_Model { private $pathEntries = ''; private $httpAuth = ''; private $error = false; - private $keep_history = false; + private $keep_history = 0; public function __construct ($url, $validate=true) { if ($validate) { @@ -163,19 +163,12 @@ class FreshRSS_Feed extends Minz_Model { $this->httpAuth = $value; } public function _error ($value) { - if ($value) { - $value = true; - } else { - $value = false; - } - $this->error = $value; + $this->error = (bool)$value; } public function _keepHistory ($value) { - if ($value) { - $value = true; - } else { - $value = false; - } + $value = intval($value); + $value = min($value, 1000000); + $value = max($value, -1); $this->keep_history = $value; } public function _nbNotRead ($value) { diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php index 9bd480544..451fc3850 100644 --- a/app/Models/FeedDAO.php +++ b/app/Models/FeedDAO.php @@ -193,7 +193,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { } public function listFeedsOrderUpdate () { - $sql = 'SELECT * FROM `' . $this->prefix . 'feed` ORDER BY lastUpdate'; + $sql = 'SELECT id, url, pathEntries, httpAuth, keep_history FROM `' . $this->prefix . 'feed` ORDER BY lastUpdate'; $stm = $this->bd->prepare ($sql); $stm->execute (); @@ -326,7 +326,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { $myFeed->_pathEntries (isset($dao['pathEntries']) ? $dao['pathEntries'] : ''); $myFeed->_httpAuth (isset($dao['httpAuth']) ? base64_decode ($dao['httpAuth']) : ''); $myFeed->_error ($dao['error']); - $myFeed->_keepHistory (isset($dao['keep_history']) ? $dao['keep_history'] : ''); + $myFeed->_keepHistory (isset($dao['keep_history']) ? $dao['keep_history'] : 0); $myFeed->_nbNotRead ($dao['cache_nbUnreads']); $myFeed->_nbEntries ($dao['cache_nbEntries']); if (isset ($dao['id'])) { diff --git a/app/i18n/en.php b/app/i18n/en.php index 9c30573e8..b6417d8db 100644 --- a/app/i18n/en.php +++ b/app/i18n/en.php @@ -137,7 +137,8 @@ return array ( 'feed_url' => 'Feed URL', 'articles' => 'articles', 'number_articles' => 'Number of articles', - 'keep_history' => 'Keep old articles?', + 'keep_history' => 'Minimum number of articles to keep', + 'keep_history_help' => 'Set to -1 to keep everything', 'categorize' => 'Store in a category', 'truncate' => 'Delete all articles', 'advanced' => 'Advanced', @@ -157,13 +158,15 @@ return array ( 'general_configuration' => 'General configuration', 'language' => 'Language', - 'delete_articles_every' => 'Remove articles after', 'month' => 'months', 'default_user' => 'Username of the default user (maximum 16 alphanumeric characters)', 'persona_connection_email' => 'Login mail address (use Mozilla Persona)', 'allow_anonymous' => 'Allow anonymous reading', 'auth_token' => 'Authentication token', 'explain_token' => 'Allows to access RSS output without authentication.
    %s?token=%s', + 'archiving_configuration' => 'Archiving configuration', + 'delete_articles_every' => 'Remove articles after', + 'archiving_configuration_help' => 'More options are available in the individual stream settings', 'reading_configuration' => 'Reading configuration', 'articles_per_page' => 'Number of articles per page', 'default_view' => 'Default view', diff --git a/app/i18n/fr.php b/app/i18n/fr.php index 85bbeb4b7..b27f29940 100644 --- a/app/i18n/fr.php +++ b/app/i18n/fr.php @@ -137,7 +137,8 @@ return array ( 'feed_url' => 'URL du flux', 'articles' => 'articles', 'number_articles' => 'Nombre d’articles', - 'keep_history' => 'Garder les vieux articles ?', + 'keep_history' => 'Nombre minimum d’articles à conserver', + 'keep_history_help' => 'Mettre à -1 pour tout conserver', 'categorize' => 'Ranger dans une catégorie', 'truncate' => 'Supprimer tous les articles', 'advanced' => 'Avancé', @@ -157,13 +158,16 @@ return array ( 'general_configuration' => 'Configuration générale', 'language' => 'Langue', - 'delete_articles_every' => 'Supprimer les articles après', + 'month' => 'mois', 'default_user' => 'Nom de l’utilisateur par défaut (16 caractères alphanumériques maximum)', 'persona_connection_email' => 'Adresse courriel de connexion (utilise Mozilla Persona)', 'allow_anonymous' => 'Autoriser la lecture anonyme', 'auth_token' => 'Jeton d’identification', 'explain_token' => 'Permet d’accéder à la sortie RSS sans besoin de s’authentifier.
    %s?output=rss&token=%s', + 'archiving_configuration' => 'Configuration de l’archivage', + 'delete_articles_every' => 'Supprimer les articles après', + 'archiving_configuration_help' => 'D’autres options sont disponibles dans la configuration individuelle des flux', 'reading_configuration' => 'Configuration de lecture', 'articles_per_page' => 'Nombre d’articles par page', 'default_view' => 'Vue par défaut', diff --git a/app/views/configure/display.phtml b/app/views/configure/display.phtml index ad35d7c71..68ef26bbf 100644 --- a/app/views/configure/display.phtml +++ b/app/views/configure/display.phtml @@ -31,13 +31,6 @@
    -
    - -
    - -
    -
    -
    conf->mailLogin (); ?> @@ -59,7 +52,27 @@
    - + + + +
    + +
    + + + + + + + + + + +
    + +
    +
    +
    diff --git a/app/views/configure/feed.phtml b/app/views/configure/feed.phtml index 4504b8d76..3fe7d1b3a 100644 --- a/app/views/configure/feed.phtml +++ b/app/views/configure/feed.phtml @@ -52,6 +52,9 @@
    + + +
    @@ -64,10 +67,21 @@
    flux->nbEntries (); ?> - +
    +
    +
    + +
    + + + + + + + + + +
    diff --git a/public/install.php b/public/install.php index afef7e473..3885f143e 100644 --- a/public/install.php +++ b/public/install.php @@ -93,7 +93,7 @@ FROM `%1$scategory006` ORDER BY id2; INSERT IGNORE INTO `%2$sfeed` (url, category, name, website, description, priority, pathEntries, httpAuth, keep_history) -SELECT url, category2, name, website, description, priority, pathEntries, httpAuth, keep_history +SELECT url, category2, name, website, description, priority, pathEntries, httpAuth, -1 * keep_history FROM `%1$sfeed006` ORDER BY id2; -- cgit v1.2.3 From a4b890b67fb4d97c97a2b1b455c327ce4b905194 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Wed, 25 Dec 2013 14:29:29 +0100 Subject: Suite gestion plus fine de l'historique --- app/Controllers/configureController.php | 7 +------ app/Models/FeedDAO.php | 2 +- app/views/configure/display.phtml | 37 ++++++++++++++++++++++++++------- app/views/configure/feed.phtml | 12 +++++++---- 4 files changed, 39 insertions(+), 19 deletions(-) (limited to 'app/Models') diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php index 8d3e02d3e..b83501f0b 100755 --- a/app/Controllers/configureController.php +++ b/app/Controllers/configureController.php @@ -97,18 +97,13 @@ class FreshRSS_configure_Controller extends Minz_ActionController { $description = sanitizeHTML(Minz_Request::param('description', '', true)); $website = Minz_Request::param('website', ''); $url = Minz_Request::param('url', ''); - $hist = Minz_Request::param ('keep_history', 'no'); + $keep_history = intval(Minz_Request::param ('keep_history', 0)); $cat = Minz_Request::param ('category', 0); $path = Minz_Request::param ('path_entries', ''); $priority = Minz_Request::param ('priority', 0); $user = Minz_Request::param ('http_user', ''); $pass = Minz_Request::param ('http_pass', ''); - $keep_history = false; - if ($hist == 'yes') { - $keep_history = true; - } - $httpAuth = ''; if ($user != '' || $pass != '') { $httpAuth = $user . ':' . $pass; diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php index 451fc3850..7d91a032a 100644 --- a/app/Models/FeedDAO.php +++ b/app/Models/FeedDAO.php @@ -193,7 +193,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { } public function listFeedsOrderUpdate () { - $sql = 'SELECT id, url, pathEntries, httpAuth, keep_history FROM `' . $this->prefix . 'feed` ORDER BY lastUpdate'; + $sql = 'SELECT id, name, url, pathEntries, httpAuth, keep_history FROM `' . $this->prefix . 'feed` ORDER BY lastUpdate'; $stm = $this->bd->prepare ($sql); $stm->execute (); diff --git a/app/views/configure/display.phtml b/app/views/configure/display.phtml index 68ef26bbf..8995dc839 100644 --- a/app/views/configure/display.phtml +++ b/app/views/configure/display.phtml @@ -53,6 +53,13 @@
    +
    +
    + + +
    +
    +
    @@ -73,6 +80,13 @@
    +
    +
    + + +
    +
    +
    @@ -173,6 +187,13 @@
    +
    +
    + + +
    +
    +
    @@ -206,7 +227,14 @@ -
    conf->bottomlineLink () ? ' checked="checked"' : ''; ?> />
    +
    +
    + +
    +
    + + +
    @@ -220,12 +248,5 @@ - -
    -
    - - -
    -
    diff --git a/app/views/configure/feed.phtml b/app/views/configure/feed.phtml index 3fe7d1b3a..5940055ed 100644 --- a/app/views/configure/feed.phtml +++ b/app/views/configure/feed.phtml @@ -52,6 +52,12 @@ +
    +
    + + +
    +
    @@ -84,9 +90,9 @@ -
    - +
    +
    @@ -123,11 +129,9 @@
    -
    -
    -- cgit v1.2.3 From 06d4b8d10247146d9c6f7c78ff9fc584438dd8fe Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Wed, 25 Dec 2013 17:37:52 +0100 Subject: Option globale pour la taille minimale de l'historique par défaut MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plus une réorganisation des options --- app/Controllers/configureController.php | 5 ++- app/Controllers/feedController.php | 9 +++-- app/Controllers/indexController.php | 5 +-- app/Models/Configuration.php | 14 +++++++- app/Models/ConfigurationDAO.php | 8 +++-- app/Models/EntryDAO.php | 8 +++-- app/Models/Feed.php | 4 +-- app/Models/FeedDAO.php | 4 +-- app/i18n/en.php | 8 +++-- app/i18n/fr.php | 8 +++-- app/views/configure/display.phtml | 35 ++++++++++++------- app/views/configure/feed.phtml | 62 ++++++++++++++++++--------------- public/install.php | 4 +-- public/themes/default/freshrss.css | 4 +++ public/themes/default/global.css | 3 ++ public/themes/flat-design/freshrss.css | 4 +++ public/themes/flat-design/global.css | 3 ++ 17 files changed, 124 insertions(+), 64 deletions(-) (limited to 'app/Models') diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php index b83501f0b..762134dd0 100755 --- a/app/Controllers/configureController.php +++ b/app/Controllers/configureController.php @@ -97,7 +97,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController { $description = sanitizeHTML(Minz_Request::param('description', '', true)); $website = Minz_Request::param('website', ''); $url = Minz_Request::param('url', ''); - $keep_history = intval(Minz_Request::param ('keep_history', 0)); + $keep_history = intval(Minz_Request::param ('keep_history', -2)); $cat = Minz_Request::param ('category', 0); $path = Minz_Request::param ('path_entries', ''); $priority = Minz_Request::param ('priority', 0); @@ -160,6 +160,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController { $lazyload = Minz_Request::param ('lazyload', 'no'); $sort = Minz_Request::param ('sort_order', 'DESC'); $old = Minz_Request::param ('old_entries', 3); + $keepHistoryDefault = Minz_Request::param('keep_history_default', 0); $mail = Minz_Request::param ('mail_login', false); $anon = Minz_Request::param ('anon_access', 'no'); $token = Minz_Request::param ('token', $current_token); @@ -189,6 +190,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController { $this->view->conf->_lazyload ($lazyload); $this->view->conf->_sortOrder ($sort); $this->view->conf->_oldEntries ($old); + $this->view->conf->_keepHistoryDefault($keepHistoryDefault); $this->view->conf->_mailLogin ($mail); $this->view->conf->_anonAccess ($anon); $this->view->conf->_token ($token); @@ -221,6 +223,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController { 'lazyload' => $this->view->conf->lazyload (), 'sort_order' => $this->view->conf->sortOrder (), 'old_entries' => $this->view->conf->oldEntries (), + 'keep_history_default' => $this->view->conf->keepHistoryDefault(), 'mail_login' => $this->view->conf->mailLogin (), 'anon_access' => $this->view->conf->anonAccess (), 'token' => $this->view->conf->token (), diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index e7d9c97c3..836044da6 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -223,8 +223,13 @@ class FreshRSS_feed_Controller extends Minz_ActionController { } } - if (($feed->keepHistory() >= 0) && (rand(0, 30) === 1)) { - $nb = $feedDAO->cleanOldEntries ($feed->id (), $date_min, max($feed->keepHistory(), count($entries) + 10)); + $feedHistory = $feed->keepHistory(); + if ($feedHistory == -2) { //default + $feedHistory = $this->view->conf->keepHistoryDefault(); + } + + if (($feedHistory >= 0) && (rand(0, 30) === 1)) { + $nb = $feedDAO->cleanOldEntries ($feed->id (), $date_min, max($feedHistory, count($entries) + 10)); if ($nb > 0) { Minz_Log::record ($nb . ' old entries cleaned in feed ' . $feed->id (), Minz_Log::DEBUG); } diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php index e3c253518..6c0ba9058 100755 --- a/app/Controllers/indexController.php +++ b/app/Controllers/indexController.php @@ -130,16 +130,17 @@ class FreshRSS_index_Controller extends Minz_ActionController { // on calcule la date des articles les plus anciens qu'on affiche $nb_month_old = $this->view->conf->oldEntries (); $date_min = $today - (3600 * 24 * 30 * $nb_month_old); //Do not use a fast changing value such as time() to allow SQL caching + $keepHistoryDefault = $this->view->conf->keepHistoryDefault(); try { - $entries = $this->entryDAO->listWhere($getType, $getId, $state, $order, $nb + 1, $first, $filter, $date_min); + $entries = $this->entryDAO->listWhere($getType, $getId, $state, $order, $nb + 1, $first, $filter, $date_min, $keepHistoryDefault); // Si on a récupéré aucun article "non lus" // on essaye de récupérer tous les articles if ($state === 'not_read' && empty($entries)) { //TODO: Remove in v0.8 Minz_Log::record ('Conflicting information about nbNotRead!', Minz_Log::DEBUG); $this->view->state = 'all'; - $entries = $this->entryDAO->listWhere($getType, $getId, 'all', $order, $nb, $first, $filter, $date_min); + $entries = $this->entryDAO->listWhere($getType, $getId, 'all', $order, $nb, $first, $filter, $date_min, $keepHistoryDefault); } if (count($entries) <= $nb) { diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php index d5f69601f..47509636f 100644 --- a/app/Models/Configuration.php +++ b/app/Models/Configuration.php @@ -14,6 +14,7 @@ class FreshRSS_Configuration extends Minz_Model { private $lazyload; private $sort_order; private $old_entries; + private $keep_history_default; private $shortcuts = array (); private $mail_login = ''; private $mark_when = array (); @@ -44,6 +45,7 @@ class FreshRSS_Configuration extends Minz_Model { $this->_lazyload ($confDAO->lazyload); $this->_sortOrder ($confDAO->sort_order); $this->_oldEntries ($confDAO->old_entries); + $this->_keepHistoryDefault($confDAO->keep_history_default); $this->_shortcuts ($confDAO->shortcuts); $this->_mailLogin ($confDAO->mail_login); $this->_markWhen ($confDAO->mark_when); @@ -95,6 +97,9 @@ class FreshRSS_Configuration extends Minz_Model { public function oldEntries () { return $this->old_entries; } + public function keepHistoryDefault() { + return $this->keep_history_default; + } public function shortcuts () { return $this->shortcuts; } @@ -217,11 +222,18 @@ class FreshRSS_Configuration extends Minz_Model { } public function _oldEntries ($value) { if (ctype_digit ($value) && $value > 0) { - $this->old_entries = $value; + $this->old_entries = intval($value); } else { $this->old_entries = 3; } } + public function _keepHistoryDefault($value) { + if (ctype_digit($value) && $value >= -1) { + $this->keep_history_default = intval($value); + } else { + $this->keep_history_default = 0; + } + } public function _shortcuts ($values) { foreach ($values as $key => $value) { $this->shortcuts[$key] = $value; diff --git a/app/Models/ConfigurationDAO.php b/app/Models/ConfigurationDAO.php index 0eebf2d90..91210e701 100644 --- a/app/Models/ConfigurationDAO.php +++ b/app/Models/ConfigurationDAO.php @@ -10,6 +10,7 @@ class FreshRSS_ConfigurationDAO extends Minz_ModelArray { public $lazyload = 'yes'; public $sort_order = 'DESC'; public $old_entries = 3; + public $keep_history_default = 0; public $shortcuts = array ( 'mark_read' => 'r', 'mark_favorite' => 'f', @@ -62,7 +63,7 @@ class FreshRSS_ConfigurationDAO extends Minz_ModelArray { $this->language = $this->array['language']; } if (isset ($this->array['posts_per_page'])) { - $this->posts_per_page = $this->array['posts_per_page']; + $this->posts_per_page = intval($this->array['posts_per_page']); } if (isset ($this->array['view_mode'])) { $this->view_mode = $this->array['view_mode']; @@ -83,7 +84,10 @@ class FreshRSS_ConfigurationDAO extends Minz_ModelArray { $this->sort_order = $this->array['sort_order']; } if (isset ($this->array['old_entries'])) { - $this->old_entries = $this->array['old_entries']; + $this->old_entries = intval($this->array['old_entries']); + } + if (isset ($this->array['keep_history_default'])) { + $this->keep_history_default = intval($this->array['keep_history_default']); } if (isset ($this->array['shortcuts'])) { $this->shortcuts = array_merge ( diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index f0207e96d..14d3ddcff 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -260,7 +260,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { return isset ($entries[0]) ? $entries[0] : false; } - public function listWhere($type = 'a', $id = '', $state = 'all', $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0) { + public function listWhere($type = 'a', $id = '', $state = 'all', $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $keepHistoryDefault = 0) { $where = ''; $joinFeed = false; $values = array(); @@ -307,7 +307,11 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { $where .= 'AND e1.id ' . ($order === 'DESC' ? '<=' : '>=') . $firstId . ' '; } if (($date_min > 0) && ($type !== 's')) { - $where .= 'AND (e1.id >= ' . $date_min . '000000 OR e1.is_favorite = 1 OR f.keep_history <> 0) '; + $where .= 'AND (e1.id >= ' . $date_min . '000000 OR e1.is_favorite = 1 OR (f.keep_history <> 0'; + if (intval($keepHistoryDefault) === 0) { + $where .= ' AND f.keep_history <> -2'; //default + } + $where .= ')) '; $joinFeed = true; } $search = ''; diff --git a/app/Models/Feed.php b/app/Models/Feed.php index 5bdf5e6d7..ef554e083 100644 --- a/app/Models/Feed.php +++ b/app/Models/Feed.php @@ -15,7 +15,7 @@ class FreshRSS_Feed extends Minz_Model { private $pathEntries = ''; private $httpAuth = ''; private $error = false; - private $keep_history = 0; + private $keep_history = -2; public function __construct ($url, $validate=true) { if ($validate) { @@ -168,7 +168,7 @@ class FreshRSS_Feed extends Minz_Model { public function _keepHistory ($value) { $value = intval($value); $value = min($value, 1000000); - $value = max($value, -1); + $value = max($value, -2); $this->keep_history = $value; } public function _nbNotRead ($value) { diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php index 7d91a032a..c1d1f24e8 100644 --- a/app/Models/FeedDAO.php +++ b/app/Models/FeedDAO.php @@ -2,7 +2,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { public function addFeed ($valuesTmp) { - $sql = 'INSERT INTO `' . $this->prefix . 'feed` (url, category, name, website, description, lastUpdate, priority, httpAuth, error, keep_history) VALUES(?, ?, ?, ?, ?, ?, 10, ?, 0, 0)'; + $sql = 'INSERT INTO `' . $this->prefix . 'feed` (url, category, name, website, description, lastUpdate, priority, httpAuth, error, keep_history) VALUES(?, ?, ?, ?, ?, ?, 10, ?, 0, -2)'; $stm = $this->bd->prepare ($sql); $values = array ( @@ -326,7 +326,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { $myFeed->_pathEntries (isset($dao['pathEntries']) ? $dao['pathEntries'] : ''); $myFeed->_httpAuth (isset($dao['httpAuth']) ? base64_decode ($dao['httpAuth']) : ''); $myFeed->_error ($dao['error']); - $myFeed->_keepHistory (isset($dao['keep_history']) ? $dao['keep_history'] : 0); + $myFeed->_keepHistory(isset($dao['keep_history']) ? $dao['keep_history'] : -2); $myFeed->_nbNotRead ($dao['cache_nbUnreads']); $myFeed->_nbEntries ($dao['cache_nbEntries']); if (isset ($dao['id'])) { diff --git a/app/i18n/en.php b/app/i18n/en.php index b6417d8db..ca72d885c 100644 --- a/app/i18n/en.php +++ b/app/i18n/en.php @@ -137,8 +137,9 @@ return array ( 'feed_url' => 'Feed URL', 'articles' => 'articles', 'number_articles' => 'Number of articles', + 'by_feed' => 'by feed', + 'by_default' => 'By default', 'keep_history' => 'Minimum number of articles to keep', - 'keep_history_help' => 'Set to -1 to keep everything', 'categorize' => 'Store in a category', 'truncate' => 'Delete all articles', 'advanced' => 'Advanced', @@ -164,10 +165,11 @@ return array ( 'allow_anonymous' => 'Allow anonymous reading', 'auth_token' => 'Authentication token', 'explain_token' => 'Allows to access RSS output without authentication.
    %s?token=%s', - 'archiving_configuration' => 'Archiving configuration', + 'login_configuration' => 'Login', + 'archiving_configuration' => 'Archiving', 'delete_articles_every' => 'Remove articles after', 'archiving_configuration_help' => 'More options are available in the individual stream settings', - 'reading_configuration' => 'Reading configuration', + 'reading_configuration' => 'Reading', 'articles_per_page' => 'Number of articles per page', 'default_view' => 'Default view', 'sort_order' => 'Sort order', diff --git a/app/i18n/fr.php b/app/i18n/fr.php index b27f29940..053a97c8a 100644 --- a/app/i18n/fr.php +++ b/app/i18n/fr.php @@ -137,8 +137,9 @@ return array ( 'feed_url' => 'URL du flux', 'articles' => 'articles', 'number_articles' => 'Nombre d’articles', + 'by_feed' => 'par flux', + 'by_default' => 'Par défaut', 'keep_history' => 'Nombre minimum d’articles à conserver', - 'keep_history_help' => 'Mettre à -1 pour tout conserver', 'categorize' => 'Ranger dans une catégorie', 'truncate' => 'Supprimer tous les articles', 'advanced' => 'Avancé', @@ -165,10 +166,11 @@ return array ( 'allow_anonymous' => 'Autoriser la lecture anonyme', 'auth_token' => 'Jeton d’identification', 'explain_token' => 'Permet d’accéder à la sortie RSS sans besoin de s’authentifier.
    %s?output=rss&token=%s', - 'archiving_configuration' => 'Configuration de l’archivage', + 'login_configuration' => 'Identification', + 'archiving_configuration' => 'Archivage', 'delete_articles_every' => 'Supprimer les articles après', 'archiving_configuration_help' => 'D’autres options sont disponibles dans la configuration individuelle des flux', - 'reading_configuration' => 'Configuration de lecture', + 'reading_configuration' => 'Lecture', 'articles_per_page' => 'Nombre d’articles par page', 'default_view' => 'Vue par défaut', 'sort_order' => 'Ordre de tri', diff --git a/app/views/configure/display.phtml b/app/views/configure/display.phtml index 8995dc839..3280f657f 100644 --- a/app/views/configure/display.phtml +++ b/app/views/configure/display.phtml @@ -31,6 +31,15 @@ +
    +
    + + +
    +
    + + +
    conf->mailLogin (); ?> @@ -61,22 +70,22 @@
    - +

    +
    - - - - - - - - - - -
    - + +
    +
    +
    + +
    +
    diff --git a/app/views/configure/feed.phtml b/app/views/configure/feed.phtml index 5940055ed..e738ab64f 100644 --- a/app/views/configure/feed.phtml +++ b/app/views/configure/feed.phtml @@ -52,6 +52,15 @@ +
    + +
    + +
    +
    @@ -78,16 +87,11 @@
    - - - - - - - - - - +
    @@ -97,24 +101,7 @@
    - -
    - -
    - -
    -
    -
    - -
    - - -
    -
    - + flux->httpAuth (false); ?>
    @@ -131,7 +118,24 @@
    - + + +
    +
    + + +
    + +
    + + +
    +
    + +
    +
    + +
    diff --git a/public/install.php b/public/install.php index 3885f143e..13abb010b 100644 --- a/public/install.php +++ b/public/install.php @@ -33,7 +33,7 @@ define ('SQL_FEED', 'CREATE TABLE IF NOT EXISTS `%1$sfeed` ( `pathEntries` varchar(511) DEFAULT NULL, `httpAuth` varchar(511) DEFAULT NULL, `error` boolean DEFAULT 0, - `keep_history` MEDIUMINT NOT NULL DEFAULT 0, + `keep_history` MEDIUMINT NOT NULL DEFAULT -2, -- v0.7, -2 = default `cache_nbEntries` int DEFAULT 0, -- v0.7 `cache_nbUnreads` int DEFAULT 0, -- v0.7 PRIMARY KEY (`id`), @@ -93,7 +93,7 @@ FROM `%1$scategory006` ORDER BY id2; INSERT IGNORE INTO `%2$sfeed` (url, category, name, website, description, priority, pathEntries, httpAuth, keep_history) -SELECT url, category2, name, website, description, priority, pathEntries, httpAuth, -1 * keep_history +SELECT url, category2, name, website, description, priority, pathEntries, httpAuth, IF(keep_history = 1, -1, -2) FROM `%1$sfeed006` ORDER BY id2; diff --git a/public/themes/default/freshrss.css b/public/themes/default/freshrss.css index e3c4c3c3b..2b157b27a 100644 --- a/public/themes/default/freshrss.css +++ b/public/themes/default/freshrss.css @@ -667,6 +667,10 @@ padding:.5em; } +select.number option { + text-align:right; +} + @media(max-width: 840px) { .header, .aside .btn-important, diff --git a/public/themes/default/global.css b/public/themes/default/global.css index 1c554d2dc..440fc6e41 100644 --- a/public/themes/default/global.css +++ b/public/themes/default/global.css @@ -99,6 +99,9 @@ input, select, textarea { vertical-align: middle; box-shadow: 0 2px 2px #eee inset; } + option { + padding:0 .5em 0 .5em; + } input[type="radio"], input[type="checkbox"] { width: 15px !important; diff --git a/public/themes/flat-design/freshrss.css b/public/themes/flat-design/freshrss.css index fa1ed13e6..7e3f4c81a 100644 --- a/public/themes/flat-design/freshrss.css +++ b/public/themes/flat-design/freshrss.css @@ -662,6 +662,10 @@ body { padding:.5em; } +select.number option { + text-align:right; +} + @media(max-width: 840px) { .header, .aside .btn-important, diff --git a/public/themes/flat-design/global.css b/public/themes/flat-design/global.css index 8cf6412b3..90b59d002 100644 --- a/public/themes/flat-design/global.css +++ b/public/themes/flat-design/global.css @@ -101,6 +101,9 @@ input, select, textarea { vertical-align: middle; border-radius: 5px; } + option { + padding:0 .5em 0 .5em; + } input[type="radio"], input[type="checkbox"] { width: 15px !important; -- cgit v1.2.3 From c2375265c040da5b71c4acf2871f5479fab3044c Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Wed, 25 Dec 2013 18:18:14 +0100 Subject: Fin taille historique MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Permet d'éviter les problèmes de flux vides à l'importation https://github.com/marienfressinaud/FreshRSS/issues/332 , ou de nombre d'articles non-lus qui ne correspondent pas au nombre d'articles affichés --- app/Models/EntryDAO.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/Models') diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index 14d3ddcff..99aaf3a0d 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -307,7 +307,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { $where .= 'AND e1.id ' . ($order === 'DESC' ? '<=' : '>=') . $firstId . ' '; } if (($date_min > 0) && ($type !== 's')) { - $where .= 'AND (e1.id >= ' . $date_min . '000000 OR e1.is_favorite = 1 OR (f.keep_history <> 0'; + $where .= 'AND (e1.id >= ' . $date_min . '000000 OR e1.is_read = 0 OR e1.is_favorite = 1 OR (f.keep_history <> 0'; if (intval($keepHistoryDefault) === 0) { $where .= ' AND f.keep_history <> -2'; //default } -- cgit v1.2.3 From 0b3d79745d7b8db5dc684f91e0bbad6c1a03d0ee Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Wed, 25 Dec 2013 18:50:05 +0100 Subject: Encodage titre flux pour cas Glazman --- app/Models/Feed.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'app/Models') diff --git a/app/Models/Feed.php b/app/Models/Feed.php index ef554e083..dcf97d4ec 100644 --- a/app/Models/Feed.php +++ b/app/Models/Feed.php @@ -250,11 +250,11 @@ class FreshRSS_Feed extends Minz_Model { $this->_url ($subscribe_url); } - $title = $feed->get_title (); + $title = htmlspecialchars(html_only_entity_decode($feed->get_title()), ENT_COMPAT, 'UTF-8'); $this->_name (!is_null ($title) ? $title : $this->url); - $this->_website ($feed->get_link ()); - $this->_description ($feed->get_description ()); + $this->_website(html_only_entity_decode($feed->get_link())); + $this->_description(html_only_entity_decode($feed->get_description())); // et on charge les articles du flux $this->loadEntries ($feed); -- cgit v1.2.3 From 7eda2793bbc3210ae37aa66511fd7ad7661c2149 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Wed, 25 Dec 2013 19:53:09 +0100 Subject: Nouveau bouton pour lancer manuellement la purge des vieux articles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Attention, si on supprime des articles qui sont encore dans les flux RSS, ils risquent de réapparaitre en cas de date manquante ou erronée, ou si l'utilisateur augmente la date d'expiration. Ce bouton est plus strict que la purge automatique qui conserve toujours au moins le même nombre d'articles que dans le flux RSS en cours + 10. --- app/Controllers/entryController.php | 41 +++++++++++++++++++++++++++++++++++++ app/Controllers/feedController.php | 2 +- app/Models/FeedDAO.php | 27 +++++++++++++----------- app/i18n/en.php | 2 ++ app/i18n/fr.php | 2 ++ app/views/configure/display.phtml | 1 + 6 files changed, 62 insertions(+), 13 deletions(-) (limited to 'app/Models') diff --git a/app/Controllers/entryController.php b/app/Controllers/entryController.php index a332ca8a9..1c3c56c4d 100755 --- a/app/Controllers/entryController.php +++ b/app/Controllers/entryController.php @@ -109,4 +109,45 @@ class FreshRSS_entry_Controller extends Minz_ActionController { 'a' => 'display' ), true); } + + public function purgeAction() { + @set_time_limit(300); + + $nb_month_old = max($this->view->conf->oldEntries(), 1); + $date_min = time() - (3600 * 24 * 30 * $nb_month_old); + + $feedDAO = new FreshRSS_FeedDAO(); + $feeds = $feedDAO->listFeedsOrderUpdate(); + $nbTotal = 0; + + invalidateHttpCache(); + + foreach ($feeds as $feed) { + $feedHistory = $feed->keepHistory(); + if ($feedHistory == -2) { //default + $feedHistory = $this->view->conf->keepHistoryDefault(); + } + if ($feedHistory >= 0) { + $nb = $feedDAO->cleanOldEntries($feed->id(), $date_min, $feedHistory); + if ($nb > 0) { + $nbTotal += $nb; + Minz_Log::record($nb . ' old entries cleaned in feed ' . $feed->id(), Minz_Log::DEBUG); + $feedDAO->updateLastUpdate($feed->id()); + } + } + } + + invalidateHttpCache(); + + $notif = array( + 'type' => 'good', + 'content' => Minz_Translate::t('purge_completed', $nbTotal) + ); + Minz_Session::_param('notification', $notif); + + Minz_Request::forward(array( + 'c' => 'configure', + 'a' => 'display' + ), true); + } } diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index 836044da6..04d0aa98b 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -192,7 +192,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController { } // on calcule la date des articles les plus anciens qu'on accepte - $nb_month_old = $this->view->conf->oldEntries (); + $nb_month_old = max($this->view->conf->oldEntries(), 1); $date_min = time () - (3600 * 24 * 30 * $nb_month_old); $i = 0; diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php index c1d1f24e8..d517f9580 100644 --- a/app/Models/FeedDAO.php +++ b/app/Models/FeedDAO.php @@ -315,20 +315,23 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { if (isset ($dao['id'])) { $key = $dao['id']; } + if ($catID === null) { + $catID = isset($dao['category']) ? $dao['category'] : 0; + } - $myFeed = new FreshRSS_Feed (isset($dao['url']) ? $dao['url'] : '', false); - $myFeed->_category ($catID === null ? $dao['category'] : $catID); - $myFeed->_name ($dao['name']); - $myFeed->_website ($dao['website'], false); - $myFeed->_description (isset($dao['description']) ? $dao['description'] : ''); - $myFeed->_lastUpdate (isset($dao['lastUpdate']) ? $dao['lastUpdate'] : 0); - $myFeed->_priority ($dao['priority']); - $myFeed->_pathEntries (isset($dao['pathEntries']) ? $dao['pathEntries'] : ''); - $myFeed->_httpAuth (isset($dao['httpAuth']) ? base64_decode ($dao['httpAuth']) : ''); - $myFeed->_error ($dao['error']); + $myFeed = new FreshRSS_Feed(isset($dao['url']) ? $dao['url'] : '', false); + $myFeed->_category(intval($catID)); + $myFeed->_name($dao['name']); + $myFeed->_website(isset($dao['website']) ? $dao['website'] : '', false); + $myFeed->_description(isset($dao['description']) ? $dao['description'] : ''); + $myFeed->_lastUpdate(isset($dao['lastUpdate']) ? $dao['lastUpdate'] : 0); + $myFeed->_priority(isset($dao['priority']) ? $dao['priority'] : 10); + $myFeed->_pathEntries(isset($dao['pathEntries']) ? $dao['pathEntries'] : ''); + $myFeed->_httpAuth(isset($dao['httpAuth']) ? base64_decode ($dao['httpAuth']) : ''); + $myFeed->_error(isset($dao['error']) ? $dao['error'] : 0); $myFeed->_keepHistory(isset($dao['keep_history']) ? $dao['keep_history'] : -2); - $myFeed->_nbNotRead ($dao['cache_nbUnreads']); - $myFeed->_nbEntries ($dao['cache_nbEntries']); + $myFeed->_nbNotRead(isset($dao['cache_nbUnreads']) ? $dao['cache_nbUnreads'] : 0); + $myFeed->_nbEntries(isset($dao['cache_nbEntries']) ? $dao['cache_nbEntries'] : 0); if (isset ($dao['id'])) { $myFeed->_id ($dao['id']); } diff --git a/app/i18n/en.php b/app/i18n/en.php index ca72d885c..498fccd14 100644 --- a/app/i18n/en.php +++ b/app/i18n/en.php @@ -168,6 +168,8 @@ return array ( 'login_configuration' => 'Login', 'archiving_configuration' => 'Archiving', 'delete_articles_every' => 'Remove articles after', + 'purge_now' => 'Purge now', + 'purge_completed' => 'Purge completed (%d articles deleted)', 'archiving_configuration_help' => 'More options are available in the individual stream settings', 'reading_configuration' => 'Reading', 'articles_per_page' => 'Number of articles per page', diff --git a/app/i18n/fr.php b/app/i18n/fr.php index 053a97c8a..c918daa44 100644 --- a/app/i18n/fr.php +++ b/app/i18n/fr.php @@ -169,6 +169,8 @@ return array ( 'login_configuration' => 'Identification', 'archiving_configuration' => 'Archivage', 'delete_articles_every' => 'Supprimer les articles après', + 'purge_now' => 'Purger maintenant', + 'purge_completed' => 'Purge effectuée (%d articles supprimés)', 'archiving_configuration_help' => 'D’autres options sont disponibles dans la configuration individuelle des flux', 'reading_configuration' => 'Lecture', 'articles_per_page' => 'Nombre d’articles par page', diff --git a/app/views/configure/display.phtml b/app/views/configure/display.phtml index 3280f657f..fca533752 100644 --- a/app/views/configure/display.phtml +++ b/app/views/configure/display.phtml @@ -76,6 +76,7 @@
    +  
    -- cgit v1.2.3 From 2788aaeb1a463012cfa90bf31f5efb4bf6ca7344 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Thu, 26 Dec 2013 02:50:58 +0100 Subject: Problème ctype_digit qui ne marche pas sur des variables qui sont déjà des entiers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Models/Configuration.php | 16 +++++----------- app/Models/Entry.php | 7 ++----- app/Models/Feed.php | 7 ++++--- 3 files changed, 11 insertions(+), 19 deletions(-) (limited to 'app/Models') diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php index 47509636f..cb2f90655 100644 --- a/app/Models/Configuration.php +++ b/app/Models/Configuration.php @@ -220,19 +220,13 @@ class FreshRSS_Configuration extends Minz_Model { public function _sortOrder ($value) { $this->sort_order = $value === 'ASC' ? 'ASC' : 'DESC'; } - public function _oldEntries ($value) { - if (ctype_digit ($value) && $value > 0) { - $this->old_entries = intval($value); - } else { - $this->old_entries = 3; - } + public function _oldEntries($value) { + $value = intval($value); + $this->old_entries = $value > 0 ? $value : 3; } public function _keepHistoryDefault($value) { - if (ctype_digit($value) && $value >= -1) { - $this->keep_history_default = intval($value); - } else { - $this->keep_history_default = 0; - } + $value = intval($value); + $this->keep_history_default = $value >= -1 ? $value : 0; } public function _shortcuts ($values) { foreach ($values as $key => $value) { diff --git a/app/Models/Entry.php b/app/Models/Entry.php index ed31ef2b1..ab9605eb1 100644 --- a/app/Models/Entry.php +++ b/app/Models/Entry.php @@ -106,11 +106,8 @@ class FreshRSS_Entry extends Minz_Model { $this->link = $value; } public function _date ($value) { - if (ctype_digit ($value)) { - $this->date = intval ($value); - } else { - $this->date = time (); - } + $value = intval($value); + $this->date = $value > 1 ? $value : time(); } public function _isRead ($value) { $this->is_read = $value; diff --git a/app/Models/Feed.php b/app/Models/Feed.php index dcf97d4ec..3008e33d7 100644 --- a/app/Models/Feed.php +++ b/app/Models/Feed.php @@ -154,7 +154,8 @@ class FreshRSS_Feed extends Minz_Model { $this->lastUpdate = $value; } public function _priority ($value) { - $this->priority = ctype_digit ($value) ? intval ($value) : 10; + $value = intval($value); + $this->priority = $value >= 0 ? $value : 10; } public function _pathEntries ($value) { $this->pathEntries = $value; @@ -172,10 +173,10 @@ class FreshRSS_Feed extends Minz_Model { $this->keep_history = $value; } public function _nbNotRead ($value) { - $this->nbNotRead = ctype_digit ($value) ? intval ($value) : -1; + $this->nbNotRead = intval($value); } public function _nbEntries ($value) { - $this->nbEntries = ctype_digit ($value) ? intval ($value) : -1; + $this->nbEntries = intval($value); } public function load () { -- cgit v1.2.3 From 574d37bddc4e00ddbc6af57c28838e1dea6a730b Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Thu, 26 Dec 2013 19:58:17 +0100 Subject: Favicons compatibles multi-utilisateurs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Contribue à https://github.com/marienfressinaud/FreshRSS/issues/126 --- app/Controllers/feedController.php | 2 +- app/Models/CategoryDAO.php | 2 +- app/Models/Feed.php | 13 +++++++++---- lib/Minz/Configuration.php | 2 +- p/f.php | 2 +- 5 files changed, 13 insertions(+), 8 deletions(-) (limited to 'app/Models') diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index 04d0aa98b..dc8a854a9 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -395,7 +395,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController { 'type' => 'good', 'content' => Minz_Translate::t ('feed_deleted') ); - FreshRSS_Feed::faviconDelete($id); + //TODO: Delete old favicon } else { $notif = array ( 'type' => 'bad', diff --git a/app/Models/CategoryDAO.php b/app/Models/CategoryDAO.php index 6b07ab063..1cc616ac0 100644 --- a/app/Models/CategoryDAO.php +++ b/app/Models/CategoryDAO.php @@ -90,7 +90,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo { if ($prePopulateFeeds) { $sql = 'SELECT c.id AS c_id, c.name AS c_name, ' . ($details ? 'c.color AS c_color, ' : '') - . ($details ? 'f.* ' : 'f.id, f.name, f.website, f.priority, f.error, f.cache_nbEntries, f.cache_nbUnreads ') + . ($details ? 'f.* ' : 'f.id, f.name, f.url, f.website, f.priority, f.error, f.cache_nbEntries, f.cache_nbUnreads ') . 'FROM `' . $this->prefix . 'category` c ' . 'LEFT OUTER JOIN `' . $this->prefix . 'feed` f ON f.category = c.id ' . 'GROUP BY f.id ' diff --git a/app/Models/Feed.php b/app/Models/Feed.php index 3008e33d7..f9a586122 100644 --- a/app/Models/Feed.php +++ b/app/Models/Feed.php @@ -28,6 +28,11 @@ class FreshRSS_Feed extends Minz_Model { public function id () { return $this->id; } + + public function hash() { + return hash('crc32b', Minz_Configuration::salt() . $this->url); + } + public function url () { return $this->url; } @@ -96,7 +101,7 @@ class FreshRSS_Feed extends Minz_Model { return $this->nbNotRead; } public function faviconPrepare() { - $file = DATA_PATH . '/favicons/' . $this->id () . '.txt'; + $file = DATA_PATH . '/favicons/' . $this->hash() . '.txt'; if (!file_exists ($file)) { $t = $this->website; if (empty($t)) { @@ -105,13 +110,13 @@ class FreshRSS_Feed extends Minz_Model { file_put_contents($file, $t); } } - public static function faviconDelete($id) { - $path = DATA_PATH . '/favicons/' . $id; + public static function faviconDelete($hash) { + $path = DATA_PATH . '/favicons/' . $hash; @unlink($path . '.ico'); @unlink($path . '.txt'); } public function favicon () { - return Minz_Url::display ('/f.php?' . $this->id ()); + return Minz_Url::display ('/f.php?' . $this->hash()); } public function _id ($value) { diff --git a/lib/Minz/Configuration.php b/lib/Minz/Configuration.php index 1b108dcdf..6c7206988 100644 --- a/lib/Minz/Configuration.php +++ b/lib/Minz/Configuration.php @@ -63,7 +63,7 @@ class Minz_Configuration { /* * Getteurs */ - public static function selApplication () { + public static function salt () { return self::$sel_application; } public static function environment () { diff --git a/p/f.php b/p/f.php index a56d58617..872b8cc2c 100644 --- a/p/f.php +++ b/p/f.php @@ -37,7 +37,7 @@ function download_favicon ($website, $dest) { } $id = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '0'; -if (!ctype_digit($id)) { +if (!ctype_xdigit($id)) { $id = '0'; } -- cgit v1.2.3 From 2c57e7254d613683ec20076d37b33ea2a7dd2d83 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Thu, 26 Dec 2013 20:11:18 +0100 Subject: Favicons : test pour améliorer le cache HTTP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Test d'utilisation de PATH_INFO plutôt que QUERY_STRING pour améliorer la mise en cache. À tester sur différents serveurs --- app/Models/Feed.php | 2 +- p/f.php | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) (limited to 'app/Models') diff --git a/app/Models/Feed.php b/app/Models/Feed.php index f9a586122..4f90b9872 100644 --- a/app/Models/Feed.php +++ b/app/Models/Feed.php @@ -116,7 +116,7 @@ class FreshRSS_Feed extends Minz_Model { @unlink($path . '.txt'); } public function favicon () { - return Minz_Url::display ('/f.php?' . $this->hash()); + return Minz_Url::display ('/f.php/' . $this->hash()); } public function _id ($value) { diff --git a/p/f.php b/p/f.php index 872b8cc2c..660128a59 100644 --- a/p/f.php +++ b/p/f.php @@ -36,7 +36,14 @@ function download_favicon ($website, $dest) { return true; } -$id = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '0'; +if (isset($_SERVER['PATH_INFO'])) { + $id = substr($_SERVER['PATH_INFO'], 1); +} elseif (isset($_SERVER['QUERY_STRING'])) { + $id = $_SERVER['QUERY_STRING']; +} else { + $id = '0'; +} + if (!ctype_xdigit($id)) { $id = '0'; } -- cgit v1.2.3 From 43fd0a543900866f4feaf0713e1726b98a60b22d Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Thu, 26 Dec 2013 22:20:24 +0100 Subject: Mise à jour de f.url en base de données lorsque SimplePie découvre que l'adresse a changé MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Et correction problème favicon lorsque l'adresse du flux a changé du point de vue de SimplePie. Plus petites optimisations. --- app/Controllers/entryController.php | 2 +- app/Controllers/feedController.php | 27 ++++++++++----------------- app/Models/Feed.php | 18 ++++++++++++------ 3 files changed, 23 insertions(+), 24 deletions(-) (limited to 'app/Models') diff --git a/app/Controllers/entryController.php b/app/Controllers/entryController.php index 1c3c56c4d..26f3422ca 100755 --- a/app/Controllers/entryController.php +++ b/app/Controllers/entryController.php @@ -131,7 +131,7 @@ class FreshRSS_entry_Controller extends Minz_ActionController { $nb = $feedDAO->cleanOldEntries($feed->id(), $date_min, $feedHistory); if ($nb > 0) { $nbTotal += $nb; - Minz_Log::record($nb . ' old entries cleaned in feed ' . $feed->id(), Minz_Log::DEBUG); + Minz_Log::record($nb . ' old entries cleaned in feed [' . $feed->url() . ']', Minz_Log::DEBUG); $feedDAO->updateLastUpdate($feed->id()); } } diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index dc8a854a9..ca230232f 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -20,15 +20,6 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $this->catDAO->checkDefault (); } - private static function entryDateComparer($e1, $e2) { - $d1 = $e1->date(true); - $d2 = $e2->date(true); - if ($d1 === $d2) { - return 0; - } - return ($d1 < $d2) ? -1 : 1; - } - public function addAction () { @set_time_limit(300); @@ -55,7 +46,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController { } $feed->_httpAuth ($httpAuth); - $feed->load (); + $feed->load(true); $feedDAO = new FreshRSS_FeedDAO (); $values = array ( @@ -91,8 +82,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $is_read = $this->view->conf->markUponReception() === 'yes' ? 1 : 0; $entryDAO = new FreshRSS_EntryDAO (); - $entries = $feed->entries (); - usort($entries, 'self::entryDateComparer'); + $entries = array_reverse($feed->entries()); //We want chronological order and SimplePie uses reverse order // on calcule la date des articles les plus anciens qu'on accepte $nb_month_old = $this->view->conf->oldEntries (); @@ -199,10 +189,9 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $flux_update = 0; foreach ($feeds as $feed) { try { - $feed->load (); - $feed->faviconPrepare(); - $entries = $feed->entries (); - usort($entries, 'self::entryDateComparer'); + $url = $feed->url(); + $feed->load(false); + $entries = array_reverse($feed->entries()); //We want chronological order and SimplePie uses reverse order $is_read = $this->view->conf->markUponReception() === 'yes' ? 1 : 0; @@ -231,7 +220,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController { if (($feedHistory >= 0) && (rand(0, 30) === 1)) { $nb = $feedDAO->cleanOldEntries ($feed->id (), $date_min, max($feedHistory, count($entries) + 10)); if ($nb > 0) { - Minz_Log::record ($nb . ' old entries cleaned in feed ' . $feed->id (), Minz_Log::DEBUG); + Minz_Log::record ($nb . ' old entries cleaned in feed [' . $feed->url() . ']', Minz_Log::DEBUG); } } @@ -239,6 +228,10 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $feedDAO->updateLastUpdate ($feed->id ()); $feedDAO->commit (); $flux_update++; + if ($feed->url() !== $url) { //URL has changed (auto-discovery) + $feedDAO->updateFeed($feed->id(), array('url' => $feed->url())); + } + $feed->faviconPrepare(); } catch (FreshRSS_Feed_Exception $e) { Minz_Log::record ($e->getMessage (), Minz_Log::NOTICE); $feedDAO->updateLastUpdate ($feed->id (), 1); diff --git a/app/Models/Feed.php b/app/Models/Feed.php index 4f90b9872..0f467f776 100644 --- a/app/Models/Feed.php +++ b/app/Models/Feed.php @@ -16,6 +16,7 @@ class FreshRSS_Feed extends Minz_Model { private $httpAuth = ''; private $error = false; private $keep_history = -2; + private $hash = null; public function __construct ($url, $validate=true) { if ($validate) { @@ -30,7 +31,10 @@ class FreshRSS_Feed extends Minz_Model { } public function hash() { - return hash('crc32b', Minz_Configuration::salt() . $this->url); + if ($this->hash === null) { + $this->hash = hash('crc32b', Minz_Configuration::salt() . $this->url); + } + return $this->hash; } public function url () { @@ -184,7 +188,7 @@ class FreshRSS_Feed extends Minz_Model { $this->nbEntries = intval($value); } - public function load () { + public function load ($loadDetails = false) { if (!is_null ($this->url)) { if (CACHE_PATH === false) { throw new Minz_FileNotExistException ( @@ -256,11 +260,13 @@ class FreshRSS_Feed extends Minz_Model { $this->_url ($subscribe_url); } - $title = htmlspecialchars(html_only_entity_decode($feed->get_title()), ENT_COMPAT, 'UTF-8'); - $this->_name (!is_null ($title) ? $title : $this->url); + if ($loadDetails) { + $title = htmlspecialchars(html_only_entity_decode($feed->get_title()), ENT_COMPAT, 'UTF-8'); + $this->_name (!is_null ($title) ? $title : $this->url); - $this->_website(html_only_entity_decode($feed->get_link())); - $this->_description(html_only_entity_decode($feed->get_description())); + $this->_website(html_only_entity_decode($feed->get_link())); + $this->_description(html_only_entity_decode($feed->get_description())); + } // et on charge les articles du flux $this->loadEntries ($feed); -- cgit v1.2.3 From b99979cef78f7cd0c1cb4ae81115d09881e85926 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Fri, 27 Dec 2013 14:11:17 +0100 Subject: Bug affichage ID category introduit récemment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Models/Feed.php | 3 ++- app/Models/FeedDAO.php | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) (limited to 'app/Models') diff --git a/app/Models/Feed.php b/app/Models/Feed.php index 0f467f776..32f8546dd 100644 --- a/app/Models/Feed.php +++ b/app/Models/Feed.php @@ -136,7 +136,8 @@ class FreshRSS_Feed extends Minz_Model { $this->url = $value; } public function _category ($value) { - $this->category = $value; + $value = intval($value); + $this->category = $value >= 0 ? $value : 0; } public function _name ($value) { if (is_null ($value)) { diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php index d517f9580..e102da4ec 100644 --- a/app/Models/FeedDAO.php +++ b/app/Models/FeedDAO.php @@ -316,11 +316,13 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { $key = $dao['id']; } if ($catID === null) { - $catID = isset($dao['category']) ? $dao['category'] : 0; + $category = isset($dao['category']) ? $dao['category'] : 0; + } else { + $category = $catID ; } $myFeed = new FreshRSS_Feed(isset($dao['url']) ? $dao['url'] : '', false); - $myFeed->_category(intval($catID)); + $myFeed->_category($category); $myFeed->_name($dao['name']); $myFeed->_website(isset($dao['website']) ? $dao['website'] : '', false); $myFeed->_description(isset($dao['description']) ? $dao['description'] : ''); -- cgit v1.2.3 From 9ac1496d63da32524a33696187342ce061e9ef28 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sat, 28 Dec 2013 13:54:52 +0100 Subject: Bouge anon_access dans config.php MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit L'accès anonyme n'est pas au niveau utilisateur mais au niveau global. Bouge FreshRSS_Configuration::conf->anonAccess() qui était stocké dans *_user.php vers Minz_Configuration::allowAnonymous() stocké dans config.php Contribue à https://github.com/marienfressinaud/FreshRSS/issues/126 + autres optimisations Contribue à https://github.com/marienfressinaud/FreshRSS/issues/260 --- app/Controllers/configureController.php | 11 ++++--- app/Controllers/indexController.php | 6 ++-- app/Models/Configuration.php | 12 ------- app/Models/ConfigurationDAO.php | 4 --- app/layout/header.phtml | 2 +- app/views/configure/users.phtml | 2 +- app/views/index/index.phtml | 2 +- data/.gitignore | 3 +- lib/Minz/Configuration.php | 58 ++++++++++++++++++++++++++------- 9 files changed, 61 insertions(+), 39 deletions(-) (limited to 'app/Models') diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php index 487f6e4ad..dd9674588 100755 --- a/app/Controllers/configureController.php +++ b/app/Controllers/configureController.php @@ -398,15 +398,13 @@ class FreshRSS_configure_Controller extends Minz_ActionController { $current_token = $this->view->conf->token(); $mail = Minz_Request::param('mail_login', false); - $anon = Minz_Request::param('anon_access', 'no'); $token = Minz_Request::param('token', $current_token); + $this->view->conf->_mailLogin($mail); - $this->view->conf->_anonAccess($anon); $this->view->conf->_token($token); $values = array( 'mail_login' => $this->view->conf->mailLogin(), - 'anon_access' => $this->view->conf->anonAccess(), 'token' => $this->view->conf->token(), ); @@ -415,7 +413,12 @@ class FreshRSS_configure_Controller extends Minz_ActionController { Minz_Session::_param('conf', $this->view->conf); Minz_Session::_param('mail', $this->view->conf->mailLogin()); - // notif + $anon = (bool)(Minz_Request::param('anon_access', false)); + if ($anon != Minz_Configuration::allowAnonymous()) { + Minz_Configuration::_allowAnonymous($anon); + Minz_Configuration::writeFile(); + } + $notif = array( 'type' => 'good', 'content' => Minz_Translate::t('configuration_updated') diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php index 6c0ba9058..0c229aedb 100755 --- a/app/Controllers/indexController.php +++ b/app/Controllers/indexController.php @@ -24,7 +24,7 @@ class FreshRSS_index_Controller extends Minz_ActionController { // check if user is log in if(login_is_conf ($this->view->conf) && !is_logged() && - $this->view->conf->anonAccess() === 'no' && + !Minz_Configuration::allowAnonymous() && !($output === 'rss' && $token_is_ok)) { return; } @@ -36,8 +36,8 @@ class FreshRSS_index_Controller extends Minz_ActionController { $params['search'] = urlencode ($params['search']); } if (login_is_conf($this->view->conf) && - $this->view->conf->anonAccess() === 'no' && - $token != '') { + !Minz_Configuration::allowAnonymous() && + $token !== '') { $params['token'] = $token; } $this->view->rss_url = array ( diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php index cb2f90655..7f4be474d 100644 --- a/app/Models/Configuration.php +++ b/app/Models/Configuration.php @@ -20,7 +20,6 @@ class FreshRSS_Configuration extends Minz_Model { private $mark_when = array (); private $sharing = array (); private $theme; - private $anon_access; private $token; private $auto_load_more; private $topline_read; @@ -52,7 +51,6 @@ class FreshRSS_Configuration extends Minz_Model { $this->_sharing ($confDAO->sharing); $this->_theme ($confDAO->theme); FreshRSS_Themes::setThemeId ($confDAO->theme); - $this->_anonAccess ($confDAO->anon_access); $this->_token ($confDAO->token); $this->_autoLoadMore ($confDAO->auto_load_more); $this->_topline_read ($confDAO->topline_read); @@ -132,9 +130,6 @@ class FreshRSS_Configuration extends Minz_Model { public function theme () { return $this->theme; } - public function anonAccess () { - return $this->anon_access; - } public function token () { return $this->token; } @@ -283,13 +278,6 @@ class FreshRSS_Configuration extends Minz_Model { public function _theme ($value) { $this->theme = $value; } - public function _anonAccess ($value) { - if ($value == 'yes') { - $this->anon_access = 'yes'; - } else { - $this->anon_access = 'no'; - } - } public function _token ($value) { $this->token = $value; } diff --git a/app/Models/ConfigurationDAO.php b/app/Models/ConfigurationDAO.php index 91210e701..fa4d3338f 100644 --- a/app/Models/ConfigurationDAO.php +++ b/app/Models/ConfigurationDAO.php @@ -38,7 +38,6 @@ class FreshRSS_ConfigurationDAO extends Minz_ModelArray { 'print' => true ); public $theme = 'default'; - public $anon_access = 'no'; public $token = ''; public $auto_load_more = 'yes'; public $topline_read = 'yes'; @@ -108,9 +107,6 @@ class FreshRSS_ConfigurationDAO extends Minz_ModelArray { if (isset ($this->array['theme'])) { $this->theme = $this->array['theme']; } - if (isset ($this->array['anon_access'])) { - $this->anon_access = $this->array['anon_access']; - } if (isset ($this->array['token'])) { $this->token = $this->array['token']; } diff --git a/app/layout/header.phtml b/app/layout/header.phtml index bba22508e..aeb417a6e 100644 --- a/app/layout/header.phtml +++ b/app/layout/header.phtml @@ -21,7 +21,7 @@ @@ -61,16 +61,16 @@
    @@ -79,9 +79,9 @@
    @@ -89,9 +89,9 @@
    @@ -99,9 +99,9 @@
    @@ -110,19 +110,19 @@
    @@ -132,7 +132,7 @@
    @@ -162,20 +162,20 @@ - conf->toplineRead () ? ' checked="checked"' : ''; ?> /> - conf->toplineFavorite () ? ' checked="checked"' : ''; ?> /> + conf->topline_read ? ' checked="checked"' : ''; ?> /> + conf->topline_favorite ? ' checked="checked"' : ''; ?> /> - conf->toplineDate () ? ' checked="checked"' : ''; ?> /> - conf->toplineLink () ? ' checked="checked"' : ''; ?> /> + conf->topline_date ? ' checked="checked"' : ''; ?> /> + conf->topline_link ? ' checked="checked"' : ''; ?> /> - conf->bottomlineRead () ? ' checked="checked"' : ''; ?> /> - conf->bottomlineFavorite () ? ' checked="checked"' : ''; ?> /> - conf->bottomlineSharing () ? ' checked="checked"' : ''; ?> /> - conf->bottomlineTags () ? ' checked="checked"' : ''; ?> /> - conf->bottomlineDate () ? ' checked="checked"' : ''; ?> /> - conf->bottomlineLink () ? ' checked="checked"' : ''; ?> /> + conf->bottomline_read ? ' checked="checked"' : ''; ?> /> + conf->bottomline_favorite ? ' checked="checked"' : ''; ?> /> + conf->bottomline_sharing ? ' checked="checked"' : ''; ?> /> + conf->bottomline_tags ? ' checked="checked"' : ''; ?> /> + conf->bottomline_date ? ' checked="checked"' : ''; ?> /> + conf->bottomline_link ? ' checked="checked"' : ''; ?> />
    diff --git a/app/views/configure/sharing.phtml b/app/views/configure/sharing.phtml index 825537fc9..c6a96b48a 100644 --- a/app/views/configure/sharing.phtml +++ b/app/views/configure/sharing.phtml @@ -47,7 +47,7 @@ foreach ($services as $service) { ?> diff --git a/app/views/configure/shortcut.phtml b/app/views/configure/shortcut.phtml index e78d91820..2e564a7b6 100644 --- a/app/views/configure/shortcut.phtml +++ b/app/views/configure/shortcut.phtml @@ -9,7 +9,7 @@ - conf->shortcuts (); ?> + conf->shortcuts; ?> diff --git a/app/views/configure/users.phtml b/app/views/configure/users.phtml index c57671ef3..4fd291ba3 100644 --- a/app/views/configure/users.phtml +++ b/app/views/configure/users.phtml @@ -20,7 +20,7 @@
    - conf->mailLogin(); ?> + conf->mail_login; ?>
    @@ -29,7 +29,7 @@
    - conf->token(); ?> + conf->token; ?>
    @@ -51,7 +51,7 @@
    diff --git a/app/views/helpers/javascript_vars.phtml b/app/views/helpers/javascript_vars.phtml index d008e2e48..8f508487c 100644 --- a/app/views/helpers/javascript_vars.phtml +++ b/app/views/helpers/javascript_vars.phtml @@ -1,16 +1,16 @@ conf->markWhen (); + $mark = $this->conf->mark_when; echo 'var ', - 'hide_posts=', ($this->conf->displayPosts () === 'yes' || Minz_Request::param ('output') === 'reader') ? 'false' : 'true', - ',auto_mark_article=', $mark['article'] === 'yes' ? 'true' : 'false', - ',auto_mark_site=', $mark['site'] === 'yes' ? 'true' : 'false', - ',auto_mark_scroll=', $mark['scroll'] === 'yes' ? 'true' : 'false', - ',auto_load_more=', $this->conf->autoLoadMore () === 'yes' ? 'true' : 'false', - ',full_lazyload=', $this->conf->lazyload () === 'yes' && ($this->conf->displayPosts () === 'yes' || Minz_Request::param ('output') === 'reader') ? 'true' : 'false', - ',does_lazyload=', $this->conf->lazyload() === 'yes' ? 'true' : 'false'; + 'hide_posts=', ($this->conf->display_posts || Minz_Request::param('output') === 'reader') ? 'false' : 'true', + ',auto_mark_article=', $mark['article'] ? 'true' : 'false', + ',auto_mark_site=', $mark['site'] ? 'true' : 'false', + ',auto_mark_scroll=', $mark['scroll'] ? 'true' : 'false', + ',auto_load_more=', $this->conf->auto_load_more ? 'true' : 'false', + ',full_lazyload=', $this->conf->lazyload && ($this->conf->display_posts || Minz_Request::param('output') === 'reader') ? 'true' : 'false', + ',does_lazyload=', $this->conf->lazyload ? 'true' : 'false'; - $s = $this->conf->shortcuts (); + $s = $this->conf->shortcuts; echo ',shortcuts={', 'mark_read:"', $s['mark_read'], '",', 'mark_favorite:"', $s['mark_favorite'], '",', diff --git a/app/views/helpers/view/global_view.phtml b/app/views/helpers/view/global_view.phtml index bc6e24e37..58ff13d4e 100644 --- a/app/views/helpers/view/global_view.phtml +++ b/app/views/helpers/view/global_view.phtml @@ -31,6 +31,6 @@
    -
    conf->displayPosts () === 'no' ? ' class="hide_posts"' : ''; ?>> +
    conf->display_posts ? '' : ' class="hide_posts"'; ?>> -
    \ No newline at end of file +
    diff --git a/app/views/helpers/view/normal_view.phtml b/app/views/helpers/view/normal_view.phtml index 4307c2113..f59cae2b8 100644 --- a/app/views/helpers/view/normal_view.phtml +++ b/app/views/helpers/view/normal_view.phtml @@ -18,8 +18,8 @@ if (!empty($this->entries)) { $email = $this->conf->sharing ('email'); $print = $this->conf->sharing ('print'); $today = $this->today; - $hidePosts = $this->conf->displayPosts() === 'no'; - $lazyload = $this->conf->lazyload() === 'yes'; + $hidePosts = !$this->conf->display_posts; + $lazyload = $this->conf->lazyload; ?>
    @@ -49,13 +49,13 @@ if (!empty($this->entries)) {
    @@ -86,13 +86,13 @@ if (!empty($this->entries)) {
      conf->bottomlineRead ()) { + if ($this->conf->bottomline_read) { ?>
    • isRead () ? 'read' : 'unread'); ?>
    • conf->bottomlineFavorite ()) { + if ($this->conf->bottomline_favorite) { ?>
    • isFavorite () ? 'starred' : 'non-starred'); ?>entries)) { } ?>
    • conf->bottomlineSharing () && ( + if ($this->conf->bottomline_sharing && ( $shaarli || $poche || $diaspora || $twitter || $google_plus || $facebook || $email )) { @@ -171,7 +171,7 @@ if (!empty($this->entries)) {
    • conf->bottomlineTags () ? $item->tags() : null; + $tags = $this->conf->bottomline_tags ? $item->tags() : null; if (!empty($tags)) { ?>
    • @@ -190,8 +190,8 @@ if (!empty($this->entries)) {
    - conf->bottomlineDate ()) { ?>
  • date (); ?> 
  • - conf->bottomlineLink ()) { ?> + conf->bottomline_date) { ?>
  • date (); ?> 
  • + conf->bottomline_link) { ?>
    diff --git a/app/views/helpers/view/reader_view.phtml b/app/views/helpers/view/reader_view.phtml index 47254f74e..2f64e672a 100644 --- a/app/views/helpers/view/reader_view.phtml +++ b/app/views/helpers/view/reader_view.phtml @@ -2,7 +2,7 @@ $this->partial ('nav_menu'); if (!empty($this->entries)) { - $lazyload = $this->conf->lazyload() === 'yes'; + $lazyload = $this->conf->lazyload; ?>
    diff --git a/app/views/index/index.phtml b/app/views/index/index.phtml index 2d134ba4e..4db53e2a5 100644 --- a/app/views/index/index.phtml +++ b/app/views/index/index.phtml @@ -1,7 +1,7 @@ conf->token(); +$token = $this->conf->token; $token_param = Minz_Request::param ('token', ''); $token_is_ok = ($token != '' && $token == $token_param); diff --git a/lib/Minz/Configuration.php b/lib/Minz/Configuration.php index 306328904..3864a9335 100644 --- a/lib/Minz/Configuration.php +++ b/lib/Minz/Configuration.php @@ -225,14 +225,14 @@ class Minz_Configuration { } } if (isset ($general['delay_cache'])) { - self::$delay_cache = $general['delay_cache']; + self::$delay_cache = inval($general['delay_cache']); } if (isset ($general['default_user'])) { self::$default_user = $general['default_user']; self::$current_user = self::$default_user; } if (isset ($general['allow_anonymous'])) { - self::$allow_anonymous = (bool)($general['allow_anonymous']); + self::$allow_anonymous = ((bool)($general['allow_anonymous'])) && ($general['allow_anonymous'] !== 'no'); } // Base de données diff --git a/lib/Minz/ModelArray.php b/lib/Minz/ModelArray.php index 89d7f06c1..e3ec77dc9 100644 --- a/lib/Minz/ModelArray.php +++ b/lib/Minz/ModelArray.php @@ -8,11 +8,6 @@ * La classe Model_array représente le modèle interragissant avec les fichiers de type texte gérant des tableaux php */ class Minz_ModelArray { - /** - * $array Le tableau php contenu dans le fichier $filename - */ - protected $array = array (); - /** * $filename est le nom du fichier */ @@ -25,29 +20,32 @@ class Minz_ModelArray { */ public function __construct ($filename) { $this->filename = $filename; + } + protected function loadArray() { if (!file_exists($this->filename)) { throw new Minz_FileNotExistException($this->filename, Minz_Exception::WARNING); } elseif (($handle = $this->getLock()) === false) { throw new Minz_PermissionDeniedException($this->filename); } else { - $this->array = include($this->filename); + $data = include($this->filename); $this->releaseLock($handle); - if ($this->array === false) { + if ($data === false) { throw new Minz_PermissionDeniedException($this->filename); - } elseif (!is_array($this->array)) { - $this->array = array(); + } elseif (!is_array($data)) { + $data = array(); } + return $data; } } /** * Sauve le tableau $array dans le fichier $filename **/ - protected function writeFile() { - if (!file_put_contents($this->filename, "array, true) . ';', LOCK_EX)) { + protected function writeArray($array) { + if (!file_put_contents($this->filename, "filename); } return true; diff --git a/lib/Minz/Session.php b/lib/Minz/Session.php index f527322f5..6e45fd226 100644 --- a/lib/Minz/Session.php +++ b/lib/Minz/Session.php @@ -55,11 +55,6 @@ class Minz_Session { } else { $_SESSION[$p] = $v; self::$session[$p] = $v; - - if($p == 'language') { - // reset pour remettre à jour le fichier de langue à utiliser - Minz_Translate::reset (); - } } } @@ -76,6 +71,7 @@ class Minz_Session { if (!$force) { self::_param ('language', $language); + Minz_Translate::reset (); } } } diff --git a/lib/lib_rss.php b/lib/lib_rss.php index a27ef171a..3f55c7d58 100644 --- a/lib/lib_rss.php +++ b/lib/lib_rss.php @@ -63,7 +63,7 @@ function is_logged () { // vérifie que le système d'authentification est configuré function login_is_conf ($conf) { - return $conf->mailLogin () != false; + return $conf->mail_login != ''; } // tiré de Shaarli de Seb Sauvage //Format RFC 4648 base64url -- cgit v1.2.3 From 92efd68a3a13e49fe7bbfb8441611c0dcd639415 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Mon, 30 Dec 2013 01:03:32 +0100 Subject: Début de mode multi-utilisateur avec http_auth MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit + Légère optimisation de Minz_View. + Encore plus de tests de bibliothèques dans install.php Contribue à https://github.com/marienfressinaud/FreshRSS/issues/126 et https://github.com/marienfressinaud/FreshRSS/issues/303 --- README.md | 2 +- app/Controllers/configureController.php | 5 +-- app/Controllers/entryController.php | 4 +-- app/Controllers/feedController.php | 27 +++++++------- app/Controllers/indexController.php | 31 ++++++++--------- app/FreshRSS.php | 56 ++++++++++++++++++++++------- app/Models/Configuration.php | 15 ++++---- app/actualize_script.php | 15 +++++--- app/i18n/en.php | 5 +-- app/i18n/fr.php | 5 +-- app/layout/aside_flux.phtml | 6 ++-- app/layout/header.phtml | 21 +++++------ app/layout/nav_menu.phtml | 2 +- app/views/configure/users.phtml | 51 +++++++++++++++------------ app/views/helpers/javascript_vars.phtml | 2 +- app/views/helpers/view/normal_view.phtml | 60 ++++++++++++++++++-------------- app/views/index/index.phtml | 45 ++++++++++++++---------- lib/Minz/Configuration.php | 12 ++++++- lib/Minz/View.php | 29 ++++++--------- lib/lib_rss.php | 16 +++------ p/i/install.php | 24 ++++++++++--- 21 files changed, 246 insertions(+), 187 deletions(-) (limited to 'app/Models') diff --git a/README.md b/README.md index f20f870dd..cfef89781 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Privilégiez pour cela des demandes sur GitHub # Pré-requis * Serveur Apache2 ou Nginx (non testé sur les autres) * PHP 5.2+ (PHP 5.3.3+ recommandé) - * Requis : [LibXML](http://php.net/xml), [PCRE](http://php.net/pcre), [cURL](http://php.net/curl), [PDO_MySQL](http://php.net/pdo-mysql) + * Requis : [PDO_MySQL](http://php.net/pdo-mysql), [cURL](http://php.net/curl), [LibXML](http://php.net/xml), [PCRE](http://php.net/pcre), [ctype](http://php.net/ctype) * Recommandés : [JSON](http://php.net/json), [zlib](http://php.net/zlib), [mbstring](http://php.net/mbstring), [iconv](http://php.net/iconv) * MySQL 5.0.3+ (ou SQLite 3.7.4+ à venir) * Un navigateur Web récent tel Firefox, Chrome, Opera, Safari, Internet Explorer 9+ diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php index 0c0b4951d..656e2ac89 100755 --- a/app/Controllers/configureController.php +++ b/app/Controllers/configureController.php @@ -2,7 +2,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController { public function firstAction () { - if (login_is_conf ($this->view->conf) && !is_logged ()) { + if (!$this->view->loginOk) { Minz_Error::error ( 403, array ('error' => array (Minz_Translate::t ('access_denied'))) @@ -16,7 +16,6 @@ class FreshRSS_configure_Controller extends Minz_ActionController { public function categorizeAction () { $feedDAO = new FreshRSS_FeedDAO (); $catDAO = new FreshRSS_CategoryDAO (); - $catDAO->checkDefault (); $defaultCategory = $catDAO->getDefault (); $defaultId = $defaultCategory->id (); @@ -167,8 +166,6 @@ class FreshRSS_configure_Controller extends Minz_ActionController { $this->view->conf->_bottomline_link(Minz_Request::param('bottomline_link', false)); $this->view->conf->save(); - Minz_Session::_param ('mail', $this->view->conf->mail_login); - Minz_Session::_param ('language', $this->view->conf->language); Minz_Translate::reset (); diff --git a/app/Controllers/entryController.php b/app/Controllers/entryController.php index b0fc37cdf..da4ab5ecc 100755 --- a/app/Controllers/entryController.php +++ b/app/Controllers/entryController.php @@ -2,7 +2,7 @@ class FreshRSS_entry_Controller extends Minz_ActionController { public function firstAction () { - if (login_is_conf ($this->view->conf) && !is_logged ()) { + if (!$this->view->loginOk) { Minz_Error::error ( 403, array ('error' => array (Minz_Translate::t ('access_denied'))) @@ -38,7 +38,7 @@ class FreshRSS_entry_Controller extends Minz_ActionController { $nextGet = Minz_Request::param ('nextGet', $get); $idMax = Minz_Request::param ('idMax', 0); - $is_read = !!$is_read; + $is_read = (bool)$is_read; $entryDAO = new FreshRSS_EntryDAO (); if ($id == false) { diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index 42a0dcb11..2d7c0ab43 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -2,18 +2,17 @@ class FreshRSS_feed_Controller extends Minz_ActionController { public function firstAction () { - $token = $this->view->conf->token; - $token_param = Minz_Request::param ('token', ''); - $token_is_ok = ($token != '' && $token == $token_param); - $action = Minz_Request::actionName (); - - if (login_is_conf ($this->view->conf) && - !is_logged () && - !($token_is_ok && $action == 'actualize')) { - Minz_Error::error ( - 403, - array ('error' => array (Minz_Translate::t ('access_denied'))) - ); + if (!$this->view->loginOk) { + $token = $this->view->conf->token; //TODO: check the token logic again, and if it is still needed + $token_param = Minz_Request::param ('token', ''); + $token_is_ok = ($token != '' && $token == $token_param); + $action = Minz_Request::actionName (); + if (!($token_is_ok && $action === 'actualize')) { + Minz_Error::error ( + 403, + array ('error' => array (Minz_Translate::t ('access_denied'))) + ); + } } $this->catDAO = new FreshRSS_CategoryDAO (); @@ -411,10 +410,8 @@ class FreshRSS_feed_Controller extends Minz_ActionController { } private function addCategories ($categories) { - $catDAO = new FreshRSS_CategoryDAO (); - foreach ($categories as $cat) { - if (!$catDAO->searchByName ($cat->name ())) { + if (!$this->catDAO->searchByName ($cat->name ())) { $values = array ( 'id' => $cat->id (), 'name' => $cat->name (), diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php index 54826636f..66809964d 100755 --- a/app/Controllers/indexController.php +++ b/app/Controllers/indexController.php @@ -16,17 +16,18 @@ class FreshRSS_index_Controller extends Minz_ActionController { public function indexAction () { $output = Minz_Request::param ('output'); - - $token = $this->view->conf->token; - $token_param = Minz_Request::param ('token', ''); - $token_is_ok = ($token != '' && $token === $token_param); - - // check if user is log in - if(login_is_conf ($this->view->conf) && - !is_logged() && - !Minz_Configuration::allowAnonymous() && - !($output === 'rss' && $token_is_ok)) { - return; + $token = ''; + + // check if user is logged in + if (!$this->view->loginOk && !Minz_Configuration::allowAnonymous()) + { + $token = $this->view->conf->token; + $token_param = Minz_Request::param ('token', ''); + $token_is_ok = ($token != '' && $token === $token_param); + if (!($output === 'rss' && $token_is_ok)) { + return; + } + $params['token'] = $token; } // construction of RSS url of this feed @@ -35,11 +36,6 @@ class FreshRSS_index_Controller extends Minz_ActionController { if (isset ($params['search'])) { $params['search'] = urlencode ($params['search']); } - if (login_is_conf($this->view->conf) && - !Minz_Configuration::allowAnonymous() && - $token !== '') { - $params['token'] = $token; - } $this->view->rss_url = array ( 'c' => 'index', 'a' => 'index', @@ -212,7 +208,7 @@ class FreshRSS_index_Controller extends Minz_ActionController { } public function logsAction () { - if (login_is_conf ($this->view->conf) && !is_logged ()) { + if (!$this->view->loginOk) { Minz_Error::error ( 403, array ('error' => array (Minz_Translate::t ('access_denied'))) @@ -255,6 +251,7 @@ class FreshRSS_index_Controller extends Minz_ActionController { $res = json_decode ($result, true); if ($res['status'] === 'okay' && $res['email'] === $this->view->conf->mail_login) { Minz_Session::_param ('mail', $res['email']); + $this->view->loginOk = true; invalidateHttpCache(); } else { $res = array (); diff --git a/app/FreshRSS.php b/app/FreshRSS.php index 05c8ec8e0..10f362717 100644 --- a/app/FreshRSS.php +++ b/app/FreshRSS.php @@ -1,26 +1,56 @@ loadParamsView (); - $this->loadStylesAndScripts (); - $this->loadNotifications (); + public function init($currentUser = null) { + Minz_Session::init('FreshRSS'); + $this->accessControl($currentUser); + $this->loadParamsView(); + $this->loadStylesAndScripts(); + $this->loadNotifications(); } - private function loadParamsView () { + private function accessControl($currentUser) { + if ($currentUser === null) { + switch (Minz_Configuration::authType()) { + case 'http_auth': + $currentUser = httpAuthUser(); + $loginOk = $currentUser != ''; + break; + case 'persona': + $currentUser = Minz_Configuration::defaultUser(); + $loginOk = Minz_Session::param('mail') != ''; + break; + case 'none': + $currentUser = Minz_Configuration::defaultUser(); + $loginOk = true; + break; + default: + $loginOk = false; + break; + } + } elseif ((PHP_SAPI === 'cli') && (Minz_Request::actionName() === 'actualize')) { //Command line + Minz_Configuration::_authType('none'); + $loginOk = true; + } + + if (!$loginOk || !isValidUser($currentUser)) { + $currentUser = Minz_Configuration::defaultUser(); + $loginOk = false; + } + Minz_Configuration::_currentUser($currentUser); + Minz_View::_param ('loginOk', $loginOk); + try { - $this->conf = new FreshRSS_Configuration(); + $this->conf = new FreshRSS_Configuration($currentUser); } catch (Minz_Exception $e) { // Permission denied or conf file does not exist - // it's critical! die($e->getMessage()); } - Minz_View::_param ('conf', $this->conf); + } + + private function loadParamsView () { Minz_Session::_param ('language', $this->conf->language); Minz_Translate::init(); - $output = Minz_Request::param ('output'); if (!$output) { $output = $this->conf->view_mode; @@ -31,12 +61,12 @@ class FreshRSS extends Minz_FrontController { private function loadStylesAndScripts () { $theme = FreshRSS_Themes::get_infos($this->conf->theme); if ($theme) { - foreach($theme["files"] as $file) { + foreach($theme['files'] as $file) { Minz_View::appendStyle (Minz_Url::display ('/themes/' . $theme['path'] . '/' . $file . '?' . @filemtime(PUBLIC_PATH . '/themes/' . $theme['path'] . '/' . $file))); } } - if (login_is_conf ($this->conf)) { + if (Minz_Configuration::authType() === 'persona') { Minz_View::appendScript ('https://login.persona.org/include.js'); } $includeLazyLoad = $this->conf->lazyload && ($this->conf->display_posts || Minz_Request::param ('output') === 'reader'); diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php index b0a5d9940..ec7daaa7d 100644 --- a/app/Models/Configuration.php +++ b/app/Models/Configuration.php @@ -59,10 +59,9 @@ class FreshRSS_Configuration extends Minz_ModelArray { 'fr' => 'Français', ); - public function __construct ($filename = '') { - if (empty($filename)) { - $filename = DATA_PATH . '/' . Minz_Configuration::currentUser () . '_user.php'; - } + public function __construct ($user) { + $filename = DATA_PATH . '/' . $user . '_user.php'; + parent::__construct($filename); $data = parent::loadArray(); @@ -72,6 +71,7 @@ class FreshRSS_Configuration extends Minz_ModelArray { $this->$function($value); } } + $this->data['user'] = $user; } public function save() { @@ -151,10 +151,11 @@ class FreshRSS_Configuration extends Minz_ModelArray { } } public function _mail_login ($value) { - if (filter_var($value, FILTER_VALIDATE_EMAIL)) { - $this->mail_login = $value; + $value = filter_var($value, FILTER_VALIDATE_EMAIL); + if ($value) { + $this->data['mail_login'] = $value; } else { - $this->mail_login = ''; + $this->data['mail_login'] = ''; } } public function _anon_access ($value) { diff --git a/app/actualize_script.php b/app/actualize_script.php index 20438128a..e0c560ff7 100755 --- a/app/actualize_script.php +++ b/app/actualize_script.php @@ -1,6 +1,8 @@ init (); -Minz_Session::_param('mail', true); // permet de se passer de la phase de connexion -$front_controller->run (); -invalidateHttpCache(); + +$users = listUsers(); +shuffle($users); + +foreach ($users as $user) { + $front_controller->init($user); + $front_controller->run(); + invalidateHttpCache($user); +} diff --git a/app/i18n/en.php b/app/i18n/en.php index 65afc11e5..8b9eee548 100644 --- a/app/i18n/en.php +++ b/app/i18n/en.php @@ -158,13 +158,14 @@ return array ( 'current_user' => 'Current user', 'default_user' => 'Username of the default user (maximum 16 alphanumeric characters)', - 'persona_connection_email' => 'Login mail address (use Mozilla Persona)', + 'persona_connection_email' => 'Login mail address (for Mozilla Persona)', 'allow_anonymous' => 'Allow anonymous reading for the default user (%s)', 'auth_token' => 'Authentication token', - 'explain_token' => 'Allows to access RSS output without authentication.
    %s?token=%s', + 'explain_token' => 'Allows to access RSS output of the default user without authentication.
    %s?token=%s', 'login_configuration' => 'Login', 'is_admin' => 'is administrator', 'auth_type' => 'Authentication method', + 'auth_none' => 'None (dangerous)', 'users_list' => 'List of users', 'language' => 'Language', diff --git a/app/i18n/fr.php b/app/i18n/fr.php index adc38acbe..cad156d47 100644 --- a/app/i18n/fr.php +++ b/app/i18n/fr.php @@ -158,13 +158,14 @@ return array ( 'current_user' => 'Utilisateur actuel', 'default_user' => 'Nom de l’utilisateur par défaut (16 caractères alphanumériques maximum)', - 'persona_connection_email' => 'Adresse courriel de connexion (utilise Mozilla Persona)', + 'persona_connection_email' => 'Adresse courriel de connexion (pour Mozilla Persona)', 'allow_anonymous' => 'Autoriser la lecture anonyme pour l’utilisateur par défaut (%s)', 'auth_token' => 'Jeton d’identification', - 'explain_token' => 'Permet d’accéder à la sortie RSS sans besoin de s’authentifier.
    %s?output=rss&token=%s', + 'explain_token' => 'Permet d’accéder à la sortie RSS de l’utilisateur par défaut sans besoin de s’authentifier.
    %s?output=rss&token=%s', 'login_configuration' => 'Identification', 'is_admin' => 'est administrateur', 'auth_type' => 'Méthode d’authentification', + 'auth_none' => 'Aucune (dangereux)', 'users_list' => 'Liste des utilisateurs', 'language' => 'Langue', diff --git a/app/layout/aside_flux.phtml b/app/layout/aside_flux.phtml index 9a6b16d58..8730baf0e 100644 --- a/app/layout/aside_flux.phtml +++ b/app/layout/aside_flux.phtml @@ -2,14 +2,14 @@
      - conf) || is_logged ()) { ?> + loginOk) { ?>
    • - conf)) { ?> +
    • @@ -69,7 +69,7 @@
    • - conf) || is_logged ()) { ?> + loginOk) { ?>
    • diff --git a/app/layout/header.phtml b/app/layout/header.phtml index aeb417a6e..0f2c524c4 100644 --- a/app/layout/header.phtml +++ b/app/layout/header.phtml @@ -1,9 +1,9 @@ -conf)) { ?> + @@ -19,9 +19,7 @@
    diff --git a/app/views/configure/users.phtml b/app/views/configure/users.phtml index cb6579a6b..223f81e8d 100644 --- a/app/views/configure/users.phtml +++ b/app/views/configure/users.phtml @@ -3,16 +3,15 @@
    -
    +
    - $_SERVER['REMOTE_USER'] =
    @@ -22,21 +21,25 @@ conf->mail_login; ?>
    - + placeholder="alice@example.net" />
    +
    + +
    - + - +
    +
    @@ -46,17 +49,7 @@ -
    -
    - -
    - -
    - + $_SERVER['REMOTE_USER'] = ``
    @@ -67,6 +60,8 @@
    + + Mozilla Persona
    @@ -95,4 +90,66 @@ + +
    + + +
    + +
    + +
    +
    + +
    +
    + +
    +
    +
    + +
    + + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + + conf->mail_login; ?> +
    + +
    +
    + +
    +
    + + +
    +
    + +
    + +
    diff --git a/lib/Minz/Configuration.php b/lib/Minz/Configuration.php index 1513af6d0..873908ce6 100644 --- a/lib/Minz/Configuration.php +++ b/lib/Minz/Configuration.php @@ -28,7 +28,7 @@ class Minz_Configuration { /** * définition des variables de configuration - * $sel_application une chaîne de caractères aléatoires (obligatoire) + * $salt une chaîne de caractères aléatoires (obligatoire) * $environment gère le niveau d'affichage pour log et erreurs * $use_url_rewriting indique si on utilise l'url_rewriting * $base_url le chemin de base pour accéder à l'application @@ -42,7 +42,7 @@ class Minz_Configuration { * - password mot de passe de l'utilisateur * - base le nom de la base de données */ - private static $sel_application = ''; + private static $salt = ''; private static $environment = Minz_Configuration::PRODUCTION; private static $base_url = ''; private static $use_url_rewriting = false; @@ -55,17 +55,19 @@ class Minz_Configuration { private static $auth_type = 'none'; private static $db = array ( - 'host' => false, - 'user' => false, - 'password' => false, - 'base' => false + 'type' => 'mysql', + 'host' => '', + 'user' => '', + 'password' => '', + 'base' => '', + 'prefix' => '', ); /* * Getteurs */ public static function salt () { - return self::$sel_application; + return self::$salt; } public static function environment () { return self::$environment; @@ -145,7 +147,7 @@ class Minz_Configuration { 'general' => array( 'environment' => self::$environment, 'use_url_rewriting' => self::$use_url_rewriting, - 'sel_application' => self::$sel_application, + 'salt' => self::$salt, 'base_url' => self::$base_url, 'title' => self::$title, 'default_user' => self::$default_user, @@ -189,14 +191,18 @@ class Minz_Configuration { } $general = $ini_array['general']; - // sel_application est obligatoire - if (!isset ($general['sel_application'])) { - throw new Minz_BadConfigurationException ( - 'sel_application', - Minz_Exception::ERROR - ); + // salt est obligatoire + if (!isset ($general['salt'])) { + if (isset($general['sel_application'])) { //v0.6 + $general['salt'] = $general['sel_application']; + } else { + throw new Minz_BadConfigurationException ( + 'salt', + Minz_Exception::ERROR + ); + } } - self::$sel_application = $general['sel_application']; + self::$salt = $general['salt']; if (isset ($general['environment'])) { switch ($general['environment']) { @@ -256,18 +262,15 @@ class Minz_Configuration { } // Base de données - $db = false; if (isset ($ini_array['db'])) { $db = $ini_array['db']; - } - if ($db) { - if (!isset ($db['host'])) { + if (empty($db['host'])) { throw new Minz_BadConfigurationException ( 'host', Minz_Exception::ERROR ); } - if (!isset ($db['user'])) { + if (empty($db['user'])) { throw new Minz_BadConfigurationException ( 'user', Minz_Exception::ERROR @@ -279,19 +282,23 @@ class Minz_Configuration { Minz_Exception::ERROR ); } - if (!isset ($db['base'])) { + if (empty($db['base'])) { throw new Minz_BadConfigurationException ( 'base', Minz_Exception::ERROR ); } - self::$db['type'] = isset ($db['type']) ? $db['type'] : 'mysql'; + if (!empty($db['type'])) { + self::$db['type'] = $db['type']; + } self::$db['host'] = $db['host']; self::$db['user'] = $db['user']; self::$db['password'] = $db['password']; self::$db['base'] = $db['base']; - self::$db['prefix'] = isset ($db['prefix']) ? $db['prefix'] : ''; + if (isset($db['prefix'])) { + self::$db['prefix'] = $db['prefix']; + } } } diff --git a/lib/Minz/FileNotExistException.php b/lib/Minz/FileNotExistException.php index df2b8ff6c..f8dfbdf66 100644 --- a/lib/Minz/FileNotExistException.php +++ b/lib/Minz/FileNotExistException.php @@ -1,7 +1,7 @@ */ @@ -23,7 +23,7 @@ class Minz_ModelPdo { protected $bd; protected $prefix; - + /** * Créé la connexion à la base de données à l'aide des variables * HOST, BASE, USER et PASS définies dans le fichier de configuration @@ -80,11 +80,15 @@ class Minz_ModelPdo { $this->bd->rollBack(); } - public function size() { + public function size($all = false) { $db = Minz_Configuration::dataBase (); $sql = 'SELECT SUM(data_length + index_length) FROM information_schema.TABLES WHERE table_schema = ?'; - $stm = $this->bd->prepare ($sql); $values = array ($db['base']); + if (!$all) { + $sql .= ' AND table_name LIKE ?'; + $values[] = $this->prefix . '%'; + } + $stm = $this->bd->prepare ($sql); $stm->execute ($values); $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); return $res[0]; diff --git a/p/i/install.php b/p/i/install.php index 672f64b94..e953cf699 100644 --- a/p/i/install.php +++ b/p/i/install.php @@ -12,6 +12,8 @@ if (isset ($_GET['step'])) { define ('STEP', 1); } +define('SQL_CREATE_DB', 'CREATE DATABASE %1$s DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci;'); + include(APP_PATH . '/sql.php'); // @@ -151,7 +153,7 @@ function saveStep2 () { return false; } - $_SESSION['sel_application'] = sha1(uniqid(mt_rand(), true).implode('', stat(__FILE__))); + $_SESSION['salt'] = sha1(uniqid(mt_rand(), true).implode('', stat(__FILE__))); $_SESSION['title'] = substr(trim($_POST['title']), 0, 25); $_SESSION['old_entries'] = $_POST['old_entries']; if ((!ctype_digit($_SESSION['old_entries'])) || ($_SESSION['old_entries'] < 1)) { @@ -162,7 +164,7 @@ function saveStep2 () { $token = ''; if ($_SESSION['mail_login']) { - $token = sha1($_SESSION['sel_application'] . $_SESSION['mail_login']); + $token = sha1($_SESSION['salt'] . $_SESSION['mail_login']); } $config_array = array ( @@ -173,7 +175,7 @@ function saveStep2 () { ); $configPath = DATA_PATH . '/' . $_SESSION['default_user'] . '_user.php'; - @unlink(configPath); //To avoid access-rights problems + @unlink($configPath); //To avoid access-rights problems file_put_contents($configPath, " array( 'environment' => empty($_SESSION['environment']) ? 'production' : $_SESSION['environment'], 'use_url_rewriting' => false, - 'sel_application' => $_SESSION['sel_application'], + 'salt' => $_SESSION['salt'], 'base_url' => '', 'title' => $_SESSION['title'], 'default_user' => $_SESSION['default_user'], @@ -424,7 +426,7 @@ function checkStep0 () { if ($ini_array) { $ini_general = isset($ini_array['general']) ? $ini_array['general'] : null; if ($ini_general) { - $keys = array('environment', 'sel_application', 'title', 'default_user'); + $keys = array('environment', 'salt', 'title', 'default_user'); foreach ($keys as $key) { if ((empty($_SESSION[$key])) && isset($ini_general[$key])) { $_SESSION[$key] = $ini_general[$key]; @@ -496,7 +498,7 @@ function checkStep1 () { } function checkStep2 () { - $conf = !empty($_SESSION['sel_application']) && + $conf = !empty($_SESSION['salt']) && !empty($_SESSION['title']) && !empty($_SESSION['old_entries']) && isset($_SESSION['mail_login']) && @@ -537,7 +539,7 @@ function checkStep3 () { } function checkBD () { - $error = false; + $ok = false; try { $str = ''; @@ -575,35 +577,18 @@ function checkBD () { $res = $c->query($sql); //Backup tables } - $sql = sprintf (SQL_CAT, $_SESSION['bd_prefix_user']); - $res = $c->query ($sql); - - if (!$res) { - $error = true; - } - - $sql = sprintf (SQL_FEED, $_SESSION['bd_prefix_user']); - $res = $c->query ($sql); - - if (!$res) { - $error = true; - } - - $sql = sprintf (SQL_ENTRY, $_SESSION['bd_prefix_user']); - $res = $c->query ($sql); - - if (!$res) { - $error = true; - } + $sql = sprintf(SQL_CREATE_TABLES, $_SESSION['bd_prefix_user']); + $stm = $c->prepare($sql, array(PDO::ATTR_EMULATE_PREPARES => true)); + $ok = $stm->execute(); } catch (PDOException $e) { $error = true; } - if ($error && file_exists (DATA_PATH . '/config.php')) { - unlink (DATA_PATH . '/config.php'); + if (!$ok) { + @unlink(DATA_PATH . '/config.php'); } - return !$error; + return $ok; } /*** AFFICHAGE ***/ @@ -729,9 +714,6 @@ function printStep2 () {
    -
    -- cgit v1.2.3 From 66229a5d71b85c0a57d63fa8b1cc6e5729cabfe4 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Wed, 1 Jan 2014 04:39:39 +0100 Subject: Minz : bug avec OPcache de PHP 5.5+ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Minz ne prenait pas en charge OPcache (cache PHP) http://php.net/opcache activé par défaut depuis PHP5.5. Ce fut un peu dur d'isoler ce bug :-/ Il faut penser à appeler opcache_invalidate avant de ré-utiliser un fichier par include(). Aussi, le mécanisme de lock() n'est plus approprié ni nécessaire. Pour FreshRSS, évite l'utilisation de ModelArray car il ne restait que quelques lignes d'utiles, et évite un héritage + appel de classe, ce qui est toujours ça de gagné. --- app/Models/Configuration.php | 20 +++++++++++++++----- lib/Minz/Configuration.php | 17 +++++++---------- lib/Minz/ModelArray.php | 5 ++++- 3 files changed, 26 insertions(+), 16 deletions(-) (limited to 'app/Models') diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php index ec7daaa7d..abd874f27 100644 --- a/app/Models/Configuration.php +++ b/app/Models/Configuration.php @@ -1,6 +1,8 @@ 'en', 'old_entries' => 3, @@ -60,10 +62,12 @@ class FreshRSS_Configuration extends Minz_ModelArray { ); public function __construct ($user) { - $filename = DATA_PATH . '/' . $user . '_user.php'; + $this->filename = DATA_PATH . '/' . $user . '_user.php'; - parent::__construct($filename); - $data = parent::loadArray(); + $data = include($this->filename); + if (!is_array($data)) { + throw new Minz_PermissionDeniedException($this->filename); + } foreach ($data as $key => $value) { if (isset($this->data[$key])) { @@ -75,8 +79,14 @@ class FreshRSS_Configuration extends Minz_ModelArray { } public function save() { + if (file_put_contents($this->filename, "filename); + } + if (function_exists('opcache_invalidate')) { + opcache_invalidate($this->filename); //Clear PHP 5.5+ cache for include + } invalidateHttpCache(); - return parent::writeArray($this->data); + return true; } public function __get($name) { diff --git a/lib/Minz/Configuration.php b/lib/Minz/Configuration.php index 873908ce6..fc94d9731 100644 --- a/lib/Minz/Configuration.php +++ b/lib/Minz/Configuration.php @@ -157,25 +157,22 @@ class Minz_Configuration { 'db' => self::$db, ); @rename(DATA_PATH . self::CONF_PATH_NAME, DATA_PATH . self::CONF_PATH_NAME . '.bak'); - return file_put_contents(DATA_PATH . self::CONF_PATH_NAME, "filename, "filename, "filename); } + if (function_exists('opcache_invalidate')) { + opcache_invalidate($this->filename); //Clear PHP 5.5+ cache for include + } return true; } -- cgit v1.2.3 From bc4116ea77a927683b2abe8a7b4b4fde6452f7d6 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Wed, 1 Jan 2014 04:54:44 +0100 Subject: Changement sauvegarde configuration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changement oublié dans https://github.com/marienfressinaud/FreshRSS/commit/66229a5d71b85c0a57d63fa8b1cc6e5729cabfe4 pour https://github.com/marienfressinaud/FreshRSS/issues/126 --- app/Models/Configuration.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'app/Models') diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php index abd874f27..a39d47888 100644 --- a/app/Models/Configuration.php +++ b/app/Models/Configuration.php @@ -79,7 +79,8 @@ class FreshRSS_Configuration { } public function save() { - if (file_put_contents($this->filename, "filename, $this->filename . '.bak'); + if (file_put_contents($this->filename, "data, true) . ';', LOCK_EX) === false) { throw new Minz_PermissionDeniedException($this->filename); } if (function_exists('opcache_invalidate')) { -- cgit v1.2.3 From 8beb15460a3c55c37264fdf414e7dcf9ff4e2a62 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Wed, 1 Jan 2014 05:06:36 +0100 Subject: Sauvegardes avec extension .bak.php pour plus de sécurité MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Évite le téléchargement --- app/Models/Configuration.php | 2 +- data/.gitignore | 2 +- lib/Minz/Configuration.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'app/Models') diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php index a39d47888..e0ae3bf6b 100644 --- a/app/Models/Configuration.php +++ b/app/Models/Configuration.php @@ -79,7 +79,7 @@ class FreshRSS_Configuration { } public function save() { - @rename($this->filename, $this->filename . '.bak'); + @rename($this->filename, $this->filename . '.bak.php'); if (file_put_contents($this->filename, "data, true) . ';', LOCK_EX) === false) { throw new Minz_PermissionDeniedException($this->filename); } diff --git a/data/.gitignore b/data/.gitignore index 1d8393f3b..005982b00 100644 --- a/data/.gitignore +++ b/data/.gitignore @@ -4,4 +4,4 @@ config.php *.sqlite touch.txt no-cache.txt -*.bak +*.bak.php diff --git a/lib/Minz/Configuration.php b/lib/Minz/Configuration.php index fc94d9731..2c30661ed 100644 --- a/lib/Minz/Configuration.php +++ b/lib/Minz/Configuration.php @@ -156,7 +156,7 @@ class Minz_Configuration { ), 'db' => self::$db, ); - @rename(DATA_PATH . self::CONF_PATH_NAME, DATA_PATH . self::CONF_PATH_NAME . '.bak'); + @rename(DATA_PATH . self::CONF_PATH_NAME, DATA_PATH . self::CONF_PATH_NAME . '.bak.php'); $result = file_put_contents(DATA_PATH . self::CONF_PATH_NAME, " Date: Wed, 1 Jan 2014 15:08:15 +0100 Subject: SQL : Insère la catégorie par défaut dès la création des tables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Models/UserDAO.php | 5 ++++- app/sql.php | 2 ++ p/i/install.php | 5 ++++- 3 files changed, 10 insertions(+), 2 deletions(-) (limited to 'app/Models') diff --git a/app/Models/UserDAO.php b/app/Models/UserDAO.php index afa049fb9..a25b57f89 100644 --- a/app/Models/UserDAO.php +++ b/app/Models/UserDAO.php @@ -7,7 +7,10 @@ class FreshRSS_UserDAO extends Minz_ModelPdo { $sql = sprintf(SQL_CREATE_TABLES, $db['prefix'] . $username . '_'); $stm = $this->bd->prepare($sql, array(PDO::ATTR_EMULATE_PREPARES => true)); - if ($stm && $stm->execute()) { + $values = array( + 'catName' => Minz_Translate::t('default_category'), + ); + if ($stm && $stm->execute($values)) { return true; } else { $info = $stm->errorInfo(); diff --git a/app/sql.php b/app/sql.php index 8646b4da5..5a28858a7 100644 --- a/app/sql.php +++ b/app/sql.php @@ -52,6 +52,8 @@ CREATE TABLE IF NOT EXISTS `%1$sentry` ( INDEX (`is_read`) -- v0.7 ) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = INNODB; + +INSERT INTO `%1$scategory` (name) VALUES(:catName); '); define('SQL_DROP_TABLES', 'DROP TABLES %1$sentry, %1$sfeed, %1$scategory'); diff --git a/p/i/install.php b/p/i/install.php index 0cd952fef..3316d222b 100644 --- a/p/i/install.php +++ b/p/i/install.php @@ -585,7 +585,10 @@ function checkBD () { $sql = sprintf(SQL_CREATE_TABLES, $_SESSION['bd_prefix_user']); $stm = $c->prepare($sql, array(PDO::ATTR_EMULATE_PREPARES => true)); - $ok = $stm->execute(); + $values = array( + 'catName' => _t('default_category'), + ); + $ok = $stm->execute($values); } catch (PDOException $e) { $error = true; } -- cgit v1.2.3 From 67531f796f4c273cc9b078db788fdb51ece824ee Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Wed, 1 Jan 2014 17:01:09 +0100 Subject: HTTP Auth : améliorations + changelog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Affichage logo FreshRSS pendant la demande de nom d'utilisateur HTTP * Meilleure page d'erreur si utilisateur HTTP non-configuré dans FreshRSS + log Je recommande donc de placer le .htaccess pour le contrôle d'accès dans ./FreshRSS/p/i/.htaccess et pas plus haut pour une meilleure mise en cache et messages de connexion. --- CHANGELOG | 14 ++++++++++++++ README.md | 16 +++++++++------- app/FreshRSS.php | 23 ++++++++++++++++++----- app/Models/Configuration.php | 2 +- index.html | 6 +++--- index.php | 2 +- p/index.html | 22 ++++++++++++++++++++-- p/index.php | 3 --- 8 files changed, 66 insertions(+), 22 deletions(-) delete mode 100644 p/index.php (limited to 'app/Models') diff --git a/CHANGELOG b/CHANGELOG index 108e29195..f4b168b41 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,19 @@ # Journal des modifications +## 2014-01-xx FreshRSS 0.7.5 + +* Nouveau mode multi-utilisateur + * L’utilisateur par défaut (administrateur) peut créer et supprimer d’autres utilisateurs + * Nécessite un contrôle d’accès, soit : + * par HTTP (par exemple sous Apache en créant un fichier ./p/i/.htaccess et .htpasswd) + * le nom d’utilisateur HTTP doit correspondre au nom d’utilisateur FreshRSS + * par Mozilla Persona, en renseignant l’adresse courriel des utilisateurs +* Options + * Réorganisation des options +* PHP + * Compatibilité PHP 5.5+ avec OPcache + * Amélioration des performances + ## 2014-01-xx FreshRSS 0.7 * Installeur diff --git a/README.md b/README.md index cfef89781..cf1308c33 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # FreshRSS FreshRSS est un agrégateur de flux RSS à auto-héberger à l’image de [Leed](http://projet.idleman.fr/leed/) ou de [Kriss Feed](http://tontof.net/kriss/feed/). Il se veut léger et facile à prendre en main tout en étant un outil puissant et paramétrable. +Il permet de gérer plusieurs utilisateurs, et dispose d’un mode de lecture anonyme. * Site officiel : http://freshrss.org * Démo : http://marienfressinaud.fr/projets/freshrss/ @@ -21,11 +22,11 @@ Privilégiez pour cela des demandes sur GitHub # Pré-requis * Serveur Apache2 ou Nginx (non testé sur les autres) * PHP 5.2+ (PHP 5.3.3+ recommandé) - * Requis : [PDO_MySQL](http://php.net/pdo-mysql), [cURL](http://php.net/curl), [LibXML](http://php.net/xml), [PCRE](http://php.net/pcre), [ctype](http://php.net/ctype) - * Recommandés : [JSON](http://php.net/json), [zlib](http://php.net/zlib), [mbstring](http://php.net/mbstring), [iconv](http://php.net/iconv) + * Requis : [PDO_MySQL](http://php.net/pdo-mysql), [cURL](http://php.net/curl), [LibXML](http://php.net/xml), [PCRE](http://php.net/pcre), [ctype](http://php.net/ctype) + * Recommandés : [JSON](http://php.net/json), [zlib](http://php.net/zlib), [mbstring](http://php.net/mbstring), [iconv](http://php.net/iconv) * MySQL 5.0.3+ (ou SQLite 3.7.4+ à venir) * Un navigateur Web récent tel Firefox, Chrome, Opera, Safari, Internet Explorer 9+ - * Fonctionne aussi sur mobile + * Fonctionne aussi sur mobile ![Capture d’écran de FreshRSS](http://marienfressinaud.fr/data/images/freshrss/freshrss_default-design.png) @@ -37,10 +38,11 @@ Privilégiez pour cela des demandes sur GitHub 5. Tout devrait fonctionner :) En cas de problème, n’hésitez pas à me contacter. # Contrôle d’accès -Il est recommandé de limiter l’accès à votre FreshRSS, soit : +Il est requis pour le mode multi-utilisateur, et recommandé dans tous les cas, de limiter l’accès à votre FreshRSS : * En utilisant l’identification par [Mozilla Persona](https://login.persona.org/about) incluse dans FreshRSS -* En utilisant un contrôle d’accès défini par votre serveur Web - * Voir par exemple la [documentation d’Apache sur l’authentification](http://httpd.apache.org/docs/trunk/howto/auth.html) +* En utilisant un contrôle d’accès HTTP défini par votre serveur Web + * Voir par exemple la [documentation d’Apache sur l’authentification](http://httpd.apache.org/docs/trunk/howto/auth.html) + * Créer dans ce cas un fichier `./p/i/.htaccess` avec un fichier `.htpasswd` correspondant. # Rafraîchissement automatique des flux * Vous pouvez ajouter une tâche CRON sur le script d’actualisation des flux. Par exemple, pour exécuter le script toutes les heures : @@ -51,6 +53,6 @@ Il est recommandé de limiter l’accès à votre FreshRSS, soit : # Conseils * Pour une meilleure sécurité, faites en sorte que seul le répertoire `./p/` soit accessible depuis le Web, par exemple en faisant pointer un sous-domaine sur le répertoire `./p/`. -* Les données personnelles se trouvent dans le répertoire `./data/` (déjà protégé par un .htaccess pour Apache - vérifiez que cela fonctionne -, à protéger vous-même dans le cas d’autres serveurs Web). + * En particulier, les données personnelles se trouvent dans le répertoire `./data/`. * Le fichier `./constants.php` définit les chemins d’accès aux répertoires clés de l’application. Si vous les bougez, tout se passe ici. * En cas de problème, les logs peuvent être utile à lire, soit depuis l’interface de FreshRSS, soit manuellement depuis `./data/log/*.log`. diff --git a/app/FreshRSS.php b/app/FreshRSS.php index 0af0c01da..4ebb6d631 100644 --- a/app/FreshRSS.php +++ b/app/FreshRSS.php @@ -51,12 +51,25 @@ class FreshRSS extends Minz_FrontController { try { $this->conf = new FreshRSS_Configuration($currentUser); - } catch (Minz_Exception $e) { - Minz_Session::_param('currentUser', ''); - die('Invalid configuration for user [' . $currentUser . ']! ' . $e->getMessage()); //Permission denied or conf file does not exist + Minz_View::_param ('conf', $this->conf); + Minz_Session::_param('currentUser', $currentUser); + } catch (Minz_Exception $me) { + $loginOk = false; + try { + $this->conf = new FreshRSS_Configuration(Minz_Configuration::defaultUser()); + Minz_Session::_param('currentUser', Minz_Configuration::defaultUser()); + Minz_View::_param('conf', $this->conf); + $notif = array( + 'type' => 'bad', + 'content' => 'Invalid configuration for user [' . $currentUser . ']!', + ); + Minz_Session::_param ('notification', $notif); + Minz_Log::record ($notif['content'] . ' ' . $me->getMessage(), Minz_Log::WARNING); + Minz_Session::_param('currentUser', ''); + } catch (Exception $e) { + die($e->getMessage()); + } } - Minz_View::_param ('conf', $this->conf); - Minz_Session::_param('currentUser', $currentUser); if ($loginOk) { switch (Minz_Configuration::authType()) { diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php index e0ae3bf6b..d44420f56 100644 --- a/app/Models/Configuration.php +++ b/app/Models/Configuration.php @@ -64,7 +64,7 @@ class FreshRSS_Configuration { public function __construct ($user) { $this->filename = DATA_PATH . '/' . $user . '_user.php'; - $data = include($this->filename); + $data = @include($this->filename); if (!is_array($data)) { throw new Minz_PermissionDeniedException($this->filename); } diff --git a/index.html b/index.html index 1bfdea859..6ac025960 100644 --- a/index.html +++ b/index.html @@ -2,12 +2,12 @@ - + Redirection - + -

    FreshRSS

    +

    FreshRSS

    diff --git a/index.php b/index.php index 3fdae4285..828cca39a 100644 --- a/index.php +++ b/index.php @@ -1,3 +1,3 @@ + -Redirection +FreshRSS + + + -

    FreshRSS

    +

    FreshRSS

    +

    diff --git a/p/index.php b/p/index.php deleted file mode 100644 index e90662078..000000000 --- a/p/index.php +++ /dev/null @@ -1,3 +0,0 @@ - Date: Fri, 3 Jan 2014 00:01:29 -0500 Subject: Add a shortcut to open the share div. The default shortcut is "s". --- app/Models/Configuration.php | 1 + app/i18n/en.php | 1 + app/i18n/fr.php | 1 + app/views/configure/shortcut.phtml | 7 +++++++ app/views/helpers/javascript_vars.phtml | 3 ++- p/scripts/main.js | 12 ++++++++++++ 6 files changed, 24 insertions(+), 1 deletion(-) (limited to 'app/Models') diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php index d44420f56..c29e74603 100644 --- a/app/Models/Configuration.php +++ b/app/Models/Configuration.php @@ -33,6 +33,7 @@ class FreshRSS_Configuration { 'prev_entry' => 'k', 'collapse_entry' => 'c', 'load_more' => 'm', + 'auto_share' => 's', ), 'topline_read' => true, 'topline_favorite' => true, diff --git a/app/i18n/en.php b/app/i18n/en.php index 28aa1296a..06bbf48e6 100644 --- a/app/i18n/en.php +++ b/app/i18n/en.php @@ -123,6 +123,7 @@ return array ( 'next_page' => 'Skip to the next page', 'previous_page' => 'Skip to the previous page', 'collapse_article' => 'Collapse current article', + 'auto_share' => 'Share current article', 'file_to_import' => 'File to import', 'import' => 'Import', diff --git a/app/i18n/fr.php b/app/i18n/fr.php index 39aeaf29a..cb689610b 100644 --- a/app/i18n/fr.php +++ b/app/i18n/fr.php @@ -123,6 +123,7 @@ return array ( 'next_page' => 'Passer à la page suivante', 'previous_page' => 'Passer à la page précédente', 'collapse_article' => 'Refermer l’article courant', + 'auto_share' => 'Partager l’article courant', 'file_to_import' => 'Fichier à importer', 'import' => 'Importer', diff --git a/app/views/configure/shortcut.phtml b/app/views/configure/shortcut.phtml index 2e564a7b6..b0867f711 100644 --- a/app/views/configure/shortcut.phtml +++ b/app/views/configure/shortcut.phtml @@ -68,6 +68,13 @@
    +
    + +
    + +
    +
    +
    diff --git a/app/views/helpers/javascript_vars.phtml b/app/views/helpers/javascript_vars.phtml index 8419b9391..92c068f7e 100644 --- a/app/views/helpers/javascript_vars.phtml +++ b/app/views/helpers/javascript_vars.phtml @@ -18,7 +18,8 @@ 'prev_entry:"', $s['prev_entry'], '",', 'next_entry:"', $s['next_entry'], '",', 'collapse_entry:"', $s['collapse_entry'], '",', - 'load_more:"', $s['load_more'], '"', + 'load_more:"', $s['load_more'], '",', + 'auto_share:"', $s['auto_share'], '"', "},\n"; if (Minz_Request::param ('output') === 'global') { diff --git a/p/scripts/main.js b/p/scripts/main.js index ef05eb2fb..43b0384e8 100644 --- a/p/scripts/main.js +++ b/p/scripts/main.js @@ -220,6 +220,13 @@ function collapse_entry() { $(".flux.current").toggleClass("active"); } +function auto_share() { + var share = $(".flux.current.active").find('.dropdown-target[id^="dropdown-share"]'); + if (share.length) { + window.location.replace('/i/#' + share.attr('id')); + } +} + function inMarkViewport(flux, box_to_follow, relative_follow) { var top = flux.position().top; if (relative_follow) { @@ -338,6 +345,11 @@ function init_shortcuts() { }, { 'disable_in_input': true }); + shortcut.add(shortcuts.auto_share, function () { + auto_share(); + }, { + 'disable_in_input': true + }); // Touches de navigation shortcut.add(shortcuts.prev_entry, prev_entry, { -- cgit v1.2.3 From 4a19676cf22e38922fbeacc7e7621d99c2d23095 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sun, 5 Jan 2014 02:25:56 +0100 Subject: Compatibilité MySQL 5.5 Windows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Testé vite fait avec MySQL 5.5 sous Windows, et les chaînes vides '' utilisées jusqu'à présent dans le cas de booléens faux causaient une erreur. Vérifier s'il n'y a pas d'autres exemples ailleurs dans le code --- app/Models/EntryDAO.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app/Models') diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index 99aaf3a0d..9070ae426 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -14,8 +14,8 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { $valuesTmp['content'], substr($valuesTmp['link'], 0, 1023), $valuesTmp['date'], - $valuesTmp['is_read'], - $valuesTmp['is_favorite'], + $valuesTmp['is_read'] ? 1 : 0, + $valuesTmp['is_favorite'] ? 1 : 0, $valuesTmp['id_feed'], substr($valuesTmp['tags'], 0, 1023), ); -- cgit v1.2.3 From 6eea96656a933b29b699999eec97299c4e363c79 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sun, 5 Jan 2014 18:06:06 +0100 Subject: Utilise QUERY_STRING plutôt que PATH_INFO pour favicons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Corrige https://github.com/marienfressinaud/FreshRSS/issues/348 Revenir dessus en cas de problème de cache HTTP des favicons. --- app/Models/Feed.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/Models') diff --git a/app/Models/Feed.php b/app/Models/Feed.php index f38828a42..df762c8fa 100644 --- a/app/Models/Feed.php +++ b/app/Models/Feed.php @@ -116,7 +116,7 @@ class FreshRSS_Feed extends Minz_Model { @unlink($path . '.txt'); } public function favicon () { - return Minz_Url::display ('/f.php/' . $this->hash()); + return Minz_Url::display ('/f.php?' . $this->hash()); } public function _id ($value) { -- cgit v1.2.3 From 46a5bdea25c0ae0624082dd08f3c41060e7fed86 Mon Sep 17 00:00:00 2001 From: Alexis Degrugillier Date: Wed, 8 Jan 2014 00:05:00 -0500 Subject: Ajout d'un filtre sur les articles lus Ajout d'un filtre sur les articles favoris --- app/Models/EntryDAO.php | 3 +++ app/i18n/en.php | 2 ++ app/i18n/fr.php | 2 ++ app/layout/nav_menu.phtml | 48 +++++++++++++++++++++++++++++++++++++---------- 4 files changed, 45 insertions(+), 10 deletions(-) (limited to 'app/Models') diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index 9070ae426..aaf4dcf6a 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -293,6 +293,9 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { case 'read': $where .= 'AND e1.is_read = 1 '; break; + case 'favorite': + $where .= 'AND e1.is_favorite = 1 '; + break; default: throw new FreshRSS_EntriesGetter_Exception ('Bad state in Entry->listByType: [' . $state . ']!'); } diff --git a/app/i18n/en.php b/app/i18n/en.php index 4588a054a..3717e0245 100644 --- a/app/i18n/en.php +++ b/app/i18n/en.php @@ -44,6 +44,8 @@ return array ( 'rss_view' => 'RSS feed', 'show_all_articles' => 'Show all articles', 'show_not_reads' => 'Show only unread', + 'show_read' => 'Show only read', + 'show_favorite' => 'Show favorites', 'older_first' => 'Oldest first', 'newer_first' => 'Newer first', diff --git a/app/i18n/fr.php b/app/i18n/fr.php index 20085e6b4..42b6f8dcf 100644 --- a/app/i18n/fr.php +++ b/app/i18n/fr.php @@ -44,6 +44,8 @@ return array ( 'rss_view' => 'Flux RSS', 'show_all_articles' => 'Afficher tous les articles', 'show_not_reads' => 'Afficher les non lus', + 'show_read' => 'Afficher les lus', + 'show_favorite' => 'Afficher les favoris', 'older_first' => 'Plus anciens en premier', 'newer_first' => 'Plus récents en premier', diff --git a/app/layout/nav_menu.phtml b/app/layout/nav_menu.phtml index 44b49b10c..adb8aea9b 100644 --- a/app/layout/nav_menu.phtml +++ b/app/layout/nav_menu.phtml @@ -128,24 +128,52 @@
  • + state, $url_state['params']['state']) <> 0) { + ?>
  • - state == 'not_read') { - $url_state['params']['state'] = 'all'; - ?> - +
  • + + + state, $url_state['params']['state']) <> 0) { + ?> +
  • -
  • + + + state, $url_state['params']['state']) <> 0) { + ?> +
  • + + + +
  • + + + state, $url_state['params']['state']) <> 0) { + ?> +
  • + + + +
  • + + +
  • Date: Sat, 11 Jan 2014 16:48:10 +0100 Subject: Mot de passe + nonce serveur MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Début de https://github.com/marienfressinaud/FreshRSS/issues/104 --- README.md | 7 ++----- app/Controllers/javascriptController.php | 27 +++++++++++++++++++++++++++ app/Models/Configuration.php | 4 ++++ app/views/javascript/nonce.phtml | 2 ++ 4 files changed, 35 insertions(+), 5 deletions(-) create mode 100644 app/views/javascript/nonce.phtml (limited to 'app/Models') diff --git a/README.md b/README.md index 96e25f4df..4100a8638 100644 --- a/README.md +++ b/README.md @@ -74,17 +74,14 @@ mysqldump -u utilisateur -p --databases freshrss > freshrss.sql ``` - ---- - # Bibliothèques incluses -* [SimplePie](https://github.com/simplepie/simplepie) +* [SimplePie](http://simplepie.org/) * [MINZ](https://github.com/marienfressinaud/MINZ) * [php-http-304](http://alexandre.alapetite.fr/doc-alex/php-http-304/) * [jQuery](http://jquery.com/) * [keyboard_shortcuts](http://www.openjs.com/scripts/events/keyboard_shortcuts/) -## Uniquement dans certaines configurations +## Uniquement pour certaines options * [bcrypt.js](https://github.com/dcodeIO/bcrypt.js) * [phpQuery](http://code.google.com/p/phpquery/) * [Lazy Load](http://www.appelsiini.net/projects/lazyload) diff --git a/app/Controllers/javascriptController.php b/app/Controllers/javascriptController.php index 2d0ff4984..e29f439d8 100755 --- a/app/Controllers/javascriptController.php +++ b/app/Controllers/javascriptController.php @@ -16,4 +16,31 @@ class FreshRSS_javascript_Controller extends Minz_ActionController { $catDAO = new FreshRSS_CategoryDAO(); $this->view->categories = $catDAO->listCategories(true, false); } + + // For Web-form login + public function nonceAction() { + header('Content-Type: application/json; charset=UTF-8'); + header('Last-Modified: ' . gmdate('D, d M Y H:i:s \G\M\T')); + header('Expires: 0'); + header('Cache-Control: private, no-cache, no-store, must-revalidate'); + header('Pragma: no-cache'); + + $user = isset($_GET['user']) ? $_GET['user'] : ''; + if (ctype_alnum($user)) { + try { + $conf = new FreshRSS_Configuration($user); + $hash = $conf->passwordHash; //CRYPT_BLOWFISH - Blowfish hashing with a salt as follows: "$2a$", "$2x$" or "$2y$", a two digit cost parameter, "$", and 22 characters from the alphabet "./0-9A-Za-z". + if (strlen($hash) >= 60) { + $this->view->salt1 = substr($hash, 0, 29); + $this->view->nonce = sha1(Minz_Configuration::salt() . uniqid(mt_rand(), true)); + Minz_Session::_param ('nonce', $this->view->nonce); + return; //Success + } + } catch (Minz_Exception $me) { + Minz_Log::record ('Login failure: ' . $me->getMessage(), Minz_Log::WARNING); + } + } + $this->view->nonce = ''; //Failure + $this->view->salt1 = ''; + } } diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php index c29e74603..8f394737a 100644 --- a/app/Models/Configuration.php +++ b/app/Models/Configuration.php @@ -9,6 +9,7 @@ class FreshRSS_Configuration { 'keep_history_default' => 0, 'mail_login' => '', 'token' => '', + 'passwordHash' => '', //CRYPT_BLOWFISH 'posts_per_page' => 20, 'view_mode' => 'normal', 'default_view' => 'not_read', @@ -162,6 +163,9 @@ class FreshRSS_Configuration { } } } + public function _passwordHash ($value) { + $this->data['passwordHash'] = ctype_graph($value) && (strlen($value) >= 60) ? $value : ''; + } public function _mail_login ($value) { $value = filter_var($value, FILTER_VALIDATE_EMAIL); if ($value) { diff --git a/app/views/javascript/nonce.phtml b/app/views/javascript/nonce.phtml new file mode 100644 index 000000000..4ac46c8fc --- /dev/null +++ b/app/views/javascript/nonce.phtml @@ -0,0 +1,2 @@ + $this->salt1, 'nonce' => $this->nonce)); -- cgit v1.2.3 From f720d41cbfb18edc1b0a694a7213682b96befa1f Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Mon, 13 Jan 2014 22:27:22 +0100 Subject: Mise à jour nom thèmes + chargement robuste des thèmes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renomme : default -> Origine En cas de thème introuvable, charge le thème par défaut, sinon le premier disponible. https://github.com/marienfressinaud/FreshRSS/issues/120 --- app/Controllers/configureController.php | 6 +- app/FreshRSS.php | 5 +- app/Models/Configuration.php | 2 +- app/Models/Themes.php | 28 +- app/views/configure/display.phtml | 5 +- p/themes/Dark/freshrss.css | 862 ++++++++++++++++++++++++++++++ p/themes/Dark/global.css | 487 +++++++++++++++++ p/themes/Dark/loader.gif | Bin 0 -> 404 bytes p/themes/Dark/metadata.json | 7 + p/themes/Flat/freshrss.css | 817 ++++++++++++++++++++++++++++ p/themes/Flat/global.css | 495 +++++++++++++++++ p/themes/Flat/icons/add.svg | 30 ++ p/themes/Flat/icons/all.svg | 32 ++ p/themes/Flat/icons/close.svg | 28 + p/themes/Flat/icons/configure.svg | 31 ++ p/themes/Flat/icons/down.svg | 31 ++ p/themes/Flat/icons/next.svg | 31 ++ p/themes/Flat/icons/prev.svg | 31 ++ p/themes/Flat/icons/refresh.svg | 31 ++ p/themes/Flat/icons/search.svg | 32 ++ p/themes/Flat/icons/up.svg | 31 ++ p/themes/Flat/loader.gif | Bin 0 -> 4251 bytes p/themes/Flat/metadata.json | 7 + p/themes/Origine/freshrss.css | 880 +++++++++++++++++++++++++++++++ p/themes/Origine/global.css | 503 ++++++++++++++++++ p/themes/Origine/loader.gif | Bin 0 -> 4167 bytes p/themes/Origine/metadata.json | 7 + p/themes/default/freshrss.css | 880 ------------------------------- p/themes/default/global.css | 503 ------------------ p/themes/default/loader.gif | Bin 4167 -> 0 bytes p/themes/default/metadata.json | 7 - p/themes/default_dark/freshrss.css | 862 ------------------------------ p/themes/default_dark/global.css | 487 ----------------- p/themes/default_dark/loader.gif | Bin 404 -> 0 bytes p/themes/default_dark/metadata.json | 7 - p/themes/flat-design/freshrss.css | 817 ---------------------------- p/themes/flat-design/global.css | 495 ----------------- p/themes/flat-design/icons/add.svg | 30 -- p/themes/flat-design/icons/all.svg | 32 -- p/themes/flat-design/icons/close.svg | 28 - p/themes/flat-design/icons/configure.svg | 31 -- p/themes/flat-design/icons/down.svg | 31 -- p/themes/flat-design/icons/next.svg | 31 -- p/themes/flat-design/icons/prev.svg | 31 -- p/themes/flat-design/icons/refresh.svg | 31 -- p/themes/flat-design/icons/search.svg | 32 -- p/themes/flat-design/icons/up.svg | 31 -- p/themes/flat-design/loader.gif | Bin 4251 -> 0 bytes p/themes/flat-design/metadata.json | 7 - 49 files changed, 4407 insertions(+), 4385 deletions(-) create mode 100644 p/themes/Dark/freshrss.css create mode 100644 p/themes/Dark/global.css create mode 100644 p/themes/Dark/loader.gif create mode 100644 p/themes/Dark/metadata.json create mode 100644 p/themes/Flat/freshrss.css create mode 100644 p/themes/Flat/global.css create mode 100644 p/themes/Flat/icons/add.svg create mode 100644 p/themes/Flat/icons/all.svg create mode 100644 p/themes/Flat/icons/close.svg create mode 100644 p/themes/Flat/icons/configure.svg create mode 100644 p/themes/Flat/icons/down.svg create mode 100644 p/themes/Flat/icons/next.svg create mode 100644 p/themes/Flat/icons/prev.svg create mode 100644 p/themes/Flat/icons/refresh.svg create mode 100644 p/themes/Flat/icons/search.svg create mode 100644 p/themes/Flat/icons/up.svg create mode 100644 p/themes/Flat/loader.gif create mode 100644 p/themes/Flat/metadata.json create mode 100644 p/themes/Origine/freshrss.css create mode 100644 p/themes/Origine/global.css create mode 100644 p/themes/Origine/loader.gif create mode 100644 p/themes/Origine/metadata.json delete mode 100644 p/themes/default/freshrss.css delete mode 100644 p/themes/default/global.css delete mode 100644 p/themes/default/loader.gif delete mode 100644 p/themes/default/metadata.json delete mode 100644 p/themes/default_dark/freshrss.css delete mode 100644 p/themes/default_dark/global.css delete mode 100644 p/themes/default_dark/loader.gif delete mode 100644 p/themes/default_dark/metadata.json delete mode 100644 p/themes/flat-design/freshrss.css delete mode 100644 p/themes/flat-design/global.css delete mode 100644 p/themes/flat-design/icons/add.svg delete mode 100644 p/themes/flat-design/icons/all.svg delete mode 100644 p/themes/flat-design/icons/close.svg delete mode 100644 p/themes/flat-design/icons/configure.svg delete mode 100644 p/themes/flat-design/icons/down.svg delete mode 100644 p/themes/flat-design/icons/next.svg delete mode 100644 p/themes/flat-design/icons/prev.svg delete mode 100644 p/themes/flat-design/icons/refresh.svg delete mode 100644 p/themes/flat-design/icons/search.svg delete mode 100644 p/themes/flat-design/icons/up.svg delete mode 100644 p/themes/flat-design/loader.gif delete mode 100644 p/themes/flat-design/metadata.json (limited to 'app/Models') diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php index 5b5770cbe..70144a8db 100755 --- a/app/Controllers/configureController.php +++ b/app/Controllers/configureController.php @@ -157,7 +157,11 @@ class FreshRSS_configure_Controller extends Minz_ActionController { 'scroll' => Minz_Request::param('mark_scroll', false), 'reception' => Minz_Request::param('mark_upon_reception', false), )); - $this->view->conf->_theme(Minz_Request::param('theme', 'default')); + $themeId = Minz_Request::param('theme', ''); + if ($themeId == '') { + $themeId = FreshRSS_Themes::defaultTheme; + } + $this->view->conf->_theme($themeId); $this->view->conf->_topline_read(Minz_Request::param('topline_read', false)); $this->view->conf->_topline_favorite(Minz_Request::param('topline_favorite', false)); $this->view->conf->_topline_date(Minz_Request::param('topline_date', false)); diff --git a/app/FreshRSS.php b/app/FreshRSS.php index e64a1986b..52f34c6e2 100644 --- a/app/FreshRSS.php +++ b/app/FreshRSS.php @@ -114,11 +114,10 @@ class FreshRSS extends Minz_FrontController { } private function loadStylesAndScripts ($loginOk) { - $theme = FreshRSS_Themes::get_infos($this->conf->theme); + $theme = FreshRSS_Themes::load($this->conf->theme); if ($theme) { - FreshRSS_Themes::setThemeId($this->conf->theme); foreach($theme['files'] as $file) { - Minz_View::appendStyle (Minz_Url::display ('/themes/' . $theme['path'] . '/' . $file . '?' . @filemtime(PUBLIC_PATH . '/themes/' . $theme['path'] . '/' . $file))); + Minz_View::appendStyle (Minz_Url::display ('/themes/' . $theme['id'] . '/' . $file . '?' . @filemtime(PUBLIC_PATH . '/themes/' . $theme['path'] . '/' . $file))); } } diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php index 8f394737a..f3fb76e72 100644 --- a/app/Models/Configuration.php +++ b/app/Models/Configuration.php @@ -25,7 +25,7 @@ class FreshRSS_Configuration { 'scroll' => false, 'reception' => false, ), - 'theme' => 'default', + 'theme' => 'Origine', 'shortcuts' => array( 'mark_read' => 'r', 'mark_favorite' => 'f', diff --git a/app/Models/Themes.php b/app/Models/Themes.php index a52812339..c7099a1df 100644 --- a/app/Models/Themes.php +++ b/app/Models/Themes.php @@ -3,13 +3,17 @@ class FreshRSS_Themes extends Minz_Model { private static $themesUrl = '/themes/'; private static $defaultIconsUrl = '/themes/icons/'; + public static $defaultTheme = 'Origine'; - public static function get() { - $themes_list = array_diff( + public static function getList() { + return array_values(array_diff( scandir(PUBLIC_PATH . self::$themesUrl), array('..', '.') - ); + )); + } + public static function get() { + $themes_list = self::getList(); $list = array(); foreach ($themes_list as $theme_dir) { $theme = self::get_infos($theme_dir); @@ -28,7 +32,7 @@ class FreshRSS_Themes extends Minz_Model { $content = file_get_contents($json_filename); $res = json_decode($content, true); if ($res && isset($res['files']) && is_array($res['files'])) { - $res['path'] = $theme_id; + $res['id'] = $theme_id; return $res; } } @@ -39,12 +43,26 @@ class FreshRSS_Themes extends Minz_Model { private static $themeIconsUrl; private static $themeIcons; - public static function setThemeId($theme_id) { + public static function load($theme_id) { + $infos = self::get_infos($theme_id); + if (!$infos) { + if ($theme_id !== self::$defaultTheme) { //Fall-back to default theme + return self::load(self::$defaultTheme); + } + $themes_list = self::getList(); + if (!empty($themes_list)) { + if ($theme_id !== $themes_list[0]) { //Fall-back to first theme + return self::load($themes_list[0]); + } + } + return false; + } self::$themeIconsUrl = self::$themesUrl . $theme_id . '/icons/'; self::$themeIcons = is_dir(PUBLIC_PATH . self::$themeIconsUrl) ? array_fill_keys(array_diff( scandir(PUBLIC_PATH . self::$themeIconsUrl), array('..', '.') ), 1) : array(); + return $infos; } public static function icon($name, $urlOnly = false) { diff --git a/app/views/configure/display.phtml b/app/views/configure/display.phtml index 3cc5442fb..725356c8d 100644 --- a/app/views/configure/display.phtml +++ b/app/views/configure/display.phtml @@ -21,9 +21,10 @@
    - + themes as $theme) { ?> - diff --git a/p/themes/Dark/freshrss.css b/p/themes/Dark/freshrss.css new file mode 100644 index 000000000..e9eb2c705 --- /dev/null +++ b/p/themes/Dark/freshrss.css @@ -0,0 +1,862 @@ +@charset "UTF-8"; + +/* STRUCTURE */ +.header { + display: table; + width: 100%; + background: #1c1c1c; + table-layout: fixed; +} + .header > .item { + display: table-cell; + padding: 10px 0; + border-bottom: 1px solid #2f2f2f; + vertical-align: middle; + text-align: center; + } + .header > .item.title { + width: 250px; + white-space: nowrap; + } + .logo { + display: inline-block; + font-size: 48px; + height: 32px; + width: 32px; + padding: 10px; + } + .header > .item.title h1 { + display: inline-block; + margin: 0; + } + .header > .item.search input { + width: 230px; + transition: width 200ms linear; + } + .header .item.search input:focus { + width: 330px; + } + .header > .item.configure { + width: 100px; + } + +.item a:hover { + text-decoration: none; +} + +#global { + display: table; + width: 100%; + height: 100%; + background: #1c1c1c; + table-layout: fixed; +} + .aside { + display: table-cell; + height: 100%; + width: 250px; + vertical-align: top; + border-right: 1px solid #2f2f2f; + background: #1c1c1c; + } + .aside .nav-form input { + width: 180px; + } + .aside.aside_flux { + padding: 10px 0 40px; + } + .aside.aside_feed .nav-form input { + width: 140px; + } + .aside.aside_feed .nav-form .dropdown .dropdown-menu { + right: -20px; + } + .aside.aside_feed .nav-form .dropdown .dropdown-menu:after { + right: 33px; + } + + .nav-login { + display: none; + } + + .nav_menu { + width: 100%; + background: #1c1c1c; + border-bottom: 1px solid #2f2f2f; + text-align: center; + padding: 5px 0; + } + .nav_menu .search { + display:none; + } + +.favicon { + height: 16px; + width: 16px; +} + +.categories { + margin: 0; + padding: 0; + text-align: center; + list-style: none; +} + .category { + display: block; + width: 220px; + margin: 10px auto; + text-align: left; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + .category .btn:first-child { + width: 195px; + position: relative; + } + .category.stick .btn:first-child { + width:160px; + } + .category .btn:first-child:not([data-unread="0"]):after { + content: attr(data-unread); + position: absolute; + top: 3px; right: 3px; + padding: 1px 5px; + background: #1a1a1a; + color: #888; + font-size: 90%; + border: 1px solid #000; + border-radius: 5px; + } + .category + .feeds:not(.active) { + display:none; + } + .categories .feeds { + width: 100%; + margin: 0; + list-style: none; + } + .categories .feeds .item.active { + background: #26303F; + } + .categories .feeds .item.active .feed { + color: #888; + } + .categories .feeds .item.empty .feed { + color: #e67e22; + } + .categories .feeds .item.empty.active { + background: #e67e22; + } + .categories .feeds .item.empty.active .feed { + color: #fff; + } + .categories .feeds .item.error .feed { + color: #BD362F; + } + .categories .feeds .item .feed { + display: inline-block; + margin: 0; + width: 165px; + line-height: 35px; + font-size: 90%; + vertical-align: middle; + text-align: left; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + .feed:not([data-unread="0"]):before { + content: "(" attr(data-unread) ") "; + } + .categories .feeds .dropdown-menu { + left: 0; + } + .categories .feeds .dropdown-menu:after { + left: 2px; + } + .categories .feeds .item .dropdown-toggle > .icon { + visibility: hidden; + cursor: pointer; + } + .categories .feeds .item .dropdown-target:target ~ .dropdown-toggle > .icon, + .categories .feeds .item:hover .dropdown-toggle > .icon, + .categories .feeds .item.active .dropdown-toggle > .icon { + background-color: #1c1c1c; + border-radius: 3px; + visibility: visible; + } + +.post { + padding: 10px 50px; +} + .post form { + margin: 10px 0; + } + +.day { + min-height: 50px; + padding: 0 10px; + font-size: 130%; + font-weight: bold; + line-height: 50px; + background: #1c1c1c; + border-top: 1px solid #888; + border-bottom: 1px solid #888; +} + .day:first-child { + border-top: none; + } + .day .name { + position: absolute; + right: 0; + width: 50%; + height: 1.5em; + padding: 0 10px 0 0; + overflow: hidden; + color: #aab; + font-size: 1.8em; + opacity: .3; + text-shadow: 0px -1px 0px #333; + font-style: italic; + white-space: nowrap; + text-overflow: ellipsis; + text-align: right; + } + +.flux { + border-left: 3px solid #aaa; + background: #1c1c1c; +} + .flux.not_read { + border-left: 3px solid #FF5300; + background: #1c1c1c; + } + .flux.favorite { + border-left: 3px solid #FFC300; + background: #1c1c1c; + } + .flux.current { + border-left: 3px solid #0062BE; + background: #1a1a1a; + } + + .flux_header { + background: inherit; + height: 25px; + font-size: 12px; + border-top: 1px solid #2f2f2f; + cursor: pointer; + } + .flux .item { + line-height: 40px; + white-space: nowrap; + } + .flux_header > .item { + overflow: hidden; + text-overflow: ellipsis; + } + .flux .item.manage { + width: 40px; + text-align: center; + } + .flux .item.website { + width: 200px; + } + .website .favicon { + padding: 5px; + } + .flux .item.title { + background: inherit; + } + .flux:hover .item.title { + border-right: 2px solid rgba(127, 127, 127, 0.1); + padding-right: 1em; + position: absolute; + } + .flux .item.title a { + color: #888; + outline: none; + } + .flux.not_read .item.title, + .flux.current .item.title { + font-weight: bold; + } + .flux .item.date { + width: 200px; + padding:0 5px 0 0; + text-align: right; + font-size: 10px; + color: #666; + } + .link { + width: 40px; + text-align: center; + } + +#stream.reader .flux { + padding: 0 0 30px; + border: none; + background: #1c1c1c; + color: #888; +} + #stream.reader .flux .author { + margin: 0 0 10px; + font-size: 90%; + color: #666; + } + +#stream.global { + text-align: center; +} + #stream.global .box-category { + display: inline-block; + width: 280px; + margin: 20px 10px; + vertical-align: top; + background: #1a1a1a; + border: 1px solid #000; + border-radius: 5px; + text-align: left; + box-shadow: 0 0 5px #2f2f2f; + } + #stream.global .category { + width: 100%; + margin: 0; + } + #stream.global .btn { + display: block; + width: auto; + height: 35px; + margin: 0; + padding: 0 10px; + background: #26303F; + border: none; + border-bottom: 1px solid #2f2f2f; + border-radius: 5px 5px 0 0; + line-height: 35px; + font-size: 120%; + } + #stream.global .btn:not([data-unread="0"]) { + font-weight:bold; + } + #stream.global .btn:first-child:not([data-unread="0"]):after { + top: 0; right: 5px; + border: 0; + background: none; + color: #666; + font-weight: bold; + box-shadow: none; + } + #stream.global .box-category .feeds { + display: block; + max-height: 250px; + margin: 0; + list-style: none; + overflow: auto; + } + #stream.global .box-category .feeds .item { + padding: 2px 10px; + font-size: 90%; + } + #stream.global .box-category .feed { + width: 220px; + } + +.content { + min-height: 150px; + max-width: 550px; + margin: 0 auto; + padding: 20px 10px; + line-height: 170%; + word-wrap: break-word; +} + .content h1, .content h2, .content h3 { + margin: 20px 0 5px; + } + .content > .title { + font-size: x-large; + margin: 0; + } + + .content p { + margin: 0 0 20px; + } + img.big { + display: block; + margin: 10px auto; + } + figure img.big { + margin: 0; + } + .content hr { + margin: 30px 0; + height: 1px; + background: #ddd; + border: 0; + } + .content pre { + margin: 10px auto; + padding: 10px; + overflow: auto; + background: #000; + color: #fff; + font-size: 110%; + } + .content q, .content blockquote { + display: block; + margin: 5px 0; + padding: 5px 20px; + font-style: italic; + border-left: 4px solid #ccc; + color: #666; + } + .content blockquote p { + margin: 0; + } + +#panel { + display: none; + position: fixed; + top: 10px; bottom: 10px; + left: 100px; right: 100px; + overflow: auto; + background: #1c1c1c; + border: 1px solid #95a5a6; + border-radius: 5px; +} + #panel .close { + position: fixed; + top: 10px; right: 0; + display: inline-block; + width: 26px; + height: 26px; + margin: 0 10px 0 0; + border: 1px solid #ccc; + border-radius: 20px; + text-align: center; + line-height: 26px; + background: #fff; + } + +#overlay { + display: none; + position: fixed; + top: 0; bottom: 0; + left: 0; right: 0; + background: rgba(0, 0, 0, 0.9); +} + +.flux_content .bottom { + font-size: 90%; + text-align: center; +} + +.hide_posts > :not(.active) > .flux_content { + display:none; +} + +/*** PAGINATION ***/ +.pagination { + display: table; + width: 100%; + margin: 0; + background: #1a1a1a; + text-align: center; + color: #888; + font-size: 80%; + line-height: 200%; + table-layout: fixed; +} + .pagination .item { + display: table-cell; + line-height: 40px; + } + .pagination .item.pager-current { + font-weight: bold; + font-size: 140%; + } + .pagination .pager-first, + .pagination .pager-previous, + .pagination .pager-next, + .pagination .pager-last { + width: 100px; + } + .pagination .item a { + display: block; + color: #333; + font-style: italic; + } + .pagination:first-child .item { + border-bottom: 1px solid #aaa; + } + .pagination:last-child .item { + border-top: 1px solid #aaa; + } + +#nav_entries { + display: table; + width: 250px; + height: 40px; + position: fixed; + bottom: 0; + left: 0; + margin: 0; + background: #1c1c1c; + border-top: 1px solid #2f2f2f; + text-align: center; + line-height: 40px; + table-layout: fixed; +} + #nav_entries .item { + display: table-cell; + width: 30%; + } + #nav_entries a { + display: block; + } + #nav_entries .i_up { + margin: 5px 0 0; + vertical-align: top; + } + +.loading { + background: url("loader.gif") center center no-repeat; + font-size: 0; +} + +#bigMarkAsRead { + display: block; + font-style: normal; + padding: 32px 0 64px 0; + text-align: center; + text-decoration: none; +} + #bigMarkAsRead:hover { + background: #1c1c1c; + color: #888; + } + .bigTick { + font-size: 72pt; + line-height: 1.6em; + } + +/*** NOTIFICATION ***/ +.notification { + position: absolute; + top: 10px; + left: 25%; right: 25%; + min-height: 30px; + padding: 10px; + line-height: 30px; + text-align: center; + border-radius: 5px; + box-shadow: 0 0 5px #666; + background: #1a1a1a; + color: #888; + font-weight: bold; + z-index: 10; +} + .notification.good { + border:1px solid #f4f899; + } + .notification.bad { + border:1px solid #f4a899; + } + .notification a.close { + display: inline-block; + width: 16px; + height: 16px; + float: right; + margin: -20px -20px 0 0; + padding: 5px; + background: #1a1a1a; + border-radius: 50px; + line-height: 16px; + } + .notification.good a.close{ + border:1px solid #f4f899; + } + .notification.bad a.close{ + border:1px solid #f4a899; + } + +.toggle_aside, .btn.toggle_aside { + display: none; +} + +.actualizeProgress { + position: fixed; + top: 10px; + left: 25%; right: 25%; + padding: 5px; + background: #1a1a1a; + text-align: center; + border: 1px solid #2f2f2f; + border-radius: 5px; +} + .actualizeProgress progress { + max-width: 100%; + vertical-align: middle; + } + .actualizeProgress .progress { + color: #888; + font-size: 90%; + vertical-align: middle; + } + +.logs { + border: 1px solid #aaa; +} + .log { + padding: 5px 2%; + overflow: auto; + background: #fafafa; + border-bottom: 1px solid #999; + color: #333; + font-size: 90%; + } + .log .date { + display: block; + } + .log.error { + background: #fdd; + color: #844; + } + .log.warning { + background: #ffe; + color: #c95; + } + .log.notice { + background: #f4f4f4; + color: #aaa; + } + .log.debug { + background: #111; + color: #eee; + } + +.form-group table { + border-collapse:collapse; + margin:10px 0 0 220px; + text-align:center; +} + +.form-group tr, .form-group th, .form-group td { + border:1px solid #2f2f2f; + font-weight:normal; + padding:.5em; +} + +select.number option { + text-align:right; +} + +@media(max-width: 840px) { + .header, + .aside .btn-important, + .aside .feeds .dropdown, + .flux_header .item.website span, + .item.date { + display: none; + } + .flux_header .item.website { + width: 40px; + text-align: center; + } + .flux_header .item.website .favicon { + padding: 12px; + } + + .nav-login { + display: block; + } + + .content { + font-size: 120%; + padding: 0; + } + + .pagination { + margin: 0 0 40px; + } + .pagination .pager-previous, .pagination .pager-next { + width: 100px; + } + + .toggle_aside, .btn.toggle_aside { + display: inline-block; + } + .aside { + position: fixed; + top: 0; left: 0; + width: 0; + overflow: hidden; + border-right: none; + z-index: 10; + transition: width 200ms linear; + } + .aside.aside_flux { + padding: 10px 0 0; + } + .aside:target { + width: 80%; + border-right: 1px solid #aaa; + overflow: auto; + } + .aside .toggle_aside { + position: absolute; + right: 0; + display: inline-block; + width: 26px; + height: 26px; + margin: 0 10px 0 0; + border: 1px solid #ccc; + border-radius: 20px; + text-align: center; + line-height: 26px; + } + .aside .categories { + margin: 30px 0; + } + + #nav_entries { + width: 100%; + } + + .nav_menu .btn { + margin: 5px 10px; + } + .nav_menu .stick { + margin: 0 10px; + } + .nav_menu .stick .btn { + margin: 5px 0; + } + .nav_menu .search { + display: inline-block; + max-width: 97%; + } + .nav_menu .search input { + max-width: 97%; + width: 90px; + } + .nav_menu .search input:focus { + width: 400px; + } + + #panel { + left: 5px; right: 5px; + } + + .day .date { + display: none; + } + .day .name { + height: 2.6em; + font-size: 1em; + text-shadow: none; + } + + .notification, + .actualizeProgress { + left: 10px; + right: 10px; + } +} + +/*** FALLBACK ***/ +.btn { + background: #1c1c1c; +} + .btn:hover { + background: -moz-linear-gradient(top, #4A5D7A 0%, #26303F 100%); + background: -webkit-linear-gradient(top, #4A5D7A 0%, #26303F 100%); + background: -o-linear-gradient(top, #4A5D7A 0%, #26303F 100%); + background: -ms-linear-gradient(top, #4A5D7A 0%, #26303F 100%); + } + .btn-important { + background: #26303F; + } + .btn-important:hover { + background: -moz-linear-gradient(top, #4A5D7A 0%, #26303F 100%); + background: -webkit-linear-gradient(top, #4A5D7A 0%, #26303F 100%); + background: -o-linear-gradient(top, #4A5D7A 0%, #26303F 100%); + background: -ms-linear-gradient(top, #4A5D7A 0%, #26303F 100%); + } + .btn-attention { + background: #880011; + } + .btn-attention:hover { + background: -moz-linear-gradient(top, #cc0044 0%, #880011 100%); + background: -webkit-linear-gradient(top, #cc0044 0%, #880011 100%); + background: -o-linear-gradient(top, #cc0044 0%, #880011 100%); + background: -ms-linear-gradient(top, #cc0044 0%, #880011 100%); + } + +.dropdown-menu:after { + -moz-transform: rotate(45deg); + -webkit-transform: rotate(45deg); + -ms-transform: rotate(45deg); +} + +.nav-head { + background: #fff; + background: -moz-linear-gradient(top, #fff 0%, #f0f0f0 100%); + background: -webkit-linear-gradient(top, #fff 0%, #f0f0f0 100%); + background: -o-linear-gradient(top, #fff 0%, #f0f0f0 100%); + background: -ms-linear-gradient(top, #fff 0%, #f0f0f0 100%); +} + +.header > .item.search input { + -moz-transition: width 200ms linear; + -webkit-transition: width 200ms linear; + -o-transition: width 200ms linear; + -ms-transition: width 200ms linear; +} + +@media(max-width: 840px) { + .aside { + -moz-transition: width 200ms linear; + -webkit-transition: width 200ms linear; + -o-transition: width 200ms linear; + -ms-transition: width 200ms linear; + } +} + +@media print { + .header, + .aside, + .nav_menu, + .day, + .flux_header, + .flux_content .bottom, + .pagination { + display: none; + } + + html, body { + background: #fff; + color: #000; + font-family: Serif; + font-size: 12pt; + } + + #global, + .flux_content { + display: block !important; + } + + .flux_content .content { + width: 100% !important; + text-align: justify; + } + + .flux_content .content a { + color: #000; + } + .flux_content .content a:after { + content: " (" attr(href) ") "; + text-decoration: underline; + } +} diff --git a/p/themes/Dark/global.css b/p/themes/Dark/global.css new file mode 100644 index 000000000..04eb723f1 --- /dev/null +++ b/p/themes/Dark/global.css @@ -0,0 +1,487 @@ +@charset "UTF-8"; + +/* FONTS */ +@font-face { + font-family: "OpenSans"; + src: url("../fonts/openSans.woff") format("woff"); +} + + +* { + margin: 0; + padding: 0; +} +html, body { + height: 100%; + font-size: 95%; + font-family: "OpenSans", "Cantarell", "Helvetica", "Arial", sans-serif; + color: #888; +} + +/* LIENS */ +a { + color: #6986B2; + text-decoration: none; +} + a:hover { + text-decoration: underline; + } + +/* LISTES */ +ul, ol, dl { + margin: 10px 0 10px 30px; + line-height: 190%; +} + dd { + margin: 0 0 10px 30px; + } + +/* TITRES */ +h1, h2, h3 { + min-height: 40px; + margin: 15px 0 5px; + line-height: 40px; +} + +/* IMG */ +figure { + margin: 5px 0 10px; + text-align: center; +} + figcaption { + display: inline-block; + padding: 3px 20px; + color: #999; + font-style: italic; + border-bottom: 1px solid #ccc; + } +img { + height: auto; + max-width: 100%; + vertical-align: middle; +} + a img { + border: none; + } + +/* VIDEOS */ +iframe, embed, object, video { + max-width: 100%; +} + +/* FORMULAIRES */ +legend { + display: block; + width: 100%; + margin: 20px 0 5px; + padding: 5px 0; + border-bottom: 1px solid #2f2f2f; + font-size: 150%; + clear: both; +} +label { + display: block; + min-height: 25px; + padding: 5px 0; + font-size: 14px; + line-height: 25px; + cursor: pointer; +} +input, select, textarea { + display: inline-block; + max-width: 100%; + min-height: 25px; + padding: 5px; + background: #333; + border: 1px solid #000; + border-radius: 3px; + color: #999; + line-height: 25px; + vertical-align: middle; + box-shadow: 0 2px 2px #1d1d1d inset; +} + option { + padding:0 .5em 0 .5em; + } + input[type="radio"], + input[type="checkbox"] { + width: 15px !important; + min-height: 15px !important; + } + input:focus, select:focus, textarea:focus { + color: #6986b2; + border-color: #2f2f2f; + } + input:invalid, select:invalid { + border-color: red; + box-shadow: 0 0 2px 1px red; + } + +.form-group { + margin: 0; +} + .form-group:after { + content: ""; + display: block; + clear: both; + } + .form-group.form-actions { + min-width: 250px; + padding: 5px 0; + background: #1a1a1a; + border-top: 1px solid #2f2f2f; + } + .form-group.form-actions .btn { + margin: 0 10px; + } + .form-group .group-name { + display: block; + float: left; + width: 200px; + padding: 10px 0; + text-align: right; + } + .form-group .group-controls { + min-width: 250px; + min-height: 25px; + margin: 0 0 0 220px; + padding: 5px 0; + } + .form-group .group-controls .control { + display: block; + min-height: 30px; + padding: 5px 0; + line-height: 25px; + font-size: 14px; + } + +.stick { + display: inline-block; + white-space: nowrap; + font-size: 0px; + vertical-align: middle; +} + .stick input, + .stick .btn { + border-radius: 0; + font-size: 14px; + } + .stick .btn:first-child, + .stick input:first-child { + border-radius: 3px 0 0 3px; + } + .stick .btn-important:first-child { + border-right: 1px solid #000; + } + .stick .btn:last-child, + .stick input:last-child { + border-radius: 0 3px 3px 0; + } + .stick .btn + .btn, + .stick .btn + input, + .stick input + .btn, + .stick input + input { + border-left: none; + } + .stick .btn + .dropdown > .btn { + border-left: none; + border-radius: 0 3px 3px 0; + } + .stick .btn + .dropdown a { + font-size: 12px; + } + +.btn { + display: inline-block; + min-height: 37px; + min-width: 15px; + padding: 5px 10px; + background: linear-gradient(to bottom, #fff 0%, #eee 100%); + border-radius: 3px; + border: 1px solid #000; + color: #888; + line-height: 20px; + vertical-align: middle; + cursor: pointer; + overflow: hidden; +} + a.btn { + min-height: 25px; + line-height: 25px; + } + .btn:hover { + background: linear-gradient(to bottom, #4A5D7A, #26303F); + text-decoration: none; + } + .btn.active, + .btn:active, + .dropdown-target:target ~ .btn.dropdown-toggle { + background: #26303F; + } + + .btn-important { + background: linear-gradient(to bottom, #0084CC, #0045CC); + color: #888888; + border: 1px solid #000000; + } + .btn-important:hover { + background: linear-gradient(to bottom, #0066CC, #0045CC); + } + .btn-important:active { + background: #0044CB; + box-shadow: none; + } + + .btn-attention { + background: linear-gradient(to bottom, #E95B57, #BD362F); + color: #888888; + border: 1px solid #000000; + } + .btn-attention:hover { + background: linear-gradient(to bottom, #D14641, #BD362F); + } + .btn-attention:active { + background: #BD362F; + box-shadow: none; + } + +/* NAVIGATION */ +.nav-list .nav-header, +.nav-list .item { + display: block; + height: 35px; + line-height: 35px; +} + .nav-list .item:hover { + background: #1a1a1a; + } + .nav-list .item:hover a { + color: #26303F; + } + .nav-list .item.active { + background: #26303F; + color: #1a1a1a; + } + .nav-list .item.active a { + color: #888; + } + .nav-list .disable { + color: #aaa; + background: #fafafa; + text-align: center; + } + .nav-list .item > * { + display: block; + padding: 0 10px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + .nav-list a:hover { + text-decoration: none; + } + .nav-list .item.error a { + color: #BD362F; + } + .nav-list .item.active.error a { + color: #fff; + background: #BD362F; + } + .nav-list .item.empty a { + color: #f39c12; + } + .nav-list .item.active.empty a { + color: #fff; + background: #f39c12; + } + + .nav-list .nav-header { + padding: 0 10px; + background: #1a1a1a; + border-bottom: 1px solid #2f2f2f; + font-weight: bold; + } + .nav-list .separator { + display: block; + height: 0; + margin: 5px 0; + border-bottom: 1px solid #ddd; + } + + .nav-list .nav-form { + padding: 3px; + text-align: center; + } + +.nav-head { + display: block; + margin: 0; + background: linear-gradient(to bottom, #fff, #f0f0f0); + border-bottom: 1px solid #ddd; + text-align: right; +} + .nav-head .item { + display: inline-block; + padding: 5px 10px; + } + +/* HORIZONTAL-LIST */ +.horizontal-list { + display: table; + table-layout: fixed; + margin: 0; + padding: 0; + width: 100%; +} + .horizontal-list .item { + display: table-cell; + vertical-align: middle; + } + +/* DROPDOWN */ +.dropdown { + position: relative; + display: inline-block; +} + .dropdown-target { + display: none; + } + + .dropdown-menu { + display: none; + min-width: 200px; + margin: 5px 0 0; + padding: 5px 0; + position: absolute; + right: 0px; + background: #1a1a1a; + border: 1px solid #888; + border-radius: 5px; + text-align: left; + } + .dropdown-menu:after { + content: ""; + position: absolute; + top: -6px; + right: 13px; + width: 10px; + height: 10px; + background: #1a1a1a; + border-top: 1px solid #888; + border-left: 1px solid #888; + z-index: -10; + transform: rotate(45deg); + } + .dropdown-header { + display: block; + padding: 0 5px; + color: #888; + font-weight: bold; + font-size: 14px; + line-height: 30px; + } + .dropdown-menu .item { + display: block; + height: 30px; + font-size: 90%; + line-height: 30px; + } + .dropdown-menu > .item > a { + display: block; + padding: 0 25px; + line-height: 30px; + } + .dropdown-menu > .item:hover { + background: #26303F; + color: #888; + } + .dropdown-menu > .item[aria-checked="true"] > a:before { + content: '✓ '; + font-weight: bold; + margin: 0 0 0 -1.2em; + padding: 0 0.2em 0 0; + } + .dropdown-menu > .item:hover > a { + color: #888; + text-decoration: none; + } + .dropdown-menu .input { + display: block; + height: 40px; + font-size: 90%; + line-height: 30px; + } + .dropdown-menu .input select, + .dropdown-menu .input input { + display: block; + height: 20px; + width: 95%; + margin: auto; + padding: 2px 5px; + border-radius: 3px; + } + .dropdown-menu .input select { + width: 70%; + height: auto; + } + .dropdown-menu .separator { + display: block; + height: 0; + margin: 5px 0; + border-bottom: 1px solid #888; + } + .dropdown-target:target ~ .dropdown-menu { + display: block; + z-index: 10; + } + .dropdown-close { + display: inline; + } + .dropdown-close a { + font-size: 0; + position: fixed; + top: 0; bottom: 0; + left: 0; right: 0; + display: block; + z-index: -10; + } + +/* ALERTS */ +.alert { + display: block; + width: 90%; + margin: 15px auto; + padding: 10px 15px; + background: #1a1a1a; + border: 1px solid #ccc; + border-right: 1px solid #aaa; + border-bottom: 1px solid #aaa; + border-radius: 5px; + color: #aaa; +} + .alert-head { + margin: 0; + font-weight: bold; + font-size: 110%; + } + .alert-warn { + border: 1px solid #c95; + color: #c95; + } + .alert-success { + border: 1px solid #484; + color: #484; + } + .alert-error { + border: 1px solid #844; + color: #844; + } + +/* ICÔNES */ +.icon { + display: inline-block; + width: 16px; + height: 16px; + vertical-align: middle; + line-height: 16px; +} diff --git a/p/themes/Dark/loader.gif b/p/themes/Dark/loader.gif new file mode 100644 index 000000000..86022be71 Binary files /dev/null and b/p/themes/Dark/loader.gif differ diff --git a/p/themes/Dark/metadata.json b/p/themes/Dark/metadata.json new file mode 100644 index 000000000..27cae27df --- /dev/null +++ b/p/themes/Dark/metadata.json @@ -0,0 +1,7 @@ +{ + "name": "Dark", + "author": "AD", + "description": "Le coté obscur du thème “Origine”", + "version": 0.1, + "files": ["global.css", "freshrss.css"] +} diff --git a/p/themes/Flat/freshrss.css b/p/themes/Flat/freshrss.css new file mode 100644 index 000000000..dca1b3f28 --- /dev/null +++ b/p/themes/Flat/freshrss.css @@ -0,0 +1,817 @@ +@charset "UTF-8"; + +/* STRUCTURE */ +body { + background: #fafafa; +} + +.header { + display: table; + width: 100%; + table-layout: fixed; + background: #ecf0f1; +} + .header > .item { + display: table-cell; + padding: 10px 0; + vertical-align: middle; + text-align: center; + } + .header > .item.title { + width: 250px; + white-space: nowrap; + } + .logo { + display: inline-block; + font-size: 48px; + height: 32px; + width: 32px; + padding: 10px; + } + .header > .item.title h1 { + display: inline-block; + margin: 0; + } + .header > .item.search input { + width: 230px; + transition: width 200ms linear; + } + .header .item.search input:focus { + width: 330px; + } + .header > .item.configure { + width: 100px; + } + +.item a:hover { + text-decoration: none; +} + +#global { + display: table; + width: 100%; + height: 100%; + table-layout: fixed; +} + .aside { + display: table-cell; + height: 100%; + width: 250px; + vertical-align: top; + background: #ecf0f1; + } + .aside .nav-form input { + width: 180px; + } + .aside.aside_flux { + padding: 10px 0 40px; + } + .aside.aside_feed .nav-form input { + width: 140px; + } + .aside.aside_feed .nav-form .dropdown-menu { + right: -20px; + } + .aside.aside_feed .nav-form .dropdown-menu:after { + right: 33px; + } + + .nav-login { + display: none; + } + + .nav_menu { + width: 100%; + text-align: center; + padding: 5px 0; + } + .nav_menu .search { + display:none; + } + +.favicon { + height: 16px; + width: 16px; +} + +.categories { + margin: 0; + padding: 0; + text-align: center; + list-style: none; +} + .category { + display: block; + width: 220px; + margin: 10px auto; + text-align: left; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + .category .btn:first-child { + width: 195px; + position: relative; + } + .category.stick .btn:first-child { + width:160px; + } + .category .btn:first-child:not([data-unread="0"]):after { + content: attr(data-unread); + position: absolute; + top: 5px; right: 5px; + padding: 0 5px; + color: #fff; + font-size: 90%; + background: #3498DB; + border-radius: 5px; + } + .category + .feeds:not(.active) { + display:none; + } + .categories .feeds { + width: 100%; + margin: 0 auto; + list-style: none; + } + .categories .feeds .item.active:after { + content: "⇢"; + line-height: 35px; + float: right; + } + .categories .feeds .item.empty .feed { + color: #e67e22; + } + .categories .feeds .item.error .feed { + color: #BD362F; + } + .categories .feeds .item .feed { + display: inline-block; + margin: 0; + width: 165px; + line-height: 35px; + font-size: 90%; + vertical-align: middle; + text-align: left; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + .feed:not([data-unread="0"]) { + font-weight:bold; + } + .feed:not([data-unread="0"]):before { + content: "(" attr(data-unread) ") "; + } + .categories .feeds .dropdown-menu { + left: 0; + } + .categories .feeds .dropdown-menu:after { + left: 2px; + } + .categories .feeds .item .dropdown-toggle > .icon { + visibility: hidden; + cursor: pointer; + } + .categories .feeds .item .dropdown-target:target ~ .dropdown-toggle > .icon, + .categories .feeds .item:hover .dropdown-toggle > .icon, + .categories .feeds .item.active .dropdown-toggle > .icon { + background-color: #95a5a6; + border-radius: 3px; + visibility: visible; + } + .categories .btn:hover .notRead, + .categories .btn.active .notRead { + background: #2980B9; + border-left: 3px solid #3498DB; + } + +.post { + padding: 10px 50px; +} + .post form { + margin: 10px 0; + } + +.day { + padding: 5px 15px; + font-size: 130%; + font-weight: bold; + line-height: 50px; + border-left: 3px solid #ecf0f1; +} + .day .name { + position: absolute; + right: 0; + width: 50%; + height: 1.5em; + padding: 0 10px 0 0; + overflow: hidden; + color: #aab; + font-size: 1.8em; + opacity: .3; + font-style: italic; + text-align: right; + white-space: nowrap; + text-overflow: ellipsis; + z-index: -10; + } + +.flux { + border-left: 3px solid #ecf0f1; +} + .flux:hover { + background: #fff; + } + .flux.not_read { + border-left-color: #FF5300; + background: #FFF3ED; + } + .flux.favorite { + border-left-color: #FFC300; + background: #FFF6DA; + } + .flux.current { + border-left-color: #3498db; + background: #fff; + } + + .flux_header { + background: inherit; + height: 25px; + font-size: 12px; + border-top: 1px solid #ecf0f1; + cursor: pointer; + } + .flux .item { + line-height: 40px; + white-space: nowrap; + } + .flux_header > .item { + overflow: hidden; + text-overflow: ellipsis; + } + .flux .item.manage { + width: 40px; + text-align: center; + } + .flux .item.website { + width: 200px; + } + .website .favicon { + padding: 5px; + } + .flux .item.title { + background: inherit; + } + .flux:hover .item.title { + border-right: 2px solid rgba(127, 127, 127, 0.1); + padding-right: 1em; + position: absolute; + } + .flux .item.title a { + color: #333; + outline: none; + } + .flux.current .item.title, + .flux.not_read .item.title { + font-weight: bold; + } + .flux .item.date { + width: 200px; + padding:0 5px 0 0; + text-align: right; + font-size: 10px; + color: #666; + } + .link { + width: 40px; + text-align: center; + } + +#stream.reader .flux { + position: relative; + padding: 0 0 30px; + border: none; + background: #ecf0f1; + color: #34495e; + font-size: 120%; +} + #stream.reader .flux .author { + margin: 0 0 10px; + font-size: 90%; + color: #aaa; + } + +#stream.global { + text-align: center; +} + #stream.global .box-category { + display: inline-block; + width: 280px; + margin: 20px 10px; + vertical-align: top; + border: 1px solid #ddd; + border-radius: 5px; + text-align: left; + } + #stream.global .category { + width: 100%; + margin: 0; + } + #stream.global .btn { + display: block; + width: auto; + height: 35px; + margin: 0; + padding: 0 10px; + background: #ecf0f1; + color: #333; + border-bottom: 1px solid #ddd; + border-radius: 5px 5px 0 0; + line-height: 35px; + font-size: 120%; + } + #stream.global .btn:not([data-unread="0"]) { + font-weight:bold; + } + #stream.global .btn:first-child:not([data-unread="0"]):after { + top: 0; right: 5px; + border: 0; + background: none; + color: #333; + font-weight: bold; + box-shadow: none; + } + #stream.global .box-category .feeds { + display: block; + max-height: 250px; + margin: 0; + list-style: none; + overflow: auto; + } + #stream.global .box-category .feeds .item { + padding: 2px 10px; + font-size: 90%; + } + #stream.global .box-category .feed { + width: 220px; + } + +.content { + min-height: 150px; + max-width: 550px; + margin: 0 auto; + padding: 20px 10px; + line-height: 170%; + word-wrap: break-word; +} + .content h1, .content h2, .content h3 { + margin: 20px 0 5px; + } + .content > .title { + font-size: x-large; + margin: 0; + } + + .content p { + margin: 0 0 20px; + } + img.big { + display: block; + margin: 10px auto; + } + figure img.big { + margin: 0; + } + .content hr { + margin: 30px 0; + height: 1px; + background: #ddd; + border: 0; + } + .content pre { + margin: 10px auto; + padding: 10px; + overflow: auto; + background: #000; + color: #fff; + font-size: 110%; + } + .content q, .content blockquote { + display: block; + margin: 5px 0; + padding: 5px 20px; + font-style: italic; + border-left: 4px solid #ccc; + color: #666; + } + .content blockquote p { + margin: 0; + } + +#panel { + display: none; + position: fixed; + top: 10px; bottom: 10px; + left: 100px; right: 100px; + overflow: auto; + background: #fff; + border: 1px solid #95a5a6; + border-radius: 5px; +} + #panel .close { + position: fixed; + top: 10px; right: 0px; + display: inline-block; + width: 26px; + height: 26px; + margin: 0 10px 0 0; + border-radius: 3px; + text-align: center; + line-height: 24px; + background: #95a5a6; + } + #panel .close:hover { + background: #7f8c8d; + } + +#overlay { + display: none; + position: fixed; + top: 0; bottom: 0; + left: 0; right: 0; + background: rgba(0, 0, 0, 0.9); +} + +.flux_content .bottom { + font-size: 90%; + text-align: center; +} + +.hide_posts > :not(.active) > .flux_content { + display:none; +} + +/*** PAGINATION ***/ +.pagination { + display: table; + width: 100%; + margin: 0; + background: #ecf0f1; + text-align: center; + color: #000; + font-size: 80%; + line-height: 200%; + table-layout: fixed; + font-weight: bold; +} + .pagination .item { + display: table-cell; + line-height: 40px; + vertical-align: top; + } + .pagination .item.pager-current { + font-weight: bold; + font-size: 140%; + color: #ecf0f1; + background: #34495e; + } + .pagination .item.pager-first, + .pagination .item.pager-previous, + .pagination .item.pager-next, + .pagination .item.pager-last { + width: 100px; + } + .pagination .item a { + display: block; + color: #000; + font-weight: bold; + line-height: 40px; + } + .pagination .item a:hover { + color: #ecf0f1; + background: #34495e; + } + +#nav_entries { + display: table; + width: 250px; + height: 40px; + position: fixed; + bottom: 0; + left: 0; + margin: 0; + background: #34495e; + text-align: center; + line-height: 40px; + table-layout: fixed; +} + #nav_entries .item { + display: table-cell; + width: 30%; + } + #nav_entries a { + display: block; + } + #nav_entries .i_up { + margin: 5px 0 0; + vertical-align: top; + } + +.pagination .loading, +.pagination a:hover.loading { + background: url("loader.gif") center center no-repeat #34495e; + font-size: 0; +} + +#bigMarkAsRead { + background: #ecf0f1; + display: block; + font-style: normal; + padding: 32px 0 64px 0; + text-align: center; + text-decoration: none; + text-shadow: 0 -1px 0 #aaa; +} + #bigMarkAsRead:hover { + background: #34495e; + color: #fff; + } + .bigTick { + font-size: 72pt; + line-height: 1.6em; + } + +/*** NOTIFICATION ***/ +.notification { + position: absolute; + top: 10px; + left: 25%; right: 25%; + min-height: 30px; + padding: 10px; + line-height: 30px; + text-align: center; + border-radius: 3px; + background: #ddd; + color: #666; + font-weight: bold; + z-index: 10; +} + .notification.good { + background: #1abc9c; + color: #fff; + } + .notification.bad { + background: #e74c3c; + color: #fff; + } + .notification a.close { + display: inline-block; + width: 16px; + height: 16px; + float: right; + margin: -16px -16px 0 0; + padding: 5px; + border-radius: 3px; + line-height: 16px; + } + .notification.good a.close { + background: #1abc9c; + } + .notification.bad a.close { + background: #e74c3c; + } + +.toggle_aside, .btn.toggle_aside { + display: none; +} + +.actualizeProgress { + position: fixed; + top: 10px; + left: 25%; right: 25%; + padding: 5px; + background: #3498db; + color: #fff; + text-align: center; + border-radius: 3px; + font-weight: bold; +} + .actualizeProgress progress { + max-width: 100%; + width: 250px; + height: 15px; + vertical-align: middle; + background: #fff; + border: none; + } + .actualizeProgress .progress { + color: #ecf0f1; + } + +.logs { + border: 1px solid #34495e; +} + .log { + margin: 10px 0; + padding: 5px 2%; + overflow: auto; + background: #fafafa; + color: #666; + font-size: 90%; + } + .log>.date { + margin: 0 10px 0 0; + padding: 5px 10px; + border-radius: 20px; + } + .log.error>.date { + background: #e74c3c; + color: #fff; + } + .log.warning>.date { + background: #f39c12; + } + .log.notice>.date { + background: #ecf0f1; + } + .log.debug>.date { + background: #111; + color: #eee; + } + +.form-group table { + border-collapse:collapse; + margin:10px 0 0 220px; + text-align:center; +} + +.form-group tr, .form-group th, .form-group td { + font-weight:normal; + padding:.5em; +} + +select.number option { + text-align:right; +} + +@media(max-width: 840px) { + .header, + .aside .btn-important, + .aside .feeds .dropdown, + .flux_header .item.website span, + .item.date { + display: none; + } + .flux_header .item.website { + width: 40px; + text-align: center; + } + .flux_header .item.website .favicon { + padding: 12px; + } + + .nav-login { + display: block; + } + + .content { + font-size: 120%; + padding: 0; + } + + .pagination { + margin: 0 0 40px; + } + .pagination .pager-previous, .pagination .pager-next { + width: 100px; + } + + .toggle_aside, .btn.toggle_aside { + display: inline-block; + } + .aside { + position: fixed; + top: 0; left: 0; + width: 0; + overflow: hidden; + z-index: 10; + transition: width 200ms linear; + background: #ecf0f1; + } + .aside.aside_flux { + padding: 10px 0 0; + } + .aside:target { + width: 80%; + border-right: 1px solid #aaa; + overflow: auto; + } + .aside .toggle_aside { + position: absolute; + right: 10px; + display: inline-block; + width: 26px; + height: 26px; + margin: 0 10px 0 0; + border-radius: 3px; + text-align: center; + line-height: 24px; + background: #95a5a6; + } + .aside .toggle_aside:hover { + background: #7f8c8d; + } + .aside .categories { + margin: 30px 0; + } + + #nav_entries { + width: 100%; + } + + .nav_menu .btn { + margin: 5px 10px; + } + .nav_menu .stick { + margin: 0 10px; + } + .nav_menu .stick .btn { + margin: 5px 0; + } + .nav_menu .search { + display: inline-block; + max-width: 97%; + } + .nav_menu .search input { + max-width: 97%; + width: 90px; + } + .nav_menu .search input:focus { + width: 400px; + } + + #panel { + left: 5px; right: 5px; + } + + .day .date { + display: none; + } + .day .name { + height: 2.6em; + font-size: 1em; + text-shadow: none; + } + + .notification, + .actualizeProgress { + left: 10px; + right: 10px; + } +} + +/*** FALLBACK ***/ +.dropdown-menu:after { + -moz-transform: rotate(45deg); + -webkit-transform: rotate(45deg); + -ms-transform: rotate(45deg); +} + +@media print { + .header, + .aside, + .nav_menu, + .day, + .flux_header, + .flux_content .bottom, + .pagination { + display: none; + } + + html, body { + background: #fff; + color: #000; + font-family: Serif; + font-size: 12pt; + } + + #global, + .flux_content { + display: block !important; + } + + .flux_content .content { + width: 100% !important; + text-align: justify; + } + + .flux_content .content a { + color: #000; + } + .flux_content .content a:after { + content: " (" attr(href) ") "; + text-decoration: underline; + } +} diff --git a/p/themes/Flat/global.css b/p/themes/Flat/global.css new file mode 100644 index 000000000..5fd0a4dcf --- /dev/null +++ b/p/themes/Flat/global.css @@ -0,0 +1,495 @@ +@charset "UTF-8"; + +/* FONTS */ +@font-face { + font-family: "OpenSans"; + src: url("../fonts/openSans.woff") format("woff"); +} + + +* { + margin: 0; + padding: 0; +} +html, body { + height: 100%; + font-size: 95%; + font-family: "OpenSans", "Cantarell", "Helvetica", "Arial", sans-serif; +} + +/* LIENS */ +a { + color: #2980b9; + text-decoration: none; +} + a:hover { + text-decoration: underline; + } + +/* LISTES */ +ul, ol, dl { + margin: 10px 0 10px 30px; + line-height: 190%; +} + dd { + margin: 0 0 10px 30px; + } + +/* TITRES */ +h1, h2, h3 { + min-height: 40px; + margin: 15px 0 5px; + line-height: 40px; +} + +/* IMG */ +figure { + margin: 5px 0 10px; + text-align: center; +} + figcaption { + display: inline-block; + padding: 3px 20px; + color: #999; + font-style: italic; + border-bottom: 1px solid #ccc; + } +img { + height: auto; + max-width: 100%; + vertical-align: middle; +} + a img { + border: none; + } + +/* VIDEOS */ +iframe, embed, object, video { + max-width: 100%; +} + +/* FORMULAIRES */ +legend { + display: inline-block; + margin: 20px 0 5px; + padding: 5px 20px; + font-size: 150%; + clear: both; + background: #ecf0f1; + border-radius: 20px; +} +label { + display: block; + min-height: 25px; + padding: 5px 0; + font-size: 12px; + line-height: 25px; + cursor: pointer; + font-weight: bold; + color: #444; +} +input, select, textarea { + display: inline-block; + max-width: 100%; + min-height: 25px; + padding: 5px; + background: #FFF; + border: none; + border-bottom: 3px solid #ddd; + color: #666; + line-height: 25px; + vertical-align: middle; + border-radius: 5px; +} + option { + padding:0 .5em 0 .5em; + } + input[type="radio"], + input[type="checkbox"] { + width: 15px !important; + min-height: 15px !important; + } + input:focus, select:focus, textarea:focus { + color: #333; + border-color: #2980b9; + } + input:invalid, select:invalid { + border-color: red; + box-shadow: 0 0 2px 1px red; + } + +.form-group { + margin: 5px 0; + border: 1px solid transparent; +} + .form-group:after { + content: ""; + display: block; + clear: both; + } + .form-group:hover { + background: #fff; + border: 1px solid #eee; + border-radius: 3px; + } + .form-group.form-actions { + min-width: 250px; + margin: 20px 0; + padding: 5px 0; + background: #ecf0f1; + border-top: 3px solid #bdc3c7; + border-radius: 5px 5px 0 0; + } + .form-group.form-actions .btn { + margin: 0 10px; + } + .form-group .group-name { + display: block; + float: left; + width: 200px; + padding: 10px 0; + text-align: right; + } + .form-group .group-controls { + min-width: 250px; + min-height: 25px; + margin: 0 0 0 220px; + padding: 5px 0; + } + .form-group .group-controls label { + font-weight: normal; + font-size: 14px; + color: #000; + } + .form-group .group-controls .control { + display: block; + min-height: 30px; + padding: 5px 0; + line-height: 25px; + font-size: 14px; + } + +.stick { + display: inline-block; + white-space: nowrap; + font-size: 0px; + vertical-align: middle; +} + .stick .btn, + .stick input { + font-size: 14px; + border-radius: 0; + } + .stick .btn:first-child, + .stick input:first-child { + border-radius: 5px 0 0 5px; + } + .stick .btn:last-child, + .stick input:last-child, + .stick .btn + .dropdown > .btn { + border-radius: 0 5px 5px 0; + } + .stick .btn + .dropdown a { + font-size: 12px; + } + +.btn { + display: inline-block; + min-height: 38px; + min-width: 18px; + padding: 5px 10px; + background: #3498db; + border-radius: 5px; + border: none; + border-bottom: 3px solid #2980b9; + color: #fff; + line-height: 20px; + vertical-align: middle; + cursor: pointer; + overflow: hidden; +} + a.btn { + min-height: 25px; + line-height: 25px; + } + .btn.active, + .btn:active, + .btn:hover, + .dropdown-target:target ~ .btn.dropdown-toggle { + background: #2980b9; + text-decoration: none; + } + + .btn-important { + background: #e67e22; + color: #fff; + border-bottom: 3px solid #d35400; + } + .btn-important:active, + .btn-important:hover { + background: #d35400; + } + + .btn-attention { + background: #e74c3c; + color: #fff; + border-bottom: 3px solid #c0392b; + } + .btn-attention:hover, + .btn-attention:active { + background: #c0392b; + } + +/* NAVIGATION */ +.nav-list { + border-right: 1px solid #ecf0f1; +} +.nav-list .nav-header, +.nav-list .item { + display: block; + height: 35px; + line-height: 35px; + margin: 5px 0; +} + .nav-list .item:hover, + .nav-list .item.active { + background: #2980b9; + color: #fff; + } + .nav-list .item:hover a, + .nav-list .item.active a { + color: #fff; + } + .nav-list .disable { + color: #aaa; + background: #fafafa; + text-align: center; + } + .nav-list .item > * { + display: block; + padding: 0 10px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + .nav-list a:hover { + text-decoration: none; + } + .nav-list .item.error a { + color: #BD362F; + } + .nav-list .item:hover.error a, + .nav-list .item.active.error a { + color: #fff; + background: #BD362F; + } + .nav-list .item.empty a { + color: #f39c12; + } + .nav-list .item:hover.empty a, + .nav-list .item.active.empty a { + color: #fff; + background: #f39c12; + } + + .nav-list .nav-header { + padding: 0 10px; + margin: 0; + color: #fff; + background: #34495e; + font-weight: bold; + } + .nav-list .separator { + display: block; + height: 0; + margin: 5px 0; + border-bottom: 1px solid #ddd; + } + + .nav-list .nav-form { + padding: 3px; + text-align: center; + } + +.nav-head { + display: block; + margin: 0; + background: #34495e; + color: #fff; + text-align: right; +} + .nav-head a { + color: #fff; + } + .nav-head .item { + display: inline-block; + padding: 5px 10px; + } + +/* HORIZONTAL-LIST */ +.horizontal-list { + display: table; + table-layout: fixed; + margin: 0; + padding: 0; + width: 100%; +} + .horizontal-list .item { + display: table-cell; + vertical-align: middle; + } + +/* DROPDOWN */ +.dropdown { + position: relative; + display: inline-block; +} + .dropdown-target { + display: none; + } + + .dropdown-menu { + display: none; + min-width: 200px; + margin: 5px 0 0; + padding: 5px 0; + position: absolute; + right: 0px; + background: #fff; + border: 1px solid #95a5a6; + border-radius: 3px; + text-align: left; + } + .dropdown-menu:after { + content: ""; + position: absolute; + top: -6px; + right: 13px; + width: 10px; + height: 10px; + background: #fff; + border-top: 1px solid #95a5a6; + border-left: 1px solid #95a5a6; + z-index: -10; + transform: rotate(45deg); + } + .dropdown-header { + display: block; + padding: 0 5px; + color: #34495e; + font-weight: bold; + font-size: 14px; + line-height: 30px; + } + .dropdown-menu > .item { + display: block; + height: 30px; + font-size: 90%; + line-height: 30px; + } + .dropdown-menu > .item > a { + display: block; + padding: 0 25px; + line-height: 30px; + } + .dropdown-menu > .item:hover > a { + background: #2980b9; + color: #fff; + } + .dropdown-menu > .item[aria-checked="true"] > a:before { + content: '✓ '; + font-weight: bold; + margin: 0 0 0 -1.2em; + padding: 0 0.2em 0 0; + } + .dropdown-menu > .item:hover > a { + color: #fff; + text-decoration: none; + } + .dropdown-menu .input { + display: block; + height: 40px; + font-size: 90%; + line-height: 30px; + } + .dropdown-menu label { + font-weight: normal; + } + .dropdown-menu .input select, + .dropdown-menu .input input { + display: block; + height: 20px; + width: 95%; + margin: auto; + padding: 2px 5px; + border-radius: 3px; + } + .dropdown-menu .input select { + width: 70%; + height: auto; + } + .dropdown-menu .separator { + display: block; + height: 0; + margin: 5px 0; + border-bottom: 1px solid #95a5a6; + } + .dropdown-target:target ~ .dropdown-menu { + display: block; + z-index: 10; + } + .dropdown-close { + display: inline; + } + .dropdown-close a { + font-size: 0; + position: fixed; + top: 0; bottom: 0; + left: 0; right: 0; + display: block; + z-index: -10; + } + +/* ALERTS */ +.alert { + display: block; + width: 90%; + margin: 15px auto; + padding: 10px 15px; + background: #f4f4f4; + border: 1px solid #ccc; + border-right: 1px solid #aaa; + border-bottom: 1px solid #aaa; + border-radius: 5px; + color: #aaa; + text-shadow: 0 0 1px #eee; +} + .alert-head { + margin: 0; + font-weight: bold; + font-size: 110%; + } + .alert-warn { + background: #ffe; + border: 1px solid #eeb; + color: #c95; + } + .alert-success { + background: #dfd; + border: 1px solid #cec; + color: #484; + } + .alert-error { + background: #fdd; + border: 1px solid #ecc; + color: #844; + } + +/* ICÔNES */ +.icon { + display: inline-block; + width: 16px; + height: 16px; + vertical-align: middle; + line-height: 16px; +} diff --git a/p/themes/Flat/icons/add.svg b/p/themes/Flat/icons/add.svg new file mode 100644 index 000000000..15767a3ad --- /dev/null +++ b/p/themes/Flat/icons/add.svg @@ -0,0 +1,30 @@ + + + + + + + + image/svg+xml + + Gnome Symbolic Icon Theme + + + + + + + Gnome Symbolic Icon Theme + + + + + + + + + + + + + diff --git a/p/themes/Flat/icons/all.svg b/p/themes/Flat/icons/all.svg new file mode 100644 index 000000000..d20e0f5bf --- /dev/null +++ b/p/themes/Flat/icons/all.svg @@ -0,0 +1,32 @@ + + + + + + + + image/svg+xml + + Gnome Symbolic Icon Theme + + + + + + + Gnome Symbolic Icon Theme + + + + + + + + + + + + + + + diff --git a/p/themes/Flat/icons/close.svg b/p/themes/Flat/icons/close.svg new file mode 100644 index 000000000..629fda7ff --- /dev/null +++ b/p/themes/Flat/icons/close.svg @@ -0,0 +1,28 @@ + + + + + Gnome Symbolic Icon Theme + + + + image/svg+xml + + Gnome Symbolic Icon Theme + + + + + + + + + + + + + + + + + diff --git a/p/themes/Flat/icons/configure.svg b/p/themes/Flat/icons/configure.svg new file mode 100644 index 000000000..969c5719f --- /dev/null +++ b/p/themes/Flat/icons/configure.svg @@ -0,0 +1,31 @@ + + + + + + + + image/svg+xml + + Gnome Symbolic Icon Theme + + + + + + + Gnome Symbolic Icon Theme + + + + + + + + + + + + + + diff --git a/p/themes/Flat/icons/down.svg b/p/themes/Flat/icons/down.svg new file mode 100644 index 000000000..31730626f --- /dev/null +++ b/p/themes/Flat/icons/down.svg @@ -0,0 +1,31 @@ + + + + + + + + image/svg+xml + + Gnome Symbolic Icon Theme + + + + + + + Gnome Symbolic Icon Theme + + + + + + + + + + + + + + diff --git a/p/themes/Flat/icons/next.svg b/p/themes/Flat/icons/next.svg new file mode 100644 index 000000000..d75cc40f5 --- /dev/null +++ b/p/themes/Flat/icons/next.svg @@ -0,0 +1,31 @@ + + + + + + + + image/svg+xml + + Gnome Symbolic Icon Theme + + + + + + + Gnome Symbolic Icon Theme + + + + + + + + + + + + + + diff --git a/p/themes/Flat/icons/prev.svg b/p/themes/Flat/icons/prev.svg new file mode 100644 index 000000000..9ba03ceb2 --- /dev/null +++ b/p/themes/Flat/icons/prev.svg @@ -0,0 +1,31 @@ + + + + + + + + image/svg+xml + + Gnome Symbolic Icon Theme + + + + + + + Gnome Symbolic Icon Theme + + + + + + + + + + + + + + diff --git a/p/themes/Flat/icons/refresh.svg b/p/themes/Flat/icons/refresh.svg new file mode 100644 index 000000000..8f95bf443 --- /dev/null +++ b/p/themes/Flat/icons/refresh.svg @@ -0,0 +1,31 @@ + + + + + + + + image/svg+xml + + Gnome Symbolic Icon Theme + + + + + + + Gnome Symbolic Icon Theme + + + + + + + + + + + + + + diff --git a/p/themes/Flat/icons/search.svg b/p/themes/Flat/icons/search.svg new file mode 100644 index 000000000..bca7571b4 --- /dev/null +++ b/p/themes/Flat/icons/search.svg @@ -0,0 +1,32 @@ + + + + + + + + image/svg+xml + + Gnome Symbolic Icon Theme + + + + + + + Gnome Symbolic Icon Theme + + + + + + + + + + + + + + + diff --git a/p/themes/Flat/icons/up.svg b/p/themes/Flat/icons/up.svg new file mode 100644 index 000000000..3ab11b168 --- /dev/null +++ b/p/themes/Flat/icons/up.svg @@ -0,0 +1,31 @@ + + + + + + + + image/svg+xml + + Gnome Symbolic Icon Theme + + + + + + + Gnome Symbolic Icon Theme + + + + + + + + + + + + + + diff --git a/p/themes/Flat/loader.gif b/p/themes/Flat/loader.gif new file mode 100644 index 000000000..ce36565b3 Binary files /dev/null and b/p/themes/Flat/loader.gif differ diff --git a/p/themes/Flat/metadata.json b/p/themes/Flat/metadata.json new file mode 100644 index 000000000..6b94d11c2 --- /dev/null +++ b/p/themes/Flat/metadata.json @@ -0,0 +1,7 @@ +{ + "name": "Flat design", + "author": "Marien Fressinaud", + "description": "Thème plat pour FreshRSS", + "version": 0.1, + "files": ["global.css", "freshrss.css"] +} \ No newline at end of file diff --git a/p/themes/Origine/freshrss.css b/p/themes/Origine/freshrss.css new file mode 100644 index 000000000..593f21d30 --- /dev/null +++ b/p/themes/Origine/freshrss.css @@ -0,0 +1,880 @@ +@charset "UTF-8"; + +/* STRUCTURE */ +.header { + display: table; + width: 100%; + background: #f4f4f4; + table-layout: fixed; +} + .header > .item { + display: table-cell; + padding: 10px 0; + border-bottom: 1px solid #aaa; + vertical-align: middle; + text-align: center; + } + .header > .item.title { + width: 250px; + white-space: nowrap; + } + .logo { + display: inline-block; + font-size: 48px; + height: 32px; + width: 32px; + padding: 10px; + } + .header > .item.title h1 { + display: inline-block; + margin: 0; + text-shadow: 1px -1px 0 #ccc; + } + .header > .item.search input { + width: 230px; + transition: width 200ms linear; + } + .header .item.search input:focus { + width: 330px; + } + .header > .item.configure { + width: 100px; + } + +.item a:hover { + text-decoration: none; +} + +#global { + display: table; + width: 100%; + height: 100%; + background: #fafafa; + table-layout: fixed; +} + .aside { + display: table-cell; + height: 100%; + width: 250px; + vertical-align: top; + border-right: 1px solid #aaa; + background: #fff; + } + .aside .nav-form input { + width: 180px; + } + .aside.aside_flux { + padding: 10px 0 40px; + } + .aside.aside_feed .nav-form input { + width: 140px; + } + .aside.aside_feed .nav-form .dropdown .dropdown-menu { + right: -20px; + } + .aside.aside_feed .nav-form .dropdown .dropdown-menu:after { + right: 33px; + } + + .nav-login { + display: none; + } + + .nav_menu { + width: 100%; + background: #fafafa; + border-bottom: 1px solid #aaa; + text-align: center; + padding: 5px 0; + } + .nav_menu .search { + display:none; + } + +.favicon { + height: 16px; + width: 16px; +} + +.categories { + margin: 0; + padding: 0; + text-align: center; + list-style: none; +} + .category { + display: block; + width: 220px; + margin: 10px auto; + text-align: left; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + .category .btn:first-child { + width: 195px; + position: relative; + } + .category.stick .btn:first-child { + width:160px; + } + .category .btn:first-child:not([data-unread="0"]):after { + content: attr(data-unread); + position: absolute; + top: 3px; right: 3px; + padding: 1px 5px; + background: #ccc; + color: #fff; + font-size: 90%; + border: 1px solid #bbb; + border-radius: 5px; + box-shadow: 1px 3px 3px #aaa inset; + text-shadow: 0 0 1px #aaa; + } + .category + .feeds:not(.active) { + display:none; + } + .categories .feeds { + width: 100%; + margin: 0; + list-style: none; + } + .categories .feeds .item.active { + background: #0062BE; + } + .categories .feeds .item.active .feed { + color: #fff; + } + .categories .feeds .item.empty .feed { + color: #e67e22; + } + .categories .feeds .item.empty.active { + background: #e67e22; + } + .categories .feeds .item.empty.active .feed { + color: #fff; + } + .categories .feeds .item.error .feed { + color: #BD362F; + } + .categories .feeds .item .feed { + display: inline-block; + margin: 0; + width: 165px; + line-height: 35px; + font-size: 90%; + vertical-align: middle; + text-align: left; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + .feed:not([data-unread="0"]) { + font-weight:bold; + } + .feed:not([data-unread="0"]):before { + content: "(" attr(data-unread) ") "; + } + .categories .feeds .dropdown-menu { + left: 0; + } + .categories .feeds .dropdown-menu:after { + left: 2px; + } + .categories .feeds .item .dropdown-toggle > .icon { + visibility: hidden; + cursor: pointer; + } + .categories .feeds .item .dropdown-target:target ~ .dropdown-toggle > .icon, + .categories .feeds .item:hover .dropdown-toggle > .icon, + .categories .feeds .item.active .dropdown-toggle > .icon { + background-color: #fff; + border-radius: 3px; + visibility: visible; + } + +.post { + padding: 10px 50px; +} + .post form { + margin: 10px 0; + } + +.day { + min-height: 50px; + padding: 0 10px; + font-size: 130%; + font-weight: bold; + line-height: 50px; + background: #fff; + border-top: 1px solid #aaa; + border-bottom: 1px solid #aaa; +} + .day:first-child { + border-top: none; + } + .day .name { + position: absolute; + right: 0; + width: 50%; + height: 1.5em; + padding: 0 10px 0 0; + overflow: hidden; + color: #aab; + font-size: 1.8em; + opacity: .3; + text-shadow: 0px -1px 0px #333; + font-style: italic; + white-space: nowrap; + text-overflow: ellipsis; + text-align: right; + } + +.flux { + border-left: 3px solid #aaa; + background: #fafafa; +} + .flux:hover { + background: #fff; + } + .flux.not_read { + border-left: 3px solid #FF5300; + background: #FFF3ED; + } + .flux.favorite { + border-left: 3px solid #FFC300; + background: #FFF6DA; + } + .flux.current { + border-left: 3px solid #0062BE; + background: #fff; + } + + .flux_header { + background: inherit; + height: 25px; + font-size: 12px; + border-top: 1px solid #ddd; + cursor: pointer; + } + .flux .item { + line-height: 40px; + white-space: nowrap; + } + .flux_header > .item { + overflow: hidden; + text-overflow: ellipsis; + } + .flux .item.manage { + width: 40px; + text-align: center; + } + .flux .item.website { + width: 200px; + } + .website .favicon { + padding: 5px; + } + .flux .item.title { + background: inherit; + } + .flux:hover .item.title { + border-right: 2px solid rgba(127, 127, 127, 0.1); + padding-right: 1em; + position: absolute; + } + .flux .item.title a { + color: #000; + outline: none; + } + .flux.not_read .item.title, + .flux.current .item.title { + font-weight: bold; + } + .flux .item.date { + width: 200px; + padding:0 5px 0 0; + text-align: right; + font-size: 10px; + color: #666; + } + .link { + width: 40px; + text-align: center; + } + +#stream.reader .flux { + padding: 0 0 30px; + border: none; + background: #f0f0f0; + color: #333; +} + #stream.reader .flux .author { + margin: 0 0 10px; + font-size: 90%; + color: #666; + } + +#stream.global { + text-align: center; +} + #stream.global .box-category { + display: inline-block; + width: 280px; + margin: 20px 10px; + vertical-align: top; + background: #fff; + border: 1px solid #aaa; + border-radius: 5px; + text-align: left; + box-shadow: 0 0 5px #bbb; + } + #stream.global .category { + width: 100%; + margin: 0; + } + #stream.global .btn { + display: block; + width: auto; + height: 35px; + margin: 0; + padding: 0 10px; + background: #eee; + border: none; + border-bottom: 1px solid #aaa; + border-radius: 5px 5px 0 0; + line-height: 35px; + font-size: 120%; + } + #stream.global .btn:not([data-unread="0"]) { + font-weight:bold; + } + #stream.global .btn:first-child:not([data-unread="0"]):after { + top: 0; right: 5px; + border: 0; + background: none; + color: #666; + font-weight: bold; + box-shadow: none; + } + #stream.global .box-category .feeds { + display: block; + max-height: 250px; + margin: 0; + list-style: none; + overflow: auto; + } + #stream.global .box-category .feeds .item { + padding: 2px 10px; + font-size: 90%; + } + #stream.global .box-category .feed { + width: 220px; + } + +.content { + min-height: 150px; + max-width: 550px; + margin: 0 auto; + padding: 20px 10px; + line-height: 170%; + word-wrap: break-word; +} + .content h1, .content h2, .content h3 { + margin: 20px 0 5px; + } + .content > .title { + font-size: x-large; + margin: 0; + } + + .content p { + margin: 0 0 20px; + } + img.big { + display: block; + margin: 10px auto; + } + figure img.big { + margin: 0; + } + .content hr { + margin: 30px 0; + height: 1px; + background: #ddd; + border: 0; + } + .content pre { + margin: 10px auto; + padding: 10px; + overflow: auto; + background: #000; + color: #fff; + font-size: 110%; + } + .content q, .content blockquote { + display: block; + margin: 5px 0; + padding: 5px 20px; + font-style: italic; + border-left: 4px solid #ccc; + color: #666; + } + .content blockquote p { + margin: 0; + } + +#panel { + display: none; + position: fixed; + top: 10px; bottom: 10px; + left: 100px; right: 100px; + overflow: auto; + background: #fff; + border: 1px solid #95a5a6; + border-radius: 5px; +} + #panel .close { + position: fixed; + top: 10px; right: 0; + display: inline-block; + width: 26px; + height: 26px; + margin: 0 10px 0 0; + border: 1px solid #ccc; + border-radius: 20px; + text-align: center; + line-height: 26px; + background: #fff; + } + +#overlay { + display: none; + position: fixed; + top: 0; bottom: 0; + left: 0; right: 0; + background: rgba(0, 0, 0, 0.9); +} + +.flux_content .bottom { + font-size: 90%; + text-align: center; +} + +.hide_posts > :not(.active) > .flux_content { + display:none; +} + +/*** PAGINATION ***/ +.pagination { + display: table; + width: 100%; + margin: 0; + background: #fafafa; + text-align: center; + color: #333; + font-size: 80%; + line-height: 200%; + table-layout: fixed; +} + .pagination .item { + display: table-cell; + line-height: 40px; + } + .pagination .item.pager-current { + font-weight: bold; + font-size: 140%; + } + .pagination .pager-first, + .pagination .pager-previous, + .pagination .pager-next, + .pagination .pager-last { + width: 100px; + } + .pagination .item a { + display: block; + color: #333; + font-style: italic; + } + .pagination:first-child .item { + border-bottom: 1px solid #aaa; + } + .pagination:last-child .item { + border-top: 1px solid #aaa; + } + +#nav_entries { + display: table; + width: 250px; + height: 40px; + position: fixed; + bottom: 0; + left: 0; + margin: 0; + background: #fff; + border-top: 1px solid #ddd; + text-align: center; + line-height: 40px; + table-layout: fixed; +} + #nav_entries .item { + display: table-cell; + width: 30%; + } + #nav_entries a { + display: block; + } + #nav_entries .i_up { + margin: 5px 0 0; + vertical-align: top; + } + +.loading { + background: url("loader.gif") center center no-repeat; + font-size: 0; +} + +#bigMarkAsRead { + display: block; + font-style: normal; + padding: 32px 0 64px 0; + text-align: center; + text-decoration: none; + text-shadow: 0 -1px 0 #aaa; +} + #bigMarkAsRead:hover { + background: #333; + color: #fff; + } + .bigTick { + font-size: 72pt; + line-height: 1.6em; + } + +/*** NOTIFICATION ***/ +.notification { + position: absolute; + top: 10px; + left: 25%; right: 25%; + min-height: 30px; + padding: 10px; + line-height: 30px; + text-align: center; + border-radius: 5px; + box-shadow: 0 0 5px #666; + background: #ddd; + color: #666; + font-weight: bold; + z-index: 10; +} + .notification.good { + background: #f4f899; + } + .notification.bad { + background: #f4a899; + } + .notification a.close { + display: inline-block; + width: 16px; + height: 16px; + float: right; + margin: -20px -20px 0 0; + padding: 5px; + background: #fff; + border-radius: 50px; + border: 1px solid #aaa; + line-height: 16px; + } + +.toggle_aside, .btn.toggle_aside { + display: none; +} + +.actualizeProgress { + position: fixed; + top: 10px; + left: 25%; right: 25%; + padding: 5px; + background: #fff; + text-align: center; + border: 1px solid #ddd; + border-radius: 5px; +} + .actualizeProgress progress { + max-width: 100%; + vertical-align: middle; + } + .actualizeProgress .progress { + color: #999; + font-size: 90%; + vertical-align: middle; + } + +.logs { + border: 1px solid #aaa; +} + .log { + padding: 5px 2%; + overflow: auto; + background: #fafafa; + border-bottom: 1px solid #999; + color: #333; + font-size: 90%; + } + .log .date { + display: block; + } + .log.error { + background: #fdd; + color: #844; + } + .log.warning { + background: #ffe; + color: #c95; + } + .log.notice { + background: #f4f4f4; + color: #aaa; + } + .log.debug { + background: #111; + color: #eee; + } + +.form-group table { + border-collapse:collapse; + margin:10px 0 0 220px; + text-align:center; +} + +.form-group tr, .form-group th, .form-group td { + border:1px solid #DDD; + font-weight:normal; + padding:.5em; +} + +select.number option { + text-align:right; +} + +@media(max-width: 840px) { + .header, + .aside .btn-important, + .aside .feeds .dropdown, + .flux_header .item.website span, + .item.date { + display: none; + } + .flux_header .item.website { + width: 40px; + text-align: center; + } + .flux_header .item.website .favicon { + padding: 12px; + } + + .nav-login { + display: block; + } + + .content { + font-size: 120%; + padding: 0; + } + + .pagination { + margin: 0 0 40px; + } + .pagination .pager-previous, .pagination .pager-next { + width: 100px; + } + + .toggle_aside, .btn.toggle_aside { + display: inline-block; + } + .aside { + position: fixed; + top: 0; left: 0; + width: 0; + overflow: hidden; + border-right: none; + z-index: 10; + transition: width 200ms linear; + } + .aside.aside_flux { + padding: 10px 0 0; + } + .aside:target { + width: 80%; + border-right: 1px solid #aaa; + overflow: auto; + } + .aside .toggle_aside { + position: absolute; + right: 0; + display: inline-block; + width: 26px; + height: 26px; + margin: 0 10px 0 0; + border: 1px solid #ccc; + border-radius: 20px; + text-align: center; + line-height: 26px; + } + .aside .categories { + margin: 30px 0; + } + + #nav_entries { + width: 100%; + } + + .nav_menu .btn { + margin: 5px 10px; + } + .nav_menu .stick { + margin: 0 10px; + } + .nav_menu .stick .btn { + margin: 5px 0; + } + .nav_menu .search { + display: inline-block; + max-width: 97%; + } + .nav_menu .search input { + max-width: 97%; + width: 90px; + } + .nav_menu .search input:focus { + width: 400px; + } + + #panel { + left: 5px; right: 5px; + } + + .day .date { + display: none; + } + .day .name { + height: 2.6em; + font-size: 1em; + text-shadow: none; + } + + .notification, + .actualizeProgress { + left: 10px; + right: 10px; + } +} + +/*** FALLBACK ***/ +.btn { + background: #fff; + background: -moz-linear-gradient(top, #fff 0%, #eee 100%); + background: -webkit-linear-gradient(top, #fff 0%, #eee 100%); + background: -o-linear-gradient(top, #fff 0%, #eee 100%); + background: -ms-linear-gradient(top, #fff 0%, #eee 100%); +} + .btn:hover { + background: #f0f0f0; + background: -moz-linear-gradient(top, #f8f8f8 0%, #f0f0f0 100%); + background: -webkit-linear-gradient(top, #f8f8f8 0%, #f0f0f0 100%); + background: -o-linear-gradient(top, #f8f8f8 0%, #f0f0f0 100%); + background: -ms-linear-gradient(top, #f8f8f8 0%, #f0f0f0 100%); + } + .btn-important { + background: #0084CC; + background: -moz-linear-gradient(top, #0084CC 0%, #0045CC 100%); + background: -webkit-linear-gradient(top, #0084CC 0%, #0045CC 100%); + background: -o-linear-gradient(top, #0084CC 0%, #0045CC 100%); + background: -ms-linear-gradient(top, #0084CC 0%, #0045CC 100%); + } + .btn-important:hover { + background: -moz-linear-gradient(top, #0066CC 0%, #0045CC 100%); + background: -webkit-linear-gradient(top, #0066CC 0%, #0045CC 100%); + background: -o-linear-gradient(top, #0066CC 0%, #0045CC 100%); + background: -ms-linear-gradient(top, #0066CC 0%, #0045CC 100%); + } + .btn-attention { + background: #E95B57; + background: -moz-linear-gradient(top, #E95B57 0%, #BD362F 100%); + background: -webkit-linear-gradient(top, #E95B57 0%, #BD362F 100%); + background: -o-linear-gradient(top, #E95B57 0%, #BD362F 100%); + background: -ms-linear-gradient(top, #E95B57 0%, #BD362F 100%); + } + .btn-attention:hover { + background: -moz-linear-gradient(top, #D14641 0%, #BD362F 100%); + background: -webkit-linear-gradient(top, #D14641 0%, #BD362F 100%); + background: -o-linear-gradient(top, #D14641 0%, #BD362F 100%); + background: -ms-linear-gradient(top, #D14641 0%, #BD362F 100%); + } + +.dropdown-menu:after { + -moz-transform: rotate(45deg); + -webkit-transform: rotate(45deg); + -ms-transform: rotate(45deg); +} + +.nav-head { + background: #fff; + background: -moz-linear-gradient(top, #fff 0%, #f0f0f0 100%); + background: -webkit-linear-gradient(top, #fff 0%, #f0f0f0 100%); + background: -o-linear-gradient(top, #fff 0%, #f0f0f0 100%); + background: -ms-linear-gradient(top, #fff 0%, #f0f0f0 100%); +} + +.header > .item.search input { + -moz-transition: width 200ms linear; + -webkit-transition: width 200ms linear; + -o-transition: width 200ms linear; + -ms-transition: width 200ms linear; +} + +@media(max-width: 840px) { + .aside { + -moz-transition: width 200ms linear; + -webkit-transition: width 200ms linear; + -o-transition: width 200ms linear; + -ms-transition: width 200ms linear; + } +} + +@media print { + .header, + .aside, + .nav_menu, + .day, + .flux_header, + .flux_content .bottom, + .pagination { + display: none; + } + + html, body { + background: #fff; + color: #000; + font-family: Serif; + font-size: 12pt; + } + + #global, + .flux_content { + display: block !important; + } + + .flux_content .content { + width: 100% !important; + text-align: justify; + } + + .flux_content .content a { + color: #000; + } + .flux_content .content a:after { + content: " (" attr(href) ") "; + text-decoration: underline; + } +} diff --git a/p/themes/Origine/global.css b/p/themes/Origine/global.css new file mode 100644 index 000000000..49f3aa4cd --- /dev/null +++ b/p/themes/Origine/global.css @@ -0,0 +1,503 @@ +@charset "UTF-8"; + +/* FONTS */ +@font-face { + font-family: "OpenSans"; + src: url("../fonts/openSans.woff") format("woff"); +} + + +* { + margin: 0; + padding: 0; +} +html, body { + height: 100%; + font-size: 95%; + font-family: "OpenSans", "Cantarell", "Helvetica", "Arial", sans-serif; +} + +/* LIENS */ +a { + color: #0062BE; + text-decoration: none; +} + a:hover { + text-decoration: underline; + } + +/* LISTES */ +ul, ol, dl { + margin: 10px 0 10px 30px; + line-height: 190%; +} + dd { + margin: 0 0 10px 30px; + } + +/* TITRES */ +h1, h2, h3 { + min-height: 40px; + margin: 15px 0 5px; + line-height: 40px; +} + +/* IMG */ +figure { + margin: 5px 0 10px; + text-align: center; +} + figcaption { + display: inline-block; + padding: 3px 20px; + color: #999; + font-style: italic; + border-bottom: 1px solid #ccc; + } +img { + height: auto; + max-width: 100%; + vertical-align: middle; +} + a img { + border: none; + } + +/* VIDEOS */ +iframe, embed, object, video { + max-width: 100%; +} + +/* FORMULAIRES */ +legend { + display: block; + width: 100%; + margin: 20px 0 5px; + padding: 5px 0; + border-bottom: 1px solid #ddd; + font-size: 150%; + clear: both; +} +label { + display: block; + min-height: 25px; + padding: 5px 0; + font-size: 14px; + line-height: 25px; + cursor: pointer; +} +input, select, textarea { + display: inline-block; + max-width: 100%; + min-height: 25px; + padding: 5px; + background: #fdfdfd; + border: 1px solid #bbb; + border-radius: 3px; + color: #666; + line-height: 25px; + vertical-align: middle; + box-shadow: 0 2px 2px #eee inset; +} + option { + padding:0 .5em 0 .5em; + } + input[type="radio"], + input[type="checkbox"] { + width: 15px !important; + min-height: 15px !important; + } + input:focus, select:focus, textarea:focus { + color: #0062BE; + border-color: #33BBFF; + box-shadow: 0 2px 2px #DDDDFF inset; + } + input:invalid, select:invalid { + border-color: red; + box-shadow: 0 0 2px 1px red; + } + +.form-group { + margin: 0; +} + .form-group:after { + content: ""; + display: block; + clear: both; + } + .form-group.form-actions { + min-width: 250px; + padding: 5px 0; + background: #f4f4f4; + border-top: 1px solid #ddd; + } + .form-group.form-actions .btn { + margin: 0 10px; + } + .form-group .group-name { + display: block; + float: left; + width: 200px; + padding: 10px 0; + text-align: right; + } + .form-group .group-controls { + min-width: 250px; + min-height: 25px; + margin: 0 0 0 220px; + padding: 5px 0; + } + .form-group .group-controls .control { + display: block; + min-height: 30px; + padding: 5px 0; + line-height: 25px; + font-size: 14px; + } + +.stick { + display: inline-block; + white-space: nowrap; + font-size: 0px; + vertical-align: middle; +} + .stick input, + .stick .btn { + border-radius: 0; + font-size: 14px; + } + .stick .btn:first-child, + .stick input:first-child { + border-radius: 3px 0 0 3px; + } + .stick .btn-important:first-child { + border-right: 1px solid #06f; + } + .stick .btn:last-child, + .stick input:last-child { + border-radius: 0 3px 3px 0; + } + .stick .btn + .btn, + .stick .btn + input, + .stick input + .btn, + .stick input + input { + border-left: none; + } + .stick input + .btn { + border-top: 1px solid #bbb; + } + .stick .btn + .dropdown > .btn { + border-left: none; + border-radius: 0 3px 3px 0; + } + .stick .btn + .dropdown a { + font-size: 12px; + } + +.btn { + display: inline-block; + min-height: 37px; + min-width: 15px; + padding: 5px 10px; + background: linear-gradient(to bottom, #fff 0%, #eee 100%); + border-radius: 3px; + border: 1px solid #ddd; + border-bottom: 1px solid #aaa; + border-right: 1px solid #aaa; + color: #666; + text-shadow: 0px -1px 0 #ddd; + line-height: 20px; + vertical-align: middle; + cursor: pointer; + overflow: hidden; +} + a.btn { + min-height: 25px; + line-height: 25px; + } + .btn:hover { + background: linear-gradient(to bottom, #f8f8f8, #f0f0f0); + text-decoration: none; + } + .btn.active, + .btn:active, + .dropdown-target:target ~ .btn.dropdown-toggle { + box-shadow: 0px 2px 4px #e0e0e0 inset, 0px 1px 2px #fafafa; + background: #eee; + } + + .btn-important { + background: linear-gradient(to bottom, #0084CC, #0045CC); + color: #fff; + border: 1px solid #0062B7; + text-shadow: 0px -1px 0 #aaa; + } + .btn-important:hover { + background: linear-gradient(to bottom, #0066CC, #0045CC); + } + .btn-important:active { + background: #0044CB; + box-shadow: none; + } + + .btn-attention { + background: linear-gradient(to bottom, #E95B57, #BD362F); + color: #fff; + border: 1px solid #C44742; + text-shadow: 0px -1px 0px #666; + } + .btn-attention:hover { + background: linear-gradient(to bottom, #D14641, #BD362F); + } + .btn-attention:active { + background: #BD362F; + box-shadow: none; + } + +/* NAVIGATION */ +.nav-list .nav-header, +.nav-list .item { + display: block; + height: 35px; + line-height: 35px; +} + .nav-list .item:hover { + background: #fafafa; + } + .nav-list .item:hover a { + color: #003388; + } + .nav-list .item.active { + background: #0062BE; + color: #fff; + } + .nav-list .item.active a { + color: #fff; + } + .nav-list .disable { + color: #aaa; + background: #fafafa; + text-align: center; + } + .nav-list .item > * { + display: block; + padding: 0 10px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + .nav-list a:hover { + text-decoration: none; + } + .nav-list .item.error a { + color: #BD362F; + } + .nav-list .item.active.error a { + color: #fff; + background: #BD362F; + } + .nav-list .item.empty a { + color: #f39c12; + } + .nav-list .item.active.empty a { + color: #fff; + background: #f39c12; + } + + .nav-list .nav-header { + padding: 0 10px; + color: #888; + background: #f4f4f4; + border-bottom: 1px solid #ddd; + font-weight: bold; + text-shadow: 0 0 1px #ddd; + } + .nav-list .separator { + display: block; + height: 0; + margin: 5px 0; + border-bottom: 1px solid #ddd; + } + + .nav-list .nav-form { + padding: 3px; + text-align: center; + } + +.nav-head { + display: block; + margin: 0; + background: linear-gradient(to bottom, #fff, #f0f0f0); + border-bottom: 1px solid #ddd; + text-align: right; +} + .nav-head .item { + display: inline-block; + padding: 5px 10px; + } + +/* HORIZONTAL-LIST */ +.horizontal-list { + display: table; + table-layout: fixed; + margin: 0; + padding: 0; + width: 100%; +} + .horizontal-list .item { + display: table-cell; + vertical-align: middle; + } + +/* DROPDOWN */ +.dropdown { + position: relative; + display: inline-block; +} + .dropdown-target { + display: none; + } + + .dropdown-menu { + display: none; + min-width: 200px; + margin: 5px 0 0; + padding: 5px 0; + position: absolute; + right: 0px; + background: #fff; + border: 1px solid #ddd; + border-radius: 5px; + text-align: left; + box-shadow: 3px 3px 3px #ddd; + } + .dropdown-menu:after { + content: ""; + position: absolute; + top: -6px; + right: 13px; + width: 10px; + height: 10px; + background: #fff; + border-top: 1px solid #ddd; + border-left: 1px solid #ddd; + z-index: -10; + transform: rotate(45deg); + } + .dropdown-header { + display: block; + padding: 0 5px; + color: #888; + font-weight: bold; + font-size: 14px; + line-height: 30px; + } + .dropdown-menu > .item { + display: block; + height: 30px; + font-size: 90%; + line-height: 30px; + } + .dropdown-menu > .item > a { + display: block; + padding: 0 25px; + line-height: 30px; + } + .dropdown-menu > .item:hover { + background: #0062BE; + color: #fff; + } + .dropdown-menu > .item[aria-checked="true"] > a:before { + content: '✓ '; + font-weight: bold; + margin: 0 0 0 -1.2em; + padding: 0 0.2em 0 0; + } + .dropdown-menu > .item:hover > a { + color: #fff; + text-decoration: none; + } + .dropdown-menu .input { + display: block; + height: 40px; + font-size: 90%; + line-height: 30px; + } + .dropdown-menu .input select, + .dropdown-menu .input input { + display: block; + height: 20px; + width: 95%; + margin: auto; + padding: 2px 5px; + border-radius: 3px; + } + .dropdown-menu .input select { + width: 70%; + height: auto; + } + .dropdown-menu .separator { + display: block; + height: 0; + margin: 5px 0; + border-bottom: 1px solid #ddd; + } + .dropdown-target:target ~ .dropdown-menu { + display: block; + z-index: 10; + } + .dropdown-close { + display: inline; + } + .dropdown-close a { + font-size: 0; + position: fixed; + top: 0; bottom: 0; + left: 0; right: 0; + display: block; + z-index: -10; + } + +/* ALERTS */ +.alert { + display: block; + width: 90%; + margin: 15px auto; + padding: 10px 15px; + background: #f4f4f4; + border: 1px solid #ccc; + border-right: 1px solid #aaa; + border-bottom: 1px solid #aaa; + border-radius: 5px; + color: #aaa; + text-shadow: 0 0 1px #eee; +} + .alert-head { + margin: 0; + font-weight: bold; + font-size: 110%; + } + .alert-warn { + background: #ffe; + border: 1px solid #eeb; + color: #c95; + } + .alert-success { + background: #dfd; + border: 1px solid #cec; + color: #484; + } + .alert-error { + background: #fdd; + border: 1px solid #ecc; + color: #844; + } + +/* ICÔNES */ +.icon { + display: inline-block; + width: 16px; + height: 16px; + vertical-align: middle; + line-height: 16px; +} diff --git a/p/themes/Origine/loader.gif b/p/themes/Origine/loader.gif new file mode 100644 index 000000000..5ff26f0e3 Binary files /dev/null and b/p/themes/Origine/loader.gif differ diff --git a/p/themes/Origine/metadata.json b/p/themes/Origine/metadata.json new file mode 100644 index 000000000..f93dcbc3f --- /dev/null +++ b/p/themes/Origine/metadata.json @@ -0,0 +1,7 @@ +{ + "name": "Origine", + "author": "Marien Fressinaud", + "description": "Le thème par défaut pour FreshRSS", + "version": 0.1, + "files": ["global.css", "freshrss.css"] +} diff --git a/p/themes/default/freshrss.css b/p/themes/default/freshrss.css deleted file mode 100644 index 593f21d30..000000000 --- a/p/themes/default/freshrss.css +++ /dev/null @@ -1,880 +0,0 @@ -@charset "UTF-8"; - -/* STRUCTURE */ -.header { - display: table; - width: 100%; - background: #f4f4f4; - table-layout: fixed; -} - .header > .item { - display: table-cell; - padding: 10px 0; - border-bottom: 1px solid #aaa; - vertical-align: middle; - text-align: center; - } - .header > .item.title { - width: 250px; - white-space: nowrap; - } - .logo { - display: inline-block; - font-size: 48px; - height: 32px; - width: 32px; - padding: 10px; - } - .header > .item.title h1 { - display: inline-block; - margin: 0; - text-shadow: 1px -1px 0 #ccc; - } - .header > .item.search input { - width: 230px; - transition: width 200ms linear; - } - .header .item.search input:focus { - width: 330px; - } - .header > .item.configure { - width: 100px; - } - -.item a:hover { - text-decoration: none; -} - -#global { - display: table; - width: 100%; - height: 100%; - background: #fafafa; - table-layout: fixed; -} - .aside { - display: table-cell; - height: 100%; - width: 250px; - vertical-align: top; - border-right: 1px solid #aaa; - background: #fff; - } - .aside .nav-form input { - width: 180px; - } - .aside.aside_flux { - padding: 10px 0 40px; - } - .aside.aside_feed .nav-form input { - width: 140px; - } - .aside.aside_feed .nav-form .dropdown .dropdown-menu { - right: -20px; - } - .aside.aside_feed .nav-form .dropdown .dropdown-menu:after { - right: 33px; - } - - .nav-login { - display: none; - } - - .nav_menu { - width: 100%; - background: #fafafa; - border-bottom: 1px solid #aaa; - text-align: center; - padding: 5px 0; - } - .nav_menu .search { - display:none; - } - -.favicon { - height: 16px; - width: 16px; -} - -.categories { - margin: 0; - padding: 0; - text-align: center; - list-style: none; -} - .category { - display: block; - width: 220px; - margin: 10px auto; - text-align: left; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - .category .btn:first-child { - width: 195px; - position: relative; - } - .category.stick .btn:first-child { - width:160px; - } - .category .btn:first-child:not([data-unread="0"]):after { - content: attr(data-unread); - position: absolute; - top: 3px; right: 3px; - padding: 1px 5px; - background: #ccc; - color: #fff; - font-size: 90%; - border: 1px solid #bbb; - border-radius: 5px; - box-shadow: 1px 3px 3px #aaa inset; - text-shadow: 0 0 1px #aaa; - } - .category + .feeds:not(.active) { - display:none; - } - .categories .feeds { - width: 100%; - margin: 0; - list-style: none; - } - .categories .feeds .item.active { - background: #0062BE; - } - .categories .feeds .item.active .feed { - color: #fff; - } - .categories .feeds .item.empty .feed { - color: #e67e22; - } - .categories .feeds .item.empty.active { - background: #e67e22; - } - .categories .feeds .item.empty.active .feed { - color: #fff; - } - .categories .feeds .item.error .feed { - color: #BD362F; - } - .categories .feeds .item .feed { - display: inline-block; - margin: 0; - width: 165px; - line-height: 35px; - font-size: 90%; - vertical-align: middle; - text-align: left; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - .feed:not([data-unread="0"]) { - font-weight:bold; - } - .feed:not([data-unread="0"]):before { - content: "(" attr(data-unread) ") "; - } - .categories .feeds .dropdown-menu { - left: 0; - } - .categories .feeds .dropdown-menu:after { - left: 2px; - } - .categories .feeds .item .dropdown-toggle > .icon { - visibility: hidden; - cursor: pointer; - } - .categories .feeds .item .dropdown-target:target ~ .dropdown-toggle > .icon, - .categories .feeds .item:hover .dropdown-toggle > .icon, - .categories .feeds .item.active .dropdown-toggle > .icon { - background-color: #fff; - border-radius: 3px; - visibility: visible; - } - -.post { - padding: 10px 50px; -} - .post form { - margin: 10px 0; - } - -.day { - min-height: 50px; - padding: 0 10px; - font-size: 130%; - font-weight: bold; - line-height: 50px; - background: #fff; - border-top: 1px solid #aaa; - border-bottom: 1px solid #aaa; -} - .day:first-child { - border-top: none; - } - .day .name { - position: absolute; - right: 0; - width: 50%; - height: 1.5em; - padding: 0 10px 0 0; - overflow: hidden; - color: #aab; - font-size: 1.8em; - opacity: .3; - text-shadow: 0px -1px 0px #333; - font-style: italic; - white-space: nowrap; - text-overflow: ellipsis; - text-align: right; - } - -.flux { - border-left: 3px solid #aaa; - background: #fafafa; -} - .flux:hover { - background: #fff; - } - .flux.not_read { - border-left: 3px solid #FF5300; - background: #FFF3ED; - } - .flux.favorite { - border-left: 3px solid #FFC300; - background: #FFF6DA; - } - .flux.current { - border-left: 3px solid #0062BE; - background: #fff; - } - - .flux_header { - background: inherit; - height: 25px; - font-size: 12px; - border-top: 1px solid #ddd; - cursor: pointer; - } - .flux .item { - line-height: 40px; - white-space: nowrap; - } - .flux_header > .item { - overflow: hidden; - text-overflow: ellipsis; - } - .flux .item.manage { - width: 40px; - text-align: center; - } - .flux .item.website { - width: 200px; - } - .website .favicon { - padding: 5px; - } - .flux .item.title { - background: inherit; - } - .flux:hover .item.title { - border-right: 2px solid rgba(127, 127, 127, 0.1); - padding-right: 1em; - position: absolute; - } - .flux .item.title a { - color: #000; - outline: none; - } - .flux.not_read .item.title, - .flux.current .item.title { - font-weight: bold; - } - .flux .item.date { - width: 200px; - padding:0 5px 0 0; - text-align: right; - font-size: 10px; - color: #666; - } - .link { - width: 40px; - text-align: center; - } - -#stream.reader .flux { - padding: 0 0 30px; - border: none; - background: #f0f0f0; - color: #333; -} - #stream.reader .flux .author { - margin: 0 0 10px; - font-size: 90%; - color: #666; - } - -#stream.global { - text-align: center; -} - #stream.global .box-category { - display: inline-block; - width: 280px; - margin: 20px 10px; - vertical-align: top; - background: #fff; - border: 1px solid #aaa; - border-radius: 5px; - text-align: left; - box-shadow: 0 0 5px #bbb; - } - #stream.global .category { - width: 100%; - margin: 0; - } - #stream.global .btn { - display: block; - width: auto; - height: 35px; - margin: 0; - padding: 0 10px; - background: #eee; - border: none; - border-bottom: 1px solid #aaa; - border-radius: 5px 5px 0 0; - line-height: 35px; - font-size: 120%; - } - #stream.global .btn:not([data-unread="0"]) { - font-weight:bold; - } - #stream.global .btn:first-child:not([data-unread="0"]):after { - top: 0; right: 5px; - border: 0; - background: none; - color: #666; - font-weight: bold; - box-shadow: none; - } - #stream.global .box-category .feeds { - display: block; - max-height: 250px; - margin: 0; - list-style: none; - overflow: auto; - } - #stream.global .box-category .feeds .item { - padding: 2px 10px; - font-size: 90%; - } - #stream.global .box-category .feed { - width: 220px; - } - -.content { - min-height: 150px; - max-width: 550px; - margin: 0 auto; - padding: 20px 10px; - line-height: 170%; - word-wrap: break-word; -} - .content h1, .content h2, .content h3 { - margin: 20px 0 5px; - } - .content > .title { - font-size: x-large; - margin: 0; - } - - .content p { - margin: 0 0 20px; - } - img.big { - display: block; - margin: 10px auto; - } - figure img.big { - margin: 0; - } - .content hr { - margin: 30px 0; - height: 1px; - background: #ddd; - border: 0; - } - .content pre { - margin: 10px auto; - padding: 10px; - overflow: auto; - background: #000; - color: #fff; - font-size: 110%; - } - .content q, .content blockquote { - display: block; - margin: 5px 0; - padding: 5px 20px; - font-style: italic; - border-left: 4px solid #ccc; - color: #666; - } - .content blockquote p { - margin: 0; - } - -#panel { - display: none; - position: fixed; - top: 10px; bottom: 10px; - left: 100px; right: 100px; - overflow: auto; - background: #fff; - border: 1px solid #95a5a6; - border-radius: 5px; -} - #panel .close { - position: fixed; - top: 10px; right: 0; - display: inline-block; - width: 26px; - height: 26px; - margin: 0 10px 0 0; - border: 1px solid #ccc; - border-radius: 20px; - text-align: center; - line-height: 26px; - background: #fff; - } - -#overlay { - display: none; - position: fixed; - top: 0; bottom: 0; - left: 0; right: 0; - background: rgba(0, 0, 0, 0.9); -} - -.flux_content .bottom { - font-size: 90%; - text-align: center; -} - -.hide_posts > :not(.active) > .flux_content { - display:none; -} - -/*** PAGINATION ***/ -.pagination { - display: table; - width: 100%; - margin: 0; - background: #fafafa; - text-align: center; - color: #333; - font-size: 80%; - line-height: 200%; - table-layout: fixed; -} - .pagination .item { - display: table-cell; - line-height: 40px; - } - .pagination .item.pager-current { - font-weight: bold; - font-size: 140%; - } - .pagination .pager-first, - .pagination .pager-previous, - .pagination .pager-next, - .pagination .pager-last { - width: 100px; - } - .pagination .item a { - display: block; - color: #333; - font-style: italic; - } - .pagination:first-child .item { - border-bottom: 1px solid #aaa; - } - .pagination:last-child .item { - border-top: 1px solid #aaa; - } - -#nav_entries { - display: table; - width: 250px; - height: 40px; - position: fixed; - bottom: 0; - left: 0; - margin: 0; - background: #fff; - border-top: 1px solid #ddd; - text-align: center; - line-height: 40px; - table-layout: fixed; -} - #nav_entries .item { - display: table-cell; - width: 30%; - } - #nav_entries a { - display: block; - } - #nav_entries .i_up { - margin: 5px 0 0; - vertical-align: top; - } - -.loading { - background: url("loader.gif") center center no-repeat; - font-size: 0; -} - -#bigMarkAsRead { - display: block; - font-style: normal; - padding: 32px 0 64px 0; - text-align: center; - text-decoration: none; - text-shadow: 0 -1px 0 #aaa; -} - #bigMarkAsRead:hover { - background: #333; - color: #fff; - } - .bigTick { - font-size: 72pt; - line-height: 1.6em; - } - -/*** NOTIFICATION ***/ -.notification { - position: absolute; - top: 10px; - left: 25%; right: 25%; - min-height: 30px; - padding: 10px; - line-height: 30px; - text-align: center; - border-radius: 5px; - box-shadow: 0 0 5px #666; - background: #ddd; - color: #666; - font-weight: bold; - z-index: 10; -} - .notification.good { - background: #f4f899; - } - .notification.bad { - background: #f4a899; - } - .notification a.close { - display: inline-block; - width: 16px; - height: 16px; - float: right; - margin: -20px -20px 0 0; - padding: 5px; - background: #fff; - border-radius: 50px; - border: 1px solid #aaa; - line-height: 16px; - } - -.toggle_aside, .btn.toggle_aside { - display: none; -} - -.actualizeProgress { - position: fixed; - top: 10px; - left: 25%; right: 25%; - padding: 5px; - background: #fff; - text-align: center; - border: 1px solid #ddd; - border-radius: 5px; -} - .actualizeProgress progress { - max-width: 100%; - vertical-align: middle; - } - .actualizeProgress .progress { - color: #999; - font-size: 90%; - vertical-align: middle; - } - -.logs { - border: 1px solid #aaa; -} - .log { - padding: 5px 2%; - overflow: auto; - background: #fafafa; - border-bottom: 1px solid #999; - color: #333; - font-size: 90%; - } - .log .date { - display: block; - } - .log.error { - background: #fdd; - color: #844; - } - .log.warning { - background: #ffe; - color: #c95; - } - .log.notice { - background: #f4f4f4; - color: #aaa; - } - .log.debug { - background: #111; - color: #eee; - } - -.form-group table { - border-collapse:collapse; - margin:10px 0 0 220px; - text-align:center; -} - -.form-group tr, .form-group th, .form-group td { - border:1px solid #DDD; - font-weight:normal; - padding:.5em; -} - -select.number option { - text-align:right; -} - -@media(max-width: 840px) { - .header, - .aside .btn-important, - .aside .feeds .dropdown, - .flux_header .item.website span, - .item.date { - display: none; - } - .flux_header .item.website { - width: 40px; - text-align: center; - } - .flux_header .item.website .favicon { - padding: 12px; - } - - .nav-login { - display: block; - } - - .content { - font-size: 120%; - padding: 0; - } - - .pagination { - margin: 0 0 40px; - } - .pagination .pager-previous, .pagination .pager-next { - width: 100px; - } - - .toggle_aside, .btn.toggle_aside { - display: inline-block; - } - .aside { - position: fixed; - top: 0; left: 0; - width: 0; - overflow: hidden; - border-right: none; - z-index: 10; - transition: width 200ms linear; - } - .aside.aside_flux { - padding: 10px 0 0; - } - .aside:target { - width: 80%; - border-right: 1px solid #aaa; - overflow: auto; - } - .aside .toggle_aside { - position: absolute; - right: 0; - display: inline-block; - width: 26px; - height: 26px; - margin: 0 10px 0 0; - border: 1px solid #ccc; - border-radius: 20px; - text-align: center; - line-height: 26px; - } - .aside .categories { - margin: 30px 0; - } - - #nav_entries { - width: 100%; - } - - .nav_menu .btn { - margin: 5px 10px; - } - .nav_menu .stick { - margin: 0 10px; - } - .nav_menu .stick .btn { - margin: 5px 0; - } - .nav_menu .search { - display: inline-block; - max-width: 97%; - } - .nav_menu .search input { - max-width: 97%; - width: 90px; - } - .nav_menu .search input:focus { - width: 400px; - } - - #panel { - left: 5px; right: 5px; - } - - .day .date { - display: none; - } - .day .name { - height: 2.6em; - font-size: 1em; - text-shadow: none; - } - - .notification, - .actualizeProgress { - left: 10px; - right: 10px; - } -} - -/*** FALLBACK ***/ -.btn { - background: #fff; - background: -moz-linear-gradient(top, #fff 0%, #eee 100%); - background: -webkit-linear-gradient(top, #fff 0%, #eee 100%); - background: -o-linear-gradient(top, #fff 0%, #eee 100%); - background: -ms-linear-gradient(top, #fff 0%, #eee 100%); -} - .btn:hover { - background: #f0f0f0; - background: -moz-linear-gradient(top, #f8f8f8 0%, #f0f0f0 100%); - background: -webkit-linear-gradient(top, #f8f8f8 0%, #f0f0f0 100%); - background: -o-linear-gradient(top, #f8f8f8 0%, #f0f0f0 100%); - background: -ms-linear-gradient(top, #f8f8f8 0%, #f0f0f0 100%); - } - .btn-important { - background: #0084CC; - background: -moz-linear-gradient(top, #0084CC 0%, #0045CC 100%); - background: -webkit-linear-gradient(top, #0084CC 0%, #0045CC 100%); - background: -o-linear-gradient(top, #0084CC 0%, #0045CC 100%); - background: -ms-linear-gradient(top, #0084CC 0%, #0045CC 100%); - } - .btn-important:hover { - background: -moz-linear-gradient(top, #0066CC 0%, #0045CC 100%); - background: -webkit-linear-gradient(top, #0066CC 0%, #0045CC 100%); - background: -o-linear-gradient(top, #0066CC 0%, #0045CC 100%); - background: -ms-linear-gradient(top, #0066CC 0%, #0045CC 100%); - } - .btn-attention { - background: #E95B57; - background: -moz-linear-gradient(top, #E95B57 0%, #BD362F 100%); - background: -webkit-linear-gradient(top, #E95B57 0%, #BD362F 100%); - background: -o-linear-gradient(top, #E95B57 0%, #BD362F 100%); - background: -ms-linear-gradient(top, #E95B57 0%, #BD362F 100%); - } - .btn-attention:hover { - background: -moz-linear-gradient(top, #D14641 0%, #BD362F 100%); - background: -webkit-linear-gradient(top, #D14641 0%, #BD362F 100%); - background: -o-linear-gradient(top, #D14641 0%, #BD362F 100%); - background: -ms-linear-gradient(top, #D14641 0%, #BD362F 100%); - } - -.dropdown-menu:after { - -moz-transform: rotate(45deg); - -webkit-transform: rotate(45deg); - -ms-transform: rotate(45deg); -} - -.nav-head { - background: #fff; - background: -moz-linear-gradient(top, #fff 0%, #f0f0f0 100%); - background: -webkit-linear-gradient(top, #fff 0%, #f0f0f0 100%); - background: -o-linear-gradient(top, #fff 0%, #f0f0f0 100%); - background: -ms-linear-gradient(top, #fff 0%, #f0f0f0 100%); -} - -.header > .item.search input { - -moz-transition: width 200ms linear; - -webkit-transition: width 200ms linear; - -o-transition: width 200ms linear; - -ms-transition: width 200ms linear; -} - -@media(max-width: 840px) { - .aside { - -moz-transition: width 200ms linear; - -webkit-transition: width 200ms linear; - -o-transition: width 200ms linear; - -ms-transition: width 200ms linear; - } -} - -@media print { - .header, - .aside, - .nav_menu, - .day, - .flux_header, - .flux_content .bottom, - .pagination { - display: none; - } - - html, body { - background: #fff; - color: #000; - font-family: Serif; - font-size: 12pt; - } - - #global, - .flux_content { - display: block !important; - } - - .flux_content .content { - width: 100% !important; - text-align: justify; - } - - .flux_content .content a { - color: #000; - } - .flux_content .content a:after { - content: " (" attr(href) ") "; - text-decoration: underline; - } -} diff --git a/p/themes/default/global.css b/p/themes/default/global.css deleted file mode 100644 index 49f3aa4cd..000000000 --- a/p/themes/default/global.css +++ /dev/null @@ -1,503 +0,0 @@ -@charset "UTF-8"; - -/* FONTS */ -@font-face { - font-family: "OpenSans"; - src: url("../fonts/openSans.woff") format("woff"); -} - - -* { - margin: 0; - padding: 0; -} -html, body { - height: 100%; - font-size: 95%; - font-family: "OpenSans", "Cantarell", "Helvetica", "Arial", sans-serif; -} - -/* LIENS */ -a { - color: #0062BE; - text-decoration: none; -} - a:hover { - text-decoration: underline; - } - -/* LISTES */ -ul, ol, dl { - margin: 10px 0 10px 30px; - line-height: 190%; -} - dd { - margin: 0 0 10px 30px; - } - -/* TITRES */ -h1, h2, h3 { - min-height: 40px; - margin: 15px 0 5px; - line-height: 40px; -} - -/* IMG */ -figure { - margin: 5px 0 10px; - text-align: center; -} - figcaption { - display: inline-block; - padding: 3px 20px; - color: #999; - font-style: italic; - border-bottom: 1px solid #ccc; - } -img { - height: auto; - max-width: 100%; - vertical-align: middle; -} - a img { - border: none; - } - -/* VIDEOS */ -iframe, embed, object, video { - max-width: 100%; -} - -/* FORMULAIRES */ -legend { - display: block; - width: 100%; - margin: 20px 0 5px; - padding: 5px 0; - border-bottom: 1px solid #ddd; - font-size: 150%; - clear: both; -} -label { - display: block; - min-height: 25px; - padding: 5px 0; - font-size: 14px; - line-height: 25px; - cursor: pointer; -} -input, select, textarea { - display: inline-block; - max-width: 100%; - min-height: 25px; - padding: 5px; - background: #fdfdfd; - border: 1px solid #bbb; - border-radius: 3px; - color: #666; - line-height: 25px; - vertical-align: middle; - box-shadow: 0 2px 2px #eee inset; -} - option { - padding:0 .5em 0 .5em; - } - input[type="radio"], - input[type="checkbox"] { - width: 15px !important; - min-height: 15px !important; - } - input:focus, select:focus, textarea:focus { - color: #0062BE; - border-color: #33BBFF; - box-shadow: 0 2px 2px #DDDDFF inset; - } - input:invalid, select:invalid { - border-color: red; - box-shadow: 0 0 2px 1px red; - } - -.form-group { - margin: 0; -} - .form-group:after { - content: ""; - display: block; - clear: both; - } - .form-group.form-actions { - min-width: 250px; - padding: 5px 0; - background: #f4f4f4; - border-top: 1px solid #ddd; - } - .form-group.form-actions .btn { - margin: 0 10px; - } - .form-group .group-name { - display: block; - float: left; - width: 200px; - padding: 10px 0; - text-align: right; - } - .form-group .group-controls { - min-width: 250px; - min-height: 25px; - margin: 0 0 0 220px; - padding: 5px 0; - } - .form-group .group-controls .control { - display: block; - min-height: 30px; - padding: 5px 0; - line-height: 25px; - font-size: 14px; - } - -.stick { - display: inline-block; - white-space: nowrap; - font-size: 0px; - vertical-align: middle; -} - .stick input, - .stick .btn { - border-radius: 0; - font-size: 14px; - } - .stick .btn:first-child, - .stick input:first-child { - border-radius: 3px 0 0 3px; - } - .stick .btn-important:first-child { - border-right: 1px solid #06f; - } - .stick .btn:last-child, - .stick input:last-child { - border-radius: 0 3px 3px 0; - } - .stick .btn + .btn, - .stick .btn + input, - .stick input + .btn, - .stick input + input { - border-left: none; - } - .stick input + .btn { - border-top: 1px solid #bbb; - } - .stick .btn + .dropdown > .btn { - border-left: none; - border-radius: 0 3px 3px 0; - } - .stick .btn + .dropdown a { - font-size: 12px; - } - -.btn { - display: inline-block; - min-height: 37px; - min-width: 15px; - padding: 5px 10px; - background: linear-gradient(to bottom, #fff 0%, #eee 100%); - border-radius: 3px; - border: 1px solid #ddd; - border-bottom: 1px solid #aaa; - border-right: 1px solid #aaa; - color: #666; - text-shadow: 0px -1px 0 #ddd; - line-height: 20px; - vertical-align: middle; - cursor: pointer; - overflow: hidden; -} - a.btn { - min-height: 25px; - line-height: 25px; - } - .btn:hover { - background: linear-gradient(to bottom, #f8f8f8, #f0f0f0); - text-decoration: none; - } - .btn.active, - .btn:active, - .dropdown-target:target ~ .btn.dropdown-toggle { - box-shadow: 0px 2px 4px #e0e0e0 inset, 0px 1px 2px #fafafa; - background: #eee; - } - - .btn-important { - background: linear-gradient(to bottom, #0084CC, #0045CC); - color: #fff; - border: 1px solid #0062B7; - text-shadow: 0px -1px 0 #aaa; - } - .btn-important:hover { - background: linear-gradient(to bottom, #0066CC, #0045CC); - } - .btn-important:active { - background: #0044CB; - box-shadow: none; - } - - .btn-attention { - background: linear-gradient(to bottom, #E95B57, #BD362F); - color: #fff; - border: 1px solid #C44742; - text-shadow: 0px -1px 0px #666; - } - .btn-attention:hover { - background: linear-gradient(to bottom, #D14641, #BD362F); - } - .btn-attention:active { - background: #BD362F; - box-shadow: none; - } - -/* NAVIGATION */ -.nav-list .nav-header, -.nav-list .item { - display: block; - height: 35px; - line-height: 35px; -} - .nav-list .item:hover { - background: #fafafa; - } - .nav-list .item:hover a { - color: #003388; - } - .nav-list .item.active { - background: #0062BE; - color: #fff; - } - .nav-list .item.active a { - color: #fff; - } - .nav-list .disable { - color: #aaa; - background: #fafafa; - text-align: center; - } - .nav-list .item > * { - display: block; - padding: 0 10px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - .nav-list a:hover { - text-decoration: none; - } - .nav-list .item.error a { - color: #BD362F; - } - .nav-list .item.active.error a { - color: #fff; - background: #BD362F; - } - .nav-list .item.empty a { - color: #f39c12; - } - .nav-list .item.active.empty a { - color: #fff; - background: #f39c12; - } - - .nav-list .nav-header { - padding: 0 10px; - color: #888; - background: #f4f4f4; - border-bottom: 1px solid #ddd; - font-weight: bold; - text-shadow: 0 0 1px #ddd; - } - .nav-list .separator { - display: block; - height: 0; - margin: 5px 0; - border-bottom: 1px solid #ddd; - } - - .nav-list .nav-form { - padding: 3px; - text-align: center; - } - -.nav-head { - display: block; - margin: 0; - background: linear-gradient(to bottom, #fff, #f0f0f0); - border-bottom: 1px solid #ddd; - text-align: right; -} - .nav-head .item { - display: inline-block; - padding: 5px 10px; - } - -/* HORIZONTAL-LIST */ -.horizontal-list { - display: table; - table-layout: fixed; - margin: 0; - padding: 0; - width: 100%; -} - .horizontal-list .item { - display: table-cell; - vertical-align: middle; - } - -/* DROPDOWN */ -.dropdown { - position: relative; - display: inline-block; -} - .dropdown-target { - display: none; - } - - .dropdown-menu { - display: none; - min-width: 200px; - margin: 5px 0 0; - padding: 5px 0; - position: absolute; - right: 0px; - background: #fff; - border: 1px solid #ddd; - border-radius: 5px; - text-align: left; - box-shadow: 3px 3px 3px #ddd; - } - .dropdown-menu:after { - content: ""; - position: absolute; - top: -6px; - right: 13px; - width: 10px; - height: 10px; - background: #fff; - border-top: 1px solid #ddd; - border-left: 1px solid #ddd; - z-index: -10; - transform: rotate(45deg); - } - .dropdown-header { - display: block; - padding: 0 5px; - color: #888; - font-weight: bold; - font-size: 14px; - line-height: 30px; - } - .dropdown-menu > .item { - display: block; - height: 30px; - font-size: 90%; - line-height: 30px; - } - .dropdown-menu > .item > a { - display: block; - padding: 0 25px; - line-height: 30px; - } - .dropdown-menu > .item:hover { - background: #0062BE; - color: #fff; - } - .dropdown-menu > .item[aria-checked="true"] > a:before { - content: '✓ '; - font-weight: bold; - margin: 0 0 0 -1.2em; - padding: 0 0.2em 0 0; - } - .dropdown-menu > .item:hover > a { - color: #fff; - text-decoration: none; - } - .dropdown-menu .input { - display: block; - height: 40px; - font-size: 90%; - line-height: 30px; - } - .dropdown-menu .input select, - .dropdown-menu .input input { - display: block; - height: 20px; - width: 95%; - margin: auto; - padding: 2px 5px; - border-radius: 3px; - } - .dropdown-menu .input select { - width: 70%; - height: auto; - } - .dropdown-menu .separator { - display: block; - height: 0; - margin: 5px 0; - border-bottom: 1px solid #ddd; - } - .dropdown-target:target ~ .dropdown-menu { - display: block; - z-index: 10; - } - .dropdown-close { - display: inline; - } - .dropdown-close a { - font-size: 0; - position: fixed; - top: 0; bottom: 0; - left: 0; right: 0; - display: block; - z-index: -10; - } - -/* ALERTS */ -.alert { - display: block; - width: 90%; - margin: 15px auto; - padding: 10px 15px; - background: #f4f4f4; - border: 1px solid #ccc; - border-right: 1px solid #aaa; - border-bottom: 1px solid #aaa; - border-radius: 5px; - color: #aaa; - text-shadow: 0 0 1px #eee; -} - .alert-head { - margin: 0; - font-weight: bold; - font-size: 110%; - } - .alert-warn { - background: #ffe; - border: 1px solid #eeb; - color: #c95; - } - .alert-success { - background: #dfd; - border: 1px solid #cec; - color: #484; - } - .alert-error { - background: #fdd; - border: 1px solid #ecc; - color: #844; - } - -/* ICÔNES */ -.icon { - display: inline-block; - width: 16px; - height: 16px; - vertical-align: middle; - line-height: 16px; -} diff --git a/p/themes/default/loader.gif b/p/themes/default/loader.gif deleted file mode 100644 index 5ff26f0e3..000000000 Binary files a/p/themes/default/loader.gif and /dev/null differ diff --git a/p/themes/default/metadata.json b/p/themes/default/metadata.json deleted file mode 100644 index d316ec517..000000000 --- a/p/themes/default/metadata.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "Default", - "author": "Marien Fressinaud", - "description": "Le thème par défaut pour FreshRSS", - "version": 0.1, - "files": ["global.css", "freshrss.css"] -} diff --git a/p/themes/default_dark/freshrss.css b/p/themes/default_dark/freshrss.css deleted file mode 100644 index e9eb2c705..000000000 --- a/p/themes/default_dark/freshrss.css +++ /dev/null @@ -1,862 +0,0 @@ -@charset "UTF-8"; - -/* STRUCTURE */ -.header { - display: table; - width: 100%; - background: #1c1c1c; - table-layout: fixed; -} - .header > .item { - display: table-cell; - padding: 10px 0; - border-bottom: 1px solid #2f2f2f; - vertical-align: middle; - text-align: center; - } - .header > .item.title { - width: 250px; - white-space: nowrap; - } - .logo { - display: inline-block; - font-size: 48px; - height: 32px; - width: 32px; - padding: 10px; - } - .header > .item.title h1 { - display: inline-block; - margin: 0; - } - .header > .item.search input { - width: 230px; - transition: width 200ms linear; - } - .header .item.search input:focus { - width: 330px; - } - .header > .item.configure { - width: 100px; - } - -.item a:hover { - text-decoration: none; -} - -#global { - display: table; - width: 100%; - height: 100%; - background: #1c1c1c; - table-layout: fixed; -} - .aside { - display: table-cell; - height: 100%; - width: 250px; - vertical-align: top; - border-right: 1px solid #2f2f2f; - background: #1c1c1c; - } - .aside .nav-form input { - width: 180px; - } - .aside.aside_flux { - padding: 10px 0 40px; - } - .aside.aside_feed .nav-form input { - width: 140px; - } - .aside.aside_feed .nav-form .dropdown .dropdown-menu { - right: -20px; - } - .aside.aside_feed .nav-form .dropdown .dropdown-menu:after { - right: 33px; - } - - .nav-login { - display: none; - } - - .nav_menu { - width: 100%; - background: #1c1c1c; - border-bottom: 1px solid #2f2f2f; - text-align: center; - padding: 5px 0; - } - .nav_menu .search { - display:none; - } - -.favicon { - height: 16px; - width: 16px; -} - -.categories { - margin: 0; - padding: 0; - text-align: center; - list-style: none; -} - .category { - display: block; - width: 220px; - margin: 10px auto; - text-align: left; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - .category .btn:first-child { - width: 195px; - position: relative; - } - .category.stick .btn:first-child { - width:160px; - } - .category .btn:first-child:not([data-unread="0"]):after { - content: attr(data-unread); - position: absolute; - top: 3px; right: 3px; - padding: 1px 5px; - background: #1a1a1a; - color: #888; - font-size: 90%; - border: 1px solid #000; - border-radius: 5px; - } - .category + .feeds:not(.active) { - display:none; - } - .categories .feeds { - width: 100%; - margin: 0; - list-style: none; - } - .categories .feeds .item.active { - background: #26303F; - } - .categories .feeds .item.active .feed { - color: #888; - } - .categories .feeds .item.empty .feed { - color: #e67e22; - } - .categories .feeds .item.empty.active { - background: #e67e22; - } - .categories .feeds .item.empty.active .feed { - color: #fff; - } - .categories .feeds .item.error .feed { - color: #BD362F; - } - .categories .feeds .item .feed { - display: inline-block; - margin: 0; - width: 165px; - line-height: 35px; - font-size: 90%; - vertical-align: middle; - text-align: left; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - .feed:not([data-unread="0"]):before { - content: "(" attr(data-unread) ") "; - } - .categories .feeds .dropdown-menu { - left: 0; - } - .categories .feeds .dropdown-menu:after { - left: 2px; - } - .categories .feeds .item .dropdown-toggle > .icon { - visibility: hidden; - cursor: pointer; - } - .categories .feeds .item .dropdown-target:target ~ .dropdown-toggle > .icon, - .categories .feeds .item:hover .dropdown-toggle > .icon, - .categories .feeds .item.active .dropdown-toggle > .icon { - background-color: #1c1c1c; - border-radius: 3px; - visibility: visible; - } - -.post { - padding: 10px 50px; -} - .post form { - margin: 10px 0; - } - -.day { - min-height: 50px; - padding: 0 10px; - font-size: 130%; - font-weight: bold; - line-height: 50px; - background: #1c1c1c; - border-top: 1px solid #888; - border-bottom: 1px solid #888; -} - .day:first-child { - border-top: none; - } - .day .name { - position: absolute; - right: 0; - width: 50%; - height: 1.5em; - padding: 0 10px 0 0; - overflow: hidden; - color: #aab; - font-size: 1.8em; - opacity: .3; - text-shadow: 0px -1px 0px #333; - font-style: italic; - white-space: nowrap; - text-overflow: ellipsis; - text-align: right; - } - -.flux { - border-left: 3px solid #aaa; - background: #1c1c1c; -} - .flux.not_read { - border-left: 3px solid #FF5300; - background: #1c1c1c; - } - .flux.favorite { - border-left: 3px solid #FFC300; - background: #1c1c1c; - } - .flux.current { - border-left: 3px solid #0062BE; - background: #1a1a1a; - } - - .flux_header { - background: inherit; - height: 25px; - font-size: 12px; - border-top: 1px solid #2f2f2f; - cursor: pointer; - } - .flux .item { - line-height: 40px; - white-space: nowrap; - } - .flux_header > .item { - overflow: hidden; - text-overflow: ellipsis; - } - .flux .item.manage { - width: 40px; - text-align: center; - } - .flux .item.website { - width: 200px; - } - .website .favicon { - padding: 5px; - } - .flux .item.title { - background: inherit; - } - .flux:hover .item.title { - border-right: 2px solid rgba(127, 127, 127, 0.1); - padding-right: 1em; - position: absolute; - } - .flux .item.title a { - color: #888; - outline: none; - } - .flux.not_read .item.title, - .flux.current .item.title { - font-weight: bold; - } - .flux .item.date { - width: 200px; - padding:0 5px 0 0; - text-align: right; - font-size: 10px; - color: #666; - } - .link { - width: 40px; - text-align: center; - } - -#stream.reader .flux { - padding: 0 0 30px; - border: none; - background: #1c1c1c; - color: #888; -} - #stream.reader .flux .author { - margin: 0 0 10px; - font-size: 90%; - color: #666; - } - -#stream.global { - text-align: center; -} - #stream.global .box-category { - display: inline-block; - width: 280px; - margin: 20px 10px; - vertical-align: top; - background: #1a1a1a; - border: 1px solid #000; - border-radius: 5px; - text-align: left; - box-shadow: 0 0 5px #2f2f2f; - } - #stream.global .category { - width: 100%; - margin: 0; - } - #stream.global .btn { - display: block; - width: auto; - height: 35px; - margin: 0; - padding: 0 10px; - background: #26303F; - border: none; - border-bottom: 1px solid #2f2f2f; - border-radius: 5px 5px 0 0; - line-height: 35px; - font-size: 120%; - } - #stream.global .btn:not([data-unread="0"]) { - font-weight:bold; - } - #stream.global .btn:first-child:not([data-unread="0"]):after { - top: 0; right: 5px; - border: 0; - background: none; - color: #666; - font-weight: bold; - box-shadow: none; - } - #stream.global .box-category .feeds { - display: block; - max-height: 250px; - margin: 0; - list-style: none; - overflow: auto; - } - #stream.global .box-category .feeds .item { - padding: 2px 10px; - font-size: 90%; - } - #stream.global .box-category .feed { - width: 220px; - } - -.content { - min-height: 150px; - max-width: 550px; - margin: 0 auto; - padding: 20px 10px; - line-height: 170%; - word-wrap: break-word; -} - .content h1, .content h2, .content h3 { - margin: 20px 0 5px; - } - .content > .title { - font-size: x-large; - margin: 0; - } - - .content p { - margin: 0 0 20px; - } - img.big { - display: block; - margin: 10px auto; - } - figure img.big { - margin: 0; - } - .content hr { - margin: 30px 0; - height: 1px; - background: #ddd; - border: 0; - } - .content pre { - margin: 10px auto; - padding: 10px; - overflow: auto; - background: #000; - color: #fff; - font-size: 110%; - } - .content q, .content blockquote { - display: block; - margin: 5px 0; - padding: 5px 20px; - font-style: italic; - border-left: 4px solid #ccc; - color: #666; - } - .content blockquote p { - margin: 0; - } - -#panel { - display: none; - position: fixed; - top: 10px; bottom: 10px; - left: 100px; right: 100px; - overflow: auto; - background: #1c1c1c; - border: 1px solid #95a5a6; - border-radius: 5px; -} - #panel .close { - position: fixed; - top: 10px; right: 0; - display: inline-block; - width: 26px; - height: 26px; - margin: 0 10px 0 0; - border: 1px solid #ccc; - border-radius: 20px; - text-align: center; - line-height: 26px; - background: #fff; - } - -#overlay { - display: none; - position: fixed; - top: 0; bottom: 0; - left: 0; right: 0; - background: rgba(0, 0, 0, 0.9); -} - -.flux_content .bottom { - font-size: 90%; - text-align: center; -} - -.hide_posts > :not(.active) > .flux_content { - display:none; -} - -/*** PAGINATION ***/ -.pagination { - display: table; - width: 100%; - margin: 0; - background: #1a1a1a; - text-align: center; - color: #888; - font-size: 80%; - line-height: 200%; - table-layout: fixed; -} - .pagination .item { - display: table-cell; - line-height: 40px; - } - .pagination .item.pager-current { - font-weight: bold; - font-size: 140%; - } - .pagination .pager-first, - .pagination .pager-previous, - .pagination .pager-next, - .pagination .pager-last { - width: 100px; - } - .pagination .item a { - display: block; - color: #333; - font-style: italic; - } - .pagination:first-child .item { - border-bottom: 1px solid #aaa; - } - .pagination:last-child .item { - border-top: 1px solid #aaa; - } - -#nav_entries { - display: table; - width: 250px; - height: 40px; - position: fixed; - bottom: 0; - left: 0; - margin: 0; - background: #1c1c1c; - border-top: 1px solid #2f2f2f; - text-align: center; - line-height: 40px; - table-layout: fixed; -} - #nav_entries .item { - display: table-cell; - width: 30%; - } - #nav_entries a { - display: block; - } - #nav_entries .i_up { - margin: 5px 0 0; - vertical-align: top; - } - -.loading { - background: url("loader.gif") center center no-repeat; - font-size: 0; -} - -#bigMarkAsRead { - display: block; - font-style: normal; - padding: 32px 0 64px 0; - text-align: center; - text-decoration: none; -} - #bigMarkAsRead:hover { - background: #1c1c1c; - color: #888; - } - .bigTick { - font-size: 72pt; - line-height: 1.6em; - } - -/*** NOTIFICATION ***/ -.notification { - position: absolute; - top: 10px; - left: 25%; right: 25%; - min-height: 30px; - padding: 10px; - line-height: 30px; - text-align: center; - border-radius: 5px; - box-shadow: 0 0 5px #666; - background: #1a1a1a; - color: #888; - font-weight: bold; - z-index: 10; -} - .notification.good { - border:1px solid #f4f899; - } - .notification.bad { - border:1px solid #f4a899; - } - .notification a.close { - display: inline-block; - width: 16px; - height: 16px; - float: right; - margin: -20px -20px 0 0; - padding: 5px; - background: #1a1a1a; - border-radius: 50px; - line-height: 16px; - } - .notification.good a.close{ - border:1px solid #f4f899; - } - .notification.bad a.close{ - border:1px solid #f4a899; - } - -.toggle_aside, .btn.toggle_aside { - display: none; -} - -.actualizeProgress { - position: fixed; - top: 10px; - left: 25%; right: 25%; - padding: 5px; - background: #1a1a1a; - text-align: center; - border: 1px solid #2f2f2f; - border-radius: 5px; -} - .actualizeProgress progress { - max-width: 100%; - vertical-align: middle; - } - .actualizeProgress .progress { - color: #888; - font-size: 90%; - vertical-align: middle; - } - -.logs { - border: 1px solid #aaa; -} - .log { - padding: 5px 2%; - overflow: auto; - background: #fafafa; - border-bottom: 1px solid #999; - color: #333; - font-size: 90%; - } - .log .date { - display: block; - } - .log.error { - background: #fdd; - color: #844; - } - .log.warning { - background: #ffe; - color: #c95; - } - .log.notice { - background: #f4f4f4; - color: #aaa; - } - .log.debug { - background: #111; - color: #eee; - } - -.form-group table { - border-collapse:collapse; - margin:10px 0 0 220px; - text-align:center; -} - -.form-group tr, .form-group th, .form-group td { - border:1px solid #2f2f2f; - font-weight:normal; - padding:.5em; -} - -select.number option { - text-align:right; -} - -@media(max-width: 840px) { - .header, - .aside .btn-important, - .aside .feeds .dropdown, - .flux_header .item.website span, - .item.date { - display: none; - } - .flux_header .item.website { - width: 40px; - text-align: center; - } - .flux_header .item.website .favicon { - padding: 12px; - } - - .nav-login { - display: block; - } - - .content { - font-size: 120%; - padding: 0; - } - - .pagination { - margin: 0 0 40px; - } - .pagination .pager-previous, .pagination .pager-next { - width: 100px; - } - - .toggle_aside, .btn.toggle_aside { - display: inline-block; - } - .aside { - position: fixed; - top: 0; left: 0; - width: 0; - overflow: hidden; - border-right: none; - z-index: 10; - transition: width 200ms linear; - } - .aside.aside_flux { - padding: 10px 0 0; - } - .aside:target { - width: 80%; - border-right: 1px solid #aaa; - overflow: auto; - } - .aside .toggle_aside { - position: absolute; - right: 0; - display: inline-block; - width: 26px; - height: 26px; - margin: 0 10px 0 0; - border: 1px solid #ccc; - border-radius: 20px; - text-align: center; - line-height: 26px; - } - .aside .categories { - margin: 30px 0; - } - - #nav_entries { - width: 100%; - } - - .nav_menu .btn { - margin: 5px 10px; - } - .nav_menu .stick { - margin: 0 10px; - } - .nav_menu .stick .btn { - margin: 5px 0; - } - .nav_menu .search { - display: inline-block; - max-width: 97%; - } - .nav_menu .search input { - max-width: 97%; - width: 90px; - } - .nav_menu .search input:focus { - width: 400px; - } - - #panel { - left: 5px; right: 5px; - } - - .day .date { - display: none; - } - .day .name { - height: 2.6em; - font-size: 1em; - text-shadow: none; - } - - .notification, - .actualizeProgress { - left: 10px; - right: 10px; - } -} - -/*** FALLBACK ***/ -.btn { - background: #1c1c1c; -} - .btn:hover { - background: -moz-linear-gradient(top, #4A5D7A 0%, #26303F 100%); - background: -webkit-linear-gradient(top, #4A5D7A 0%, #26303F 100%); - background: -o-linear-gradient(top, #4A5D7A 0%, #26303F 100%); - background: -ms-linear-gradient(top, #4A5D7A 0%, #26303F 100%); - } - .btn-important { - background: #26303F; - } - .btn-important:hover { - background: -moz-linear-gradient(top, #4A5D7A 0%, #26303F 100%); - background: -webkit-linear-gradient(top, #4A5D7A 0%, #26303F 100%); - background: -o-linear-gradient(top, #4A5D7A 0%, #26303F 100%); - background: -ms-linear-gradient(top, #4A5D7A 0%, #26303F 100%); - } - .btn-attention { - background: #880011; - } - .btn-attention:hover { - background: -moz-linear-gradient(top, #cc0044 0%, #880011 100%); - background: -webkit-linear-gradient(top, #cc0044 0%, #880011 100%); - background: -o-linear-gradient(top, #cc0044 0%, #880011 100%); - background: -ms-linear-gradient(top, #cc0044 0%, #880011 100%); - } - -.dropdown-menu:after { - -moz-transform: rotate(45deg); - -webkit-transform: rotate(45deg); - -ms-transform: rotate(45deg); -} - -.nav-head { - background: #fff; - background: -moz-linear-gradient(top, #fff 0%, #f0f0f0 100%); - background: -webkit-linear-gradient(top, #fff 0%, #f0f0f0 100%); - background: -o-linear-gradient(top, #fff 0%, #f0f0f0 100%); - background: -ms-linear-gradient(top, #fff 0%, #f0f0f0 100%); -} - -.header > .item.search input { - -moz-transition: width 200ms linear; - -webkit-transition: width 200ms linear; - -o-transition: width 200ms linear; - -ms-transition: width 200ms linear; -} - -@media(max-width: 840px) { - .aside { - -moz-transition: width 200ms linear; - -webkit-transition: width 200ms linear; - -o-transition: width 200ms linear; - -ms-transition: width 200ms linear; - } -} - -@media print { - .header, - .aside, - .nav_menu, - .day, - .flux_header, - .flux_content .bottom, - .pagination { - display: none; - } - - html, body { - background: #fff; - color: #000; - font-family: Serif; - font-size: 12pt; - } - - #global, - .flux_content { - display: block !important; - } - - .flux_content .content { - width: 100% !important; - text-align: justify; - } - - .flux_content .content a { - color: #000; - } - .flux_content .content a:after { - content: " (" attr(href) ") "; - text-decoration: underline; - } -} diff --git a/p/themes/default_dark/global.css b/p/themes/default_dark/global.css deleted file mode 100644 index 04eb723f1..000000000 --- a/p/themes/default_dark/global.css +++ /dev/null @@ -1,487 +0,0 @@ -@charset "UTF-8"; - -/* FONTS */ -@font-face { - font-family: "OpenSans"; - src: url("../fonts/openSans.woff") format("woff"); -} - - -* { - margin: 0; - padding: 0; -} -html, body { - height: 100%; - font-size: 95%; - font-family: "OpenSans", "Cantarell", "Helvetica", "Arial", sans-serif; - color: #888; -} - -/* LIENS */ -a { - color: #6986B2; - text-decoration: none; -} - a:hover { - text-decoration: underline; - } - -/* LISTES */ -ul, ol, dl { - margin: 10px 0 10px 30px; - line-height: 190%; -} - dd { - margin: 0 0 10px 30px; - } - -/* TITRES */ -h1, h2, h3 { - min-height: 40px; - margin: 15px 0 5px; - line-height: 40px; -} - -/* IMG */ -figure { - margin: 5px 0 10px; - text-align: center; -} - figcaption { - display: inline-block; - padding: 3px 20px; - color: #999; - font-style: italic; - border-bottom: 1px solid #ccc; - } -img { - height: auto; - max-width: 100%; - vertical-align: middle; -} - a img { - border: none; - } - -/* VIDEOS */ -iframe, embed, object, video { - max-width: 100%; -} - -/* FORMULAIRES */ -legend { - display: block; - width: 100%; - margin: 20px 0 5px; - padding: 5px 0; - border-bottom: 1px solid #2f2f2f; - font-size: 150%; - clear: both; -} -label { - display: block; - min-height: 25px; - padding: 5px 0; - font-size: 14px; - line-height: 25px; - cursor: pointer; -} -input, select, textarea { - display: inline-block; - max-width: 100%; - min-height: 25px; - padding: 5px; - background: #333; - border: 1px solid #000; - border-radius: 3px; - color: #999; - line-height: 25px; - vertical-align: middle; - box-shadow: 0 2px 2px #1d1d1d inset; -} - option { - padding:0 .5em 0 .5em; - } - input[type="radio"], - input[type="checkbox"] { - width: 15px !important; - min-height: 15px !important; - } - input:focus, select:focus, textarea:focus { - color: #6986b2; - border-color: #2f2f2f; - } - input:invalid, select:invalid { - border-color: red; - box-shadow: 0 0 2px 1px red; - } - -.form-group { - margin: 0; -} - .form-group:after { - content: ""; - display: block; - clear: both; - } - .form-group.form-actions { - min-width: 250px; - padding: 5px 0; - background: #1a1a1a; - border-top: 1px solid #2f2f2f; - } - .form-group.form-actions .btn { - margin: 0 10px; - } - .form-group .group-name { - display: block; - float: left; - width: 200px; - padding: 10px 0; - text-align: right; - } - .form-group .group-controls { - min-width: 250px; - min-height: 25px; - margin: 0 0 0 220px; - padding: 5px 0; - } - .form-group .group-controls .control { - display: block; - min-height: 30px; - padding: 5px 0; - line-height: 25px; - font-size: 14px; - } - -.stick { - display: inline-block; - white-space: nowrap; - font-size: 0px; - vertical-align: middle; -} - .stick input, - .stick .btn { - border-radius: 0; - font-size: 14px; - } - .stick .btn:first-child, - .stick input:first-child { - border-radius: 3px 0 0 3px; - } - .stick .btn-important:first-child { - border-right: 1px solid #000; - } - .stick .btn:last-child, - .stick input:last-child { - border-radius: 0 3px 3px 0; - } - .stick .btn + .btn, - .stick .btn + input, - .stick input + .btn, - .stick input + input { - border-left: none; - } - .stick .btn + .dropdown > .btn { - border-left: none; - border-radius: 0 3px 3px 0; - } - .stick .btn + .dropdown a { - font-size: 12px; - } - -.btn { - display: inline-block; - min-height: 37px; - min-width: 15px; - padding: 5px 10px; - background: linear-gradient(to bottom, #fff 0%, #eee 100%); - border-radius: 3px; - border: 1px solid #000; - color: #888; - line-height: 20px; - vertical-align: middle; - cursor: pointer; - overflow: hidden; -} - a.btn { - min-height: 25px; - line-height: 25px; - } - .btn:hover { - background: linear-gradient(to bottom, #4A5D7A, #26303F); - text-decoration: none; - } - .btn.active, - .btn:active, - .dropdown-target:target ~ .btn.dropdown-toggle { - background: #26303F; - } - - .btn-important { - background: linear-gradient(to bottom, #0084CC, #0045CC); - color: #888888; - border: 1px solid #000000; - } - .btn-important:hover { - background: linear-gradient(to bottom, #0066CC, #0045CC); - } - .btn-important:active { - background: #0044CB; - box-shadow: none; - } - - .btn-attention { - background: linear-gradient(to bottom, #E95B57, #BD362F); - color: #888888; - border: 1px solid #000000; - } - .btn-attention:hover { - background: linear-gradient(to bottom, #D14641, #BD362F); - } - .btn-attention:active { - background: #BD362F; - box-shadow: none; - } - -/* NAVIGATION */ -.nav-list .nav-header, -.nav-list .item { - display: block; - height: 35px; - line-height: 35px; -} - .nav-list .item:hover { - background: #1a1a1a; - } - .nav-list .item:hover a { - color: #26303F; - } - .nav-list .item.active { - background: #26303F; - color: #1a1a1a; - } - .nav-list .item.active a { - color: #888; - } - .nav-list .disable { - color: #aaa; - background: #fafafa; - text-align: center; - } - .nav-list .item > * { - display: block; - padding: 0 10px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - .nav-list a:hover { - text-decoration: none; - } - .nav-list .item.error a { - color: #BD362F; - } - .nav-list .item.active.error a { - color: #fff; - background: #BD362F; - } - .nav-list .item.empty a { - color: #f39c12; - } - .nav-list .item.active.empty a { - color: #fff; - background: #f39c12; - } - - .nav-list .nav-header { - padding: 0 10px; - background: #1a1a1a; - border-bottom: 1px solid #2f2f2f; - font-weight: bold; - } - .nav-list .separator { - display: block; - height: 0; - margin: 5px 0; - border-bottom: 1px solid #ddd; - } - - .nav-list .nav-form { - padding: 3px; - text-align: center; - } - -.nav-head { - display: block; - margin: 0; - background: linear-gradient(to bottom, #fff, #f0f0f0); - border-bottom: 1px solid #ddd; - text-align: right; -} - .nav-head .item { - display: inline-block; - padding: 5px 10px; - } - -/* HORIZONTAL-LIST */ -.horizontal-list { - display: table; - table-layout: fixed; - margin: 0; - padding: 0; - width: 100%; -} - .horizontal-list .item { - display: table-cell; - vertical-align: middle; - } - -/* DROPDOWN */ -.dropdown { - position: relative; - display: inline-block; -} - .dropdown-target { - display: none; - } - - .dropdown-menu { - display: none; - min-width: 200px; - margin: 5px 0 0; - padding: 5px 0; - position: absolute; - right: 0px; - background: #1a1a1a; - border: 1px solid #888; - border-radius: 5px; - text-align: left; - } - .dropdown-menu:after { - content: ""; - position: absolute; - top: -6px; - right: 13px; - width: 10px; - height: 10px; - background: #1a1a1a; - border-top: 1px solid #888; - border-left: 1px solid #888; - z-index: -10; - transform: rotate(45deg); - } - .dropdown-header { - display: block; - padding: 0 5px; - color: #888; - font-weight: bold; - font-size: 14px; - line-height: 30px; - } - .dropdown-menu .item { - display: block; - height: 30px; - font-size: 90%; - line-height: 30px; - } - .dropdown-menu > .item > a { - display: block; - padding: 0 25px; - line-height: 30px; - } - .dropdown-menu > .item:hover { - background: #26303F; - color: #888; - } - .dropdown-menu > .item[aria-checked="true"] > a:before { - content: '✓ '; - font-weight: bold; - margin: 0 0 0 -1.2em; - padding: 0 0.2em 0 0; - } - .dropdown-menu > .item:hover > a { - color: #888; - text-decoration: none; - } - .dropdown-menu .input { - display: block; - height: 40px; - font-size: 90%; - line-height: 30px; - } - .dropdown-menu .input select, - .dropdown-menu .input input { - display: block; - height: 20px; - width: 95%; - margin: auto; - padding: 2px 5px; - border-radius: 3px; - } - .dropdown-menu .input select { - width: 70%; - height: auto; - } - .dropdown-menu .separator { - display: block; - height: 0; - margin: 5px 0; - border-bottom: 1px solid #888; - } - .dropdown-target:target ~ .dropdown-menu { - display: block; - z-index: 10; - } - .dropdown-close { - display: inline; - } - .dropdown-close a { - font-size: 0; - position: fixed; - top: 0; bottom: 0; - left: 0; right: 0; - display: block; - z-index: -10; - } - -/* ALERTS */ -.alert { - display: block; - width: 90%; - margin: 15px auto; - padding: 10px 15px; - background: #1a1a1a; - border: 1px solid #ccc; - border-right: 1px solid #aaa; - border-bottom: 1px solid #aaa; - border-radius: 5px; - color: #aaa; -} - .alert-head { - margin: 0; - font-weight: bold; - font-size: 110%; - } - .alert-warn { - border: 1px solid #c95; - color: #c95; - } - .alert-success { - border: 1px solid #484; - color: #484; - } - .alert-error { - border: 1px solid #844; - color: #844; - } - -/* ICÔNES */ -.icon { - display: inline-block; - width: 16px; - height: 16px; - vertical-align: middle; - line-height: 16px; -} diff --git a/p/themes/default_dark/loader.gif b/p/themes/default_dark/loader.gif deleted file mode 100644 index 86022be71..000000000 Binary files a/p/themes/default_dark/loader.gif and /dev/null differ diff --git a/p/themes/default_dark/metadata.json b/p/themes/default_dark/metadata.json deleted file mode 100644 index 7504831a6..000000000 --- a/p/themes/default_dark/metadata.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "Dark", - "author": "AD", - "description": "Le coté obscur du thème par défaut pour FreshRSS", - "version": 0.1, - "files": ["global.css", "freshrss.css"] -} diff --git a/p/themes/flat-design/freshrss.css b/p/themes/flat-design/freshrss.css deleted file mode 100644 index dca1b3f28..000000000 --- a/p/themes/flat-design/freshrss.css +++ /dev/null @@ -1,817 +0,0 @@ -@charset "UTF-8"; - -/* STRUCTURE */ -body { - background: #fafafa; -} - -.header { - display: table; - width: 100%; - table-layout: fixed; - background: #ecf0f1; -} - .header > .item { - display: table-cell; - padding: 10px 0; - vertical-align: middle; - text-align: center; - } - .header > .item.title { - width: 250px; - white-space: nowrap; - } - .logo { - display: inline-block; - font-size: 48px; - height: 32px; - width: 32px; - padding: 10px; - } - .header > .item.title h1 { - display: inline-block; - margin: 0; - } - .header > .item.search input { - width: 230px; - transition: width 200ms linear; - } - .header .item.search input:focus { - width: 330px; - } - .header > .item.configure { - width: 100px; - } - -.item a:hover { - text-decoration: none; -} - -#global { - display: table; - width: 100%; - height: 100%; - table-layout: fixed; -} - .aside { - display: table-cell; - height: 100%; - width: 250px; - vertical-align: top; - background: #ecf0f1; - } - .aside .nav-form input { - width: 180px; - } - .aside.aside_flux { - padding: 10px 0 40px; - } - .aside.aside_feed .nav-form input { - width: 140px; - } - .aside.aside_feed .nav-form .dropdown-menu { - right: -20px; - } - .aside.aside_feed .nav-form .dropdown-menu:after { - right: 33px; - } - - .nav-login { - display: none; - } - - .nav_menu { - width: 100%; - text-align: center; - padding: 5px 0; - } - .nav_menu .search { - display:none; - } - -.favicon { - height: 16px; - width: 16px; -} - -.categories { - margin: 0; - padding: 0; - text-align: center; - list-style: none; -} - .category { - display: block; - width: 220px; - margin: 10px auto; - text-align: left; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - .category .btn:first-child { - width: 195px; - position: relative; - } - .category.stick .btn:first-child { - width:160px; - } - .category .btn:first-child:not([data-unread="0"]):after { - content: attr(data-unread); - position: absolute; - top: 5px; right: 5px; - padding: 0 5px; - color: #fff; - font-size: 90%; - background: #3498DB; - border-radius: 5px; - } - .category + .feeds:not(.active) { - display:none; - } - .categories .feeds { - width: 100%; - margin: 0 auto; - list-style: none; - } - .categories .feeds .item.active:after { - content: "⇢"; - line-height: 35px; - float: right; - } - .categories .feeds .item.empty .feed { - color: #e67e22; - } - .categories .feeds .item.error .feed { - color: #BD362F; - } - .categories .feeds .item .feed { - display: inline-block; - margin: 0; - width: 165px; - line-height: 35px; - font-size: 90%; - vertical-align: middle; - text-align: left; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - .feed:not([data-unread="0"]) { - font-weight:bold; - } - .feed:not([data-unread="0"]):before { - content: "(" attr(data-unread) ") "; - } - .categories .feeds .dropdown-menu { - left: 0; - } - .categories .feeds .dropdown-menu:after { - left: 2px; - } - .categories .feeds .item .dropdown-toggle > .icon { - visibility: hidden; - cursor: pointer; - } - .categories .feeds .item .dropdown-target:target ~ .dropdown-toggle > .icon, - .categories .feeds .item:hover .dropdown-toggle > .icon, - .categories .feeds .item.active .dropdown-toggle > .icon { - background-color: #95a5a6; - border-radius: 3px; - visibility: visible; - } - .categories .btn:hover .notRead, - .categories .btn.active .notRead { - background: #2980B9; - border-left: 3px solid #3498DB; - } - -.post { - padding: 10px 50px; -} - .post form { - margin: 10px 0; - } - -.day { - padding: 5px 15px; - font-size: 130%; - font-weight: bold; - line-height: 50px; - border-left: 3px solid #ecf0f1; -} - .day .name { - position: absolute; - right: 0; - width: 50%; - height: 1.5em; - padding: 0 10px 0 0; - overflow: hidden; - color: #aab; - font-size: 1.8em; - opacity: .3; - font-style: italic; - text-align: right; - white-space: nowrap; - text-overflow: ellipsis; - z-index: -10; - } - -.flux { - border-left: 3px solid #ecf0f1; -} - .flux:hover { - background: #fff; - } - .flux.not_read { - border-left-color: #FF5300; - background: #FFF3ED; - } - .flux.favorite { - border-left-color: #FFC300; - background: #FFF6DA; - } - .flux.current { - border-left-color: #3498db; - background: #fff; - } - - .flux_header { - background: inherit; - height: 25px; - font-size: 12px; - border-top: 1px solid #ecf0f1; - cursor: pointer; - } - .flux .item { - line-height: 40px; - white-space: nowrap; - } - .flux_header > .item { - overflow: hidden; - text-overflow: ellipsis; - } - .flux .item.manage { - width: 40px; - text-align: center; - } - .flux .item.website { - width: 200px; - } - .website .favicon { - padding: 5px; - } - .flux .item.title { - background: inherit; - } - .flux:hover .item.title { - border-right: 2px solid rgba(127, 127, 127, 0.1); - padding-right: 1em; - position: absolute; - } - .flux .item.title a { - color: #333; - outline: none; - } - .flux.current .item.title, - .flux.not_read .item.title { - font-weight: bold; - } - .flux .item.date { - width: 200px; - padding:0 5px 0 0; - text-align: right; - font-size: 10px; - color: #666; - } - .link { - width: 40px; - text-align: center; - } - -#stream.reader .flux { - position: relative; - padding: 0 0 30px; - border: none; - background: #ecf0f1; - color: #34495e; - font-size: 120%; -} - #stream.reader .flux .author { - margin: 0 0 10px; - font-size: 90%; - color: #aaa; - } - -#stream.global { - text-align: center; -} - #stream.global .box-category { - display: inline-block; - width: 280px; - margin: 20px 10px; - vertical-align: top; - border: 1px solid #ddd; - border-radius: 5px; - text-align: left; - } - #stream.global .category { - width: 100%; - margin: 0; - } - #stream.global .btn { - display: block; - width: auto; - height: 35px; - margin: 0; - padding: 0 10px; - background: #ecf0f1; - color: #333; - border-bottom: 1px solid #ddd; - border-radius: 5px 5px 0 0; - line-height: 35px; - font-size: 120%; - } - #stream.global .btn:not([data-unread="0"]) { - font-weight:bold; - } - #stream.global .btn:first-child:not([data-unread="0"]):after { - top: 0; right: 5px; - border: 0; - background: none; - color: #333; - font-weight: bold; - box-shadow: none; - } - #stream.global .box-category .feeds { - display: block; - max-height: 250px; - margin: 0; - list-style: none; - overflow: auto; - } - #stream.global .box-category .feeds .item { - padding: 2px 10px; - font-size: 90%; - } - #stream.global .box-category .feed { - width: 220px; - } - -.content { - min-height: 150px; - max-width: 550px; - margin: 0 auto; - padding: 20px 10px; - line-height: 170%; - word-wrap: break-word; -} - .content h1, .content h2, .content h3 { - margin: 20px 0 5px; - } - .content > .title { - font-size: x-large; - margin: 0; - } - - .content p { - margin: 0 0 20px; - } - img.big { - display: block; - margin: 10px auto; - } - figure img.big { - margin: 0; - } - .content hr { - margin: 30px 0; - height: 1px; - background: #ddd; - border: 0; - } - .content pre { - margin: 10px auto; - padding: 10px; - overflow: auto; - background: #000; - color: #fff; - font-size: 110%; - } - .content q, .content blockquote { - display: block; - margin: 5px 0; - padding: 5px 20px; - font-style: italic; - border-left: 4px solid #ccc; - color: #666; - } - .content blockquote p { - margin: 0; - } - -#panel { - display: none; - position: fixed; - top: 10px; bottom: 10px; - left: 100px; right: 100px; - overflow: auto; - background: #fff; - border: 1px solid #95a5a6; - border-radius: 5px; -} - #panel .close { - position: fixed; - top: 10px; right: 0px; - display: inline-block; - width: 26px; - height: 26px; - margin: 0 10px 0 0; - border-radius: 3px; - text-align: center; - line-height: 24px; - background: #95a5a6; - } - #panel .close:hover { - background: #7f8c8d; - } - -#overlay { - display: none; - position: fixed; - top: 0; bottom: 0; - left: 0; right: 0; - background: rgba(0, 0, 0, 0.9); -} - -.flux_content .bottom { - font-size: 90%; - text-align: center; -} - -.hide_posts > :not(.active) > .flux_content { - display:none; -} - -/*** PAGINATION ***/ -.pagination { - display: table; - width: 100%; - margin: 0; - background: #ecf0f1; - text-align: center; - color: #000; - font-size: 80%; - line-height: 200%; - table-layout: fixed; - font-weight: bold; -} - .pagination .item { - display: table-cell; - line-height: 40px; - vertical-align: top; - } - .pagination .item.pager-current { - font-weight: bold; - font-size: 140%; - color: #ecf0f1; - background: #34495e; - } - .pagination .item.pager-first, - .pagination .item.pager-previous, - .pagination .item.pager-next, - .pagination .item.pager-last { - width: 100px; - } - .pagination .item a { - display: block; - color: #000; - font-weight: bold; - line-height: 40px; - } - .pagination .item a:hover { - color: #ecf0f1; - background: #34495e; - } - -#nav_entries { - display: table; - width: 250px; - height: 40px; - position: fixed; - bottom: 0; - left: 0; - margin: 0; - background: #34495e; - text-align: center; - line-height: 40px; - table-layout: fixed; -} - #nav_entries .item { - display: table-cell; - width: 30%; - } - #nav_entries a { - display: block; - } - #nav_entries .i_up { - margin: 5px 0 0; - vertical-align: top; - } - -.pagination .loading, -.pagination a:hover.loading { - background: url("loader.gif") center center no-repeat #34495e; - font-size: 0; -} - -#bigMarkAsRead { - background: #ecf0f1; - display: block; - font-style: normal; - padding: 32px 0 64px 0; - text-align: center; - text-decoration: none; - text-shadow: 0 -1px 0 #aaa; -} - #bigMarkAsRead:hover { - background: #34495e; - color: #fff; - } - .bigTick { - font-size: 72pt; - line-height: 1.6em; - } - -/*** NOTIFICATION ***/ -.notification { - position: absolute; - top: 10px; - left: 25%; right: 25%; - min-height: 30px; - padding: 10px; - line-height: 30px; - text-align: center; - border-radius: 3px; - background: #ddd; - color: #666; - font-weight: bold; - z-index: 10; -} - .notification.good { - background: #1abc9c; - color: #fff; - } - .notification.bad { - background: #e74c3c; - color: #fff; - } - .notification a.close { - display: inline-block; - width: 16px; - height: 16px; - float: right; - margin: -16px -16px 0 0; - padding: 5px; - border-radius: 3px; - line-height: 16px; - } - .notification.good a.close { - background: #1abc9c; - } - .notification.bad a.close { - background: #e74c3c; - } - -.toggle_aside, .btn.toggle_aside { - display: none; -} - -.actualizeProgress { - position: fixed; - top: 10px; - left: 25%; right: 25%; - padding: 5px; - background: #3498db; - color: #fff; - text-align: center; - border-radius: 3px; - font-weight: bold; -} - .actualizeProgress progress { - max-width: 100%; - width: 250px; - height: 15px; - vertical-align: middle; - background: #fff; - border: none; - } - .actualizeProgress .progress { - color: #ecf0f1; - } - -.logs { - border: 1px solid #34495e; -} - .log { - margin: 10px 0; - padding: 5px 2%; - overflow: auto; - background: #fafafa; - color: #666; - font-size: 90%; - } - .log>.date { - margin: 0 10px 0 0; - padding: 5px 10px; - border-radius: 20px; - } - .log.error>.date { - background: #e74c3c; - color: #fff; - } - .log.warning>.date { - background: #f39c12; - } - .log.notice>.date { - background: #ecf0f1; - } - .log.debug>.date { - background: #111; - color: #eee; - } - -.form-group table { - border-collapse:collapse; - margin:10px 0 0 220px; - text-align:center; -} - -.form-group tr, .form-group th, .form-group td { - font-weight:normal; - padding:.5em; -} - -select.number option { - text-align:right; -} - -@media(max-width: 840px) { - .header, - .aside .btn-important, - .aside .feeds .dropdown, - .flux_header .item.website span, - .item.date { - display: none; - } - .flux_header .item.website { - width: 40px; - text-align: center; - } - .flux_header .item.website .favicon { - padding: 12px; - } - - .nav-login { - display: block; - } - - .content { - font-size: 120%; - padding: 0; - } - - .pagination { - margin: 0 0 40px; - } - .pagination .pager-previous, .pagination .pager-next { - width: 100px; - } - - .toggle_aside, .btn.toggle_aside { - display: inline-block; - } - .aside { - position: fixed; - top: 0; left: 0; - width: 0; - overflow: hidden; - z-index: 10; - transition: width 200ms linear; - background: #ecf0f1; - } - .aside.aside_flux { - padding: 10px 0 0; - } - .aside:target { - width: 80%; - border-right: 1px solid #aaa; - overflow: auto; - } - .aside .toggle_aside { - position: absolute; - right: 10px; - display: inline-block; - width: 26px; - height: 26px; - margin: 0 10px 0 0; - border-radius: 3px; - text-align: center; - line-height: 24px; - background: #95a5a6; - } - .aside .toggle_aside:hover { - background: #7f8c8d; - } - .aside .categories { - margin: 30px 0; - } - - #nav_entries { - width: 100%; - } - - .nav_menu .btn { - margin: 5px 10px; - } - .nav_menu .stick { - margin: 0 10px; - } - .nav_menu .stick .btn { - margin: 5px 0; - } - .nav_menu .search { - display: inline-block; - max-width: 97%; - } - .nav_menu .search input { - max-width: 97%; - width: 90px; - } - .nav_menu .search input:focus { - width: 400px; - } - - #panel { - left: 5px; right: 5px; - } - - .day .date { - display: none; - } - .day .name { - height: 2.6em; - font-size: 1em; - text-shadow: none; - } - - .notification, - .actualizeProgress { - left: 10px; - right: 10px; - } -} - -/*** FALLBACK ***/ -.dropdown-menu:after { - -moz-transform: rotate(45deg); - -webkit-transform: rotate(45deg); - -ms-transform: rotate(45deg); -} - -@media print { - .header, - .aside, - .nav_menu, - .day, - .flux_header, - .flux_content .bottom, - .pagination { - display: none; - } - - html, body { - background: #fff; - color: #000; - font-family: Serif; - font-size: 12pt; - } - - #global, - .flux_content { - display: block !important; - } - - .flux_content .content { - width: 100% !important; - text-align: justify; - } - - .flux_content .content a { - color: #000; - } - .flux_content .content a:after { - content: " (" attr(href) ") "; - text-decoration: underline; - } -} diff --git a/p/themes/flat-design/global.css b/p/themes/flat-design/global.css deleted file mode 100644 index 5fd0a4dcf..000000000 --- a/p/themes/flat-design/global.css +++ /dev/null @@ -1,495 +0,0 @@ -@charset "UTF-8"; - -/* FONTS */ -@font-face { - font-family: "OpenSans"; - src: url("../fonts/openSans.woff") format("woff"); -} - - -* { - margin: 0; - padding: 0; -} -html, body { - height: 100%; - font-size: 95%; - font-family: "OpenSans", "Cantarell", "Helvetica", "Arial", sans-serif; -} - -/* LIENS */ -a { - color: #2980b9; - text-decoration: none; -} - a:hover { - text-decoration: underline; - } - -/* LISTES */ -ul, ol, dl { - margin: 10px 0 10px 30px; - line-height: 190%; -} - dd { - margin: 0 0 10px 30px; - } - -/* TITRES */ -h1, h2, h3 { - min-height: 40px; - margin: 15px 0 5px; - line-height: 40px; -} - -/* IMG */ -figure { - margin: 5px 0 10px; - text-align: center; -} - figcaption { - display: inline-block; - padding: 3px 20px; - color: #999; - font-style: italic; - border-bottom: 1px solid #ccc; - } -img { - height: auto; - max-width: 100%; - vertical-align: middle; -} - a img { - border: none; - } - -/* VIDEOS */ -iframe, embed, object, video { - max-width: 100%; -} - -/* FORMULAIRES */ -legend { - display: inline-block; - margin: 20px 0 5px; - padding: 5px 20px; - font-size: 150%; - clear: both; - background: #ecf0f1; - border-radius: 20px; -} -label { - display: block; - min-height: 25px; - padding: 5px 0; - font-size: 12px; - line-height: 25px; - cursor: pointer; - font-weight: bold; - color: #444; -} -input, select, textarea { - display: inline-block; - max-width: 100%; - min-height: 25px; - padding: 5px; - background: #FFF; - border: none; - border-bottom: 3px solid #ddd; - color: #666; - line-height: 25px; - vertical-align: middle; - border-radius: 5px; -} - option { - padding:0 .5em 0 .5em; - } - input[type="radio"], - input[type="checkbox"] { - width: 15px !important; - min-height: 15px !important; - } - input:focus, select:focus, textarea:focus { - color: #333; - border-color: #2980b9; - } - input:invalid, select:invalid { - border-color: red; - box-shadow: 0 0 2px 1px red; - } - -.form-group { - margin: 5px 0; - border: 1px solid transparent; -} - .form-group:after { - content: ""; - display: block; - clear: both; - } - .form-group:hover { - background: #fff; - border: 1px solid #eee; - border-radius: 3px; - } - .form-group.form-actions { - min-width: 250px; - margin: 20px 0; - padding: 5px 0; - background: #ecf0f1; - border-top: 3px solid #bdc3c7; - border-radius: 5px 5px 0 0; - } - .form-group.form-actions .btn { - margin: 0 10px; - } - .form-group .group-name { - display: block; - float: left; - width: 200px; - padding: 10px 0; - text-align: right; - } - .form-group .group-controls { - min-width: 250px; - min-height: 25px; - margin: 0 0 0 220px; - padding: 5px 0; - } - .form-group .group-controls label { - font-weight: normal; - font-size: 14px; - color: #000; - } - .form-group .group-controls .control { - display: block; - min-height: 30px; - padding: 5px 0; - line-height: 25px; - font-size: 14px; - } - -.stick { - display: inline-block; - white-space: nowrap; - font-size: 0px; - vertical-align: middle; -} - .stick .btn, - .stick input { - font-size: 14px; - border-radius: 0; - } - .stick .btn:first-child, - .stick input:first-child { - border-radius: 5px 0 0 5px; - } - .stick .btn:last-child, - .stick input:last-child, - .stick .btn + .dropdown > .btn { - border-radius: 0 5px 5px 0; - } - .stick .btn + .dropdown a { - font-size: 12px; - } - -.btn { - display: inline-block; - min-height: 38px; - min-width: 18px; - padding: 5px 10px; - background: #3498db; - border-radius: 5px; - border: none; - border-bottom: 3px solid #2980b9; - color: #fff; - line-height: 20px; - vertical-align: middle; - cursor: pointer; - overflow: hidden; -} - a.btn { - min-height: 25px; - line-height: 25px; - } - .btn.active, - .btn:active, - .btn:hover, - .dropdown-target:target ~ .btn.dropdown-toggle { - background: #2980b9; - text-decoration: none; - } - - .btn-important { - background: #e67e22; - color: #fff; - border-bottom: 3px solid #d35400; - } - .btn-important:active, - .btn-important:hover { - background: #d35400; - } - - .btn-attention { - background: #e74c3c; - color: #fff; - border-bottom: 3px solid #c0392b; - } - .btn-attention:hover, - .btn-attention:active { - background: #c0392b; - } - -/* NAVIGATION */ -.nav-list { - border-right: 1px solid #ecf0f1; -} -.nav-list .nav-header, -.nav-list .item { - display: block; - height: 35px; - line-height: 35px; - margin: 5px 0; -} - .nav-list .item:hover, - .nav-list .item.active { - background: #2980b9; - color: #fff; - } - .nav-list .item:hover a, - .nav-list .item.active a { - color: #fff; - } - .nav-list .disable { - color: #aaa; - background: #fafafa; - text-align: center; - } - .nav-list .item > * { - display: block; - padding: 0 10px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - .nav-list a:hover { - text-decoration: none; - } - .nav-list .item.error a { - color: #BD362F; - } - .nav-list .item:hover.error a, - .nav-list .item.active.error a { - color: #fff; - background: #BD362F; - } - .nav-list .item.empty a { - color: #f39c12; - } - .nav-list .item:hover.empty a, - .nav-list .item.active.empty a { - color: #fff; - background: #f39c12; - } - - .nav-list .nav-header { - padding: 0 10px; - margin: 0; - color: #fff; - background: #34495e; - font-weight: bold; - } - .nav-list .separator { - display: block; - height: 0; - margin: 5px 0; - border-bottom: 1px solid #ddd; - } - - .nav-list .nav-form { - padding: 3px; - text-align: center; - } - -.nav-head { - display: block; - margin: 0; - background: #34495e; - color: #fff; - text-align: right; -} - .nav-head a { - color: #fff; - } - .nav-head .item { - display: inline-block; - padding: 5px 10px; - } - -/* HORIZONTAL-LIST */ -.horizontal-list { - display: table; - table-layout: fixed; - margin: 0; - padding: 0; - width: 100%; -} - .horizontal-list .item { - display: table-cell; - vertical-align: middle; - } - -/* DROPDOWN */ -.dropdown { - position: relative; - display: inline-block; -} - .dropdown-target { - display: none; - } - - .dropdown-menu { - display: none; - min-width: 200px; - margin: 5px 0 0; - padding: 5px 0; - position: absolute; - right: 0px; - background: #fff; - border: 1px solid #95a5a6; - border-radius: 3px; - text-align: left; - } - .dropdown-menu:after { - content: ""; - position: absolute; - top: -6px; - right: 13px; - width: 10px; - height: 10px; - background: #fff; - border-top: 1px solid #95a5a6; - border-left: 1px solid #95a5a6; - z-index: -10; - transform: rotate(45deg); - } - .dropdown-header { - display: block; - padding: 0 5px; - color: #34495e; - font-weight: bold; - font-size: 14px; - line-height: 30px; - } - .dropdown-menu > .item { - display: block; - height: 30px; - font-size: 90%; - line-height: 30px; - } - .dropdown-menu > .item > a { - display: block; - padding: 0 25px; - line-height: 30px; - } - .dropdown-menu > .item:hover > a { - background: #2980b9; - color: #fff; - } - .dropdown-menu > .item[aria-checked="true"] > a:before { - content: '✓ '; - font-weight: bold; - margin: 0 0 0 -1.2em; - padding: 0 0.2em 0 0; - } - .dropdown-menu > .item:hover > a { - color: #fff; - text-decoration: none; - } - .dropdown-menu .input { - display: block; - height: 40px; - font-size: 90%; - line-height: 30px; - } - .dropdown-menu label { - font-weight: normal; - } - .dropdown-menu .input select, - .dropdown-menu .input input { - display: block; - height: 20px; - width: 95%; - margin: auto; - padding: 2px 5px; - border-radius: 3px; - } - .dropdown-menu .input select { - width: 70%; - height: auto; - } - .dropdown-menu .separator { - display: block; - height: 0; - margin: 5px 0; - border-bottom: 1px solid #95a5a6; - } - .dropdown-target:target ~ .dropdown-menu { - display: block; - z-index: 10; - } - .dropdown-close { - display: inline; - } - .dropdown-close a { - font-size: 0; - position: fixed; - top: 0; bottom: 0; - left: 0; right: 0; - display: block; - z-index: -10; - } - -/* ALERTS */ -.alert { - display: block; - width: 90%; - margin: 15px auto; - padding: 10px 15px; - background: #f4f4f4; - border: 1px solid #ccc; - border-right: 1px solid #aaa; - border-bottom: 1px solid #aaa; - border-radius: 5px; - color: #aaa; - text-shadow: 0 0 1px #eee; -} - .alert-head { - margin: 0; - font-weight: bold; - font-size: 110%; - } - .alert-warn { - background: #ffe; - border: 1px solid #eeb; - color: #c95; - } - .alert-success { - background: #dfd; - border: 1px solid #cec; - color: #484; - } - .alert-error { - background: #fdd; - border: 1px solid #ecc; - color: #844; - } - -/* ICÔNES */ -.icon { - display: inline-block; - width: 16px; - height: 16px; - vertical-align: middle; - line-height: 16px; -} diff --git a/p/themes/flat-design/icons/add.svg b/p/themes/flat-design/icons/add.svg deleted file mode 100644 index 15767a3ad..000000000 --- a/p/themes/flat-design/icons/add.svg +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - image/svg+xml - - Gnome Symbolic Icon Theme - - - - - - - Gnome Symbolic Icon Theme - - - - - - - - - - - - - diff --git a/p/themes/flat-design/icons/all.svg b/p/themes/flat-design/icons/all.svg deleted file mode 100644 index d20e0f5bf..000000000 --- a/p/themes/flat-design/icons/all.svg +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - image/svg+xml - - Gnome Symbolic Icon Theme - - - - - - - Gnome Symbolic Icon Theme - - - - - - - - - - - - - - - diff --git a/p/themes/flat-design/icons/close.svg b/p/themes/flat-design/icons/close.svg deleted file mode 100644 index 629fda7ff..000000000 --- a/p/themes/flat-design/icons/close.svg +++ /dev/null @@ -1,28 +0,0 @@ - - - - - Gnome Symbolic Icon Theme - - - - image/svg+xml - - Gnome Symbolic Icon Theme - - - - - - - - - - - - - - - - - diff --git a/p/themes/flat-design/icons/configure.svg b/p/themes/flat-design/icons/configure.svg deleted file mode 100644 index 969c5719f..000000000 --- a/p/themes/flat-design/icons/configure.svg +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - image/svg+xml - - Gnome Symbolic Icon Theme - - - - - - - Gnome Symbolic Icon Theme - - - - - - - - - - - - - - diff --git a/p/themes/flat-design/icons/down.svg b/p/themes/flat-design/icons/down.svg deleted file mode 100644 index 31730626f..000000000 --- a/p/themes/flat-design/icons/down.svg +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - image/svg+xml - - Gnome Symbolic Icon Theme - - - - - - - Gnome Symbolic Icon Theme - - - - - - - - - - - - - - diff --git a/p/themes/flat-design/icons/next.svg b/p/themes/flat-design/icons/next.svg deleted file mode 100644 index d75cc40f5..000000000 --- a/p/themes/flat-design/icons/next.svg +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - image/svg+xml - - Gnome Symbolic Icon Theme - - - - - - - Gnome Symbolic Icon Theme - - - - - - - - - - - - - - diff --git a/p/themes/flat-design/icons/prev.svg b/p/themes/flat-design/icons/prev.svg deleted file mode 100644 index 9ba03ceb2..000000000 --- a/p/themes/flat-design/icons/prev.svg +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - image/svg+xml - - Gnome Symbolic Icon Theme - - - - - - - Gnome Symbolic Icon Theme - - - - - - - - - - - - - - diff --git a/p/themes/flat-design/icons/refresh.svg b/p/themes/flat-design/icons/refresh.svg deleted file mode 100644 index 8f95bf443..000000000 --- a/p/themes/flat-design/icons/refresh.svg +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - image/svg+xml - - Gnome Symbolic Icon Theme - - - - - - - Gnome Symbolic Icon Theme - - - - - - - - - - - - - - diff --git a/p/themes/flat-design/icons/search.svg b/p/themes/flat-design/icons/search.svg deleted file mode 100644 index bca7571b4..000000000 --- a/p/themes/flat-design/icons/search.svg +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - image/svg+xml - - Gnome Symbolic Icon Theme - - - - - - - Gnome Symbolic Icon Theme - - - - - - - - - - - - - - - diff --git a/p/themes/flat-design/icons/up.svg b/p/themes/flat-design/icons/up.svg deleted file mode 100644 index 3ab11b168..000000000 --- a/p/themes/flat-design/icons/up.svg +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - image/svg+xml - - Gnome Symbolic Icon Theme - - - - - - - Gnome Symbolic Icon Theme - - - - - - - - - - - - - - diff --git a/p/themes/flat-design/loader.gif b/p/themes/flat-design/loader.gif deleted file mode 100644 index ce36565b3..000000000 Binary files a/p/themes/flat-design/loader.gif and /dev/null differ diff --git a/p/themes/flat-design/metadata.json b/p/themes/flat-design/metadata.json deleted file mode 100644 index 6b94d11c2..000000000 --- a/p/themes/flat-design/metadata.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "Flat design", - "author": "Marien Fressinaud", - "description": "Thème plat pour FreshRSS", - "version": 0.1, - "files": ["global.css", "freshrss.css"] -} \ No newline at end of file -- cgit v1.2.3 From 6f117abfb6010150f95d2d0830a9715cd6f270fc Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sat, 18 Jan 2014 22:54:53 +0100 Subject: Bugs chargement des articles complets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Corrige https://github.com/marienfressinaud/FreshRSS/issues/365 Ajoute le nettoyage correct du HTML par SimplePie, ainsi que le décodage des caractères HTML dans l'URL, et enfin un message dans le syslog pour chaque article téléchargé --- app/Models/Entry.php | 2 +- lib/lib_rss.php | 14 +++----------- 2 files changed, 4 insertions(+), 12 deletions(-) (limited to 'app/Models') diff --git a/app/Models/Entry.php b/app/Models/Entry.php index 83f68ce78..a6c67221b 100644 --- a/app/Models/Entry.php +++ b/app/Models/Entry.php @@ -159,7 +159,7 @@ class FreshRSS_Entry extends Minz_Model { try { // l'article n'est pas en BDD, on va le chercher sur le site $this->content = get_content_by_parsing( - $this->link(), $pathEntries + htmlspecialchars_decode($this->link(), ENT_QUOTES), $pathEntries ); } catch (Exception $e) { // rien à faire, on garde l'ancien contenu (requête a échoué) diff --git a/lib/lib_rss.php b/lib/lib_rss.php index 4f98ed14a..33d7ebc32 100644 --- a/lib/lib_rss.php +++ b/lib/lib_rss.php @@ -110,6 +110,7 @@ function sanitizeHTML($data) { static $simplePie = null; if ($simplePie == null) { $simplePie = new SimplePie(); + $simplePie->init(); } return html_only_entity_decode($simplePie->sanitize->sanitize($data, SIMPLEPIE_CONSTRUCT_MAYBE_HTML)); } @@ -118,22 +119,13 @@ function sanitizeHTML($data) { function get_content_by_parsing ($url, $path) { require_once (LIB_PATH . '/lib_phpQuery.php'); + syslog(LOG_INFO, 'FreshRSS GET ' . $url); $html = file_get_contents ($url); if ($html) { $doc = phpQuery::newDocument ($html); $content = $doc->find ($path); - $content->find ('*')->removeAttr ('style') - ->removeAttr ('id') - ->removeAttr ('class') - ->removeAttr ('onload') - ->removeAttr ('target'); - $content->removeAttr ('style') - ->removeAttr ('id') - ->removeAttr ('class') - ->removeAttr ('onload') - ->removeAttr ('target'); - return $content->__toString (); + return sanitizeHTML($content->__toString()); } else { throw new Exception (); } -- cgit v1.2.3 From bc6aba67bbe023c461a47bdf7c0c81d061ca6b5a Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Wed, 22 Jan 2014 21:34:59 +0100 Subject: Bug récupération flux tronqués MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Corrige https://github.com/marienfressinaud/FreshRSS/issues/381 --- app/Models/Feed.php | 44 +------------------------------------------ lib/lib_rss.php | 54 +++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 51 insertions(+), 47 deletions(-) (limited to 'app/Models') diff --git a/app/Models/Feed.php b/app/Models/Feed.php index df762c8fa..22c019080 100644 --- a/app/Models/Feed.php +++ b/app/Models/Feed.php @@ -187,54 +187,12 @@ class FreshRSS_Feed extends Minz_Model { Minz_Exception::ERROR ); } else { - $feed = new SimplePie (); - $feed->set_useragent(Minz_Translate::t ('freshrss') . '/' . FRESHRSS_VERSION . ' (' . PHP_OS . '; ' . FRESHRSS_WEBSITE . ') ' . SIMPLEPIE_NAME . '/' . SIMPLEPIE_VERSION); $url = htmlspecialchars_decode ($this->url, ENT_QUOTES); if ($this->httpAuth != '') { $url = preg_replace ('#((.+)://)(.+)#', '${1}' . $this->httpAuth . '@${3}', $url); } - + $feed = customSimplePie(); $feed->set_feed_url ($url); - $feed->set_cache_location (CACHE_PATH); - $feed->set_cache_duration(1500); - $feed->strip_htmltags (array ( - 'base', 'blink', 'body', 'doctype', 'embed', - 'font', 'form', 'frame', 'frameset', 'html', - 'input', 'marquee', 'meta', 'noscript', - 'object', 'param', 'plaintext', 'script', 'style', - )); - $feed->strip_attributes(array_merge($feed->strip_attributes, array( - 'autoplay', 'onload', 'onunload', 'onclick', 'ondblclick', 'onmousedown', 'onmouseup', - 'onmouseover', 'onmousemove', 'onmouseout', 'onfocus', 'onblur', - 'onkeypress', 'onkeydown', 'onkeyup', 'onselect', 'onchange', 'seamless'))); - $feed->add_attributes(array( - 'img' => array('lazyload' => ''), //http://www.w3.org/TR/resource-priorities/ - 'audio' => array('preload' => 'none'), - 'iframe' => array('postpone' => '', 'sandbox' => 'allow-scripts allow-same-origin'), - 'video' => array('postpone' => '', 'preload' => 'none'), - )); - $feed->set_url_replacements(array( - 'a' => 'href', - 'area' => 'href', - 'audio' => 'src', - 'blockquote' => 'cite', - 'del' => 'cite', - 'form' => 'action', - 'iframe' => 'src', - 'img' => array( - 'longdesc', - 'src' - ), - 'input' => 'src', - 'ins' => 'cite', - 'q' => 'cite', - 'source' => 'src', - 'track' => 'src', - 'video' => array( - 'poster', - 'src', - ), - )); $feed->init (); if ($feed->error ()) { diff --git a/lib/lib_rss.php b/lib/lib_rss.php index 33d7ebc32..ba5b01112 100644 --- a/lib/lib_rss.php +++ b/lib/lib_rss.php @@ -106,13 +106,59 @@ function html_only_entity_decode($text) { return strtr($text, $htmlEntitiesOnly); } -function sanitizeHTML($data) { +function customSimplePie() { + $simplePie = new SimplePie(); + $simplePie->set_useragent(Minz_Translate::t('freshrss') . '/' . FRESHRSS_VERSION . ' (' . PHP_OS . '; ' . FRESHRSS_WEBSITE . ') ' . SIMPLEPIE_NAME . '/' . SIMPLEPIE_VERSION); + $simplePie->set_cache_location(CACHE_PATH); + $simplePie->set_cache_duration(1500); + $simplePie->strip_htmltags(array( + 'base', 'blink', 'body', 'doctype', 'embed', + 'font', 'form', 'frame', 'frameset', 'html', + 'input', 'marquee', 'meta', 'noscript', + 'object', 'param', 'plaintext', 'script', 'style', + )); + $simplePie->strip_attributes(array_merge($simplePie->strip_attributes, array( + 'autoplay', 'onload', 'onunload', 'onclick', 'ondblclick', 'onmousedown', 'onmouseup', + 'onmouseover', 'onmousemove', 'onmouseout', 'onfocus', 'onblur', + 'onkeypress', 'onkeydown', 'onkeyup', 'onselect', 'onchange', 'seamless'))); + $simplePie->add_attributes(array( + 'img' => array('lazyload' => ''), //http://www.w3.org/TR/resource-priorities/ + 'audio' => array('preload' => 'none'), + 'iframe' => array('postpone' => '', 'sandbox' => 'allow-scripts allow-same-origin'), + 'video' => array('postpone' => '', 'preload' => 'none'), + )); + $simplePie->set_url_replacements(array( + 'a' => 'href', + 'area' => 'href', + 'audio' => 'src', + 'blockquote' => 'cite', + 'del' => 'cite', + 'form' => 'action', + 'iframe' => 'src', + 'img' => array( + 'longdesc', + 'src' + ), + 'input' => 'src', + 'ins' => 'cite', + 'q' => 'cite', + 'source' => 'src', + 'track' => 'src', + 'video' => array( + 'poster', + 'src', + ), + )); + return $simplePie; +} + +function sanitizeHTML($data, $base = '') { static $simplePie = null; if ($simplePie == null) { - $simplePie = new SimplePie(); + $simplePie = customSimplePie(); $simplePie->init(); } - return html_only_entity_decode($simplePie->sanitize->sanitize($data, SIMPLEPIE_CONSTRUCT_MAYBE_HTML)); + 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 */ @@ -125,7 +171,7 @@ function get_content_by_parsing ($url, $path) { if ($html) { $doc = phpQuery::newDocument ($html); $content = $doc->find ($path); - return sanitizeHTML($content->__toString()); + return sanitizeHTML($content->__toString(), $url); } else { throw new Exception (); } -- cgit v1.2.3 From e98ac32716dfe9343baa5bce7ad2c1e98c40540c Mon Sep 17 00:00:00 2001 From: Alexis Degrugillier Date: Wed, 22 Jan 2014 19:06:46 -0500 Subject: Ajout de statistiques de l'application Conflicts: app/i18n/en.php app/i18n/fr.php --- app/Controllers/indexController.php | 10 ++ app/Models/StatsDAO.php | 178 ++++++++++++++++++++++++++++++++++++ app/i18n/en.php | 14 ++- app/i18n/fr.php | 14 ++- app/layout/aside_flux.phtml | 2 +- app/layout/header.phtml | 1 + app/views/index/stats.phtml | 92 +++++++++++++++++++ p/scripts/flotr2.min.js | 27 ++++++ p/themes/Origine/freshrss.css | 43 +++++++++ 9 files changed, 378 insertions(+), 3 deletions(-) create mode 100644 app/Models/StatsDAO.php create mode 100644 app/views/index/stats.phtml create mode 100644 p/scripts/flotr2.min.js (limited to 'app/Models') diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php index 45ded6fd4..84ed992f9 100755 --- a/app/Controllers/indexController.php +++ b/app/Controllers/indexController.php @@ -6,12 +6,14 @@ class FreshRSS_index_Controller extends Minz_ActionController { private $entryDAO; private $feedDAO; private $catDAO; + private $statsDAO; function __construct($router) { parent::__construct($router); $this->entryDAO = new FreshRSS_EntryDAO (); $this->feedDAO = new FreshRSS_FeedDAO (); $this->catDAO = new FreshRSS_CategoryDAO (); + $this->statsDAO = new FreshRSS_StatsDAO (); } public function indexAction () { @@ -198,6 +200,14 @@ class FreshRSS_index_Controller extends Minz_ActionController { return false; } } + + public function statsAction () { + Minz_View::appendScript (Minz_Url::display ('/scripts/flotr2.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/flotr2.min.js'))); + $this->view->repartition = $this->statsDAO->calculateEntryRepartition(); + $this->view->count = ($this->statsDAO->calculateEntryCount()); + $this->view->feedByCategory = $this->statsDAO->calculateFeedByCategory(); + $this->view->entryByCategory = $this->statsDAO->calculateEntryByCategory(); + } public function aboutAction () { Minz_View::prependTitle (Minz_Translate::t ('about') . ' · '); diff --git a/app/Models/StatsDAO.php b/app/Models/StatsDAO.php new file mode 100644 index 000000000..a317c8f27 --- /dev/null +++ b/app/Models/StatsDAO.php @@ -0,0 +1,178 @@ +prefix}entry AS e +, {$this->prefix}feed AS f +WHERE e.id_feed = f.id +AND f.priority = 10 +SQL; + $stm = $this->bd->prepare($sql); + $stm->execute(); + $res = $stm->fetchAll(PDO::FETCH_ASSOC); + $repartition['main_stream'] = $res[0]; + + // Generates the repartition for all entries + $sql = <<prefix}entry AS e +SQL; + $stm = $this->bd->prepare($sql); + $stm->execute(); + $res = $stm->fetchAll(PDO::FETCH_ASSOC); + $repartition['all_feeds'] = $res[0]; + + return $repartition; + } + + /** + * Calculates entry count per day on a 30 days period. + * Returns the result as a JSON string. + * + * @return string + */ + public function calculateEntryCount() { + $count = array(); + + // Generates a list of 30 last day to be sure we always have 30 days. + // If we do not do that kind of thing, we'll end up with holes in the + // days if the user do not have a lot of feeds. + $sql = <<bd->prepare($sql); + $stm->execute(); + $res = $stm->fetchAll(PDO::FETCH_ASSOC); + foreach ($res as $value) { + $count[$value['day']] = 0; + } + + // Get stats per day for the last 30 days and applies the result on + // the array created with the last query. + $sql = <<prefix}entry AS e +WHERE FROM_UNIXTIME(e.date, '%Y%m%d') BETWEEN DATE_FORMAT(DATE_ADD(NOW(), INTERVAL -30 DAY), '%Y%m%d') AND DATE_FORMAT(DATE_ADD(NOW(), INTERVAL -1 DAY), '%Y%m%d') +GROUP BY day +ORDER BY day ASC +SQL; + $stm = $this->bd->prepare($sql); + $stm->execute(); + $res = $stm->fetchAll(PDO::FETCH_ASSOC); + + foreach ($res as $value) { + $count[$value['day']] = (int) $value['count']; + } + + return $this->convertToSerie($count); + } + + /** + * Calculates feed count per category. + * Returns the result as a JSON string. + * + * @return string + */ + public function calculateFeedByCategory() { + $sql = <<prefix}category AS c, +{$this->prefix}feed AS f +WHERE c.id = f.category +GROUP BY label +SQL; + $stm = $this->bd->prepare($sql); + $stm->execute(); + $res = $stm->fetchAll(PDO::FETCH_ASSOC); + + return $this->convertToPieSerie($res); + } + + /** + * Calculates entry count per category. + * Returns the result as a JSON string. + * + * @return string + */ + public function calculateEntryByCategory() { + $sql = <<prefix}category AS c, +{$this->prefix}feed AS f, +{$this->prefix}entry AS e +WHERE c.id = f.category +AND f.id = e.id_feed +GROUP BY label +SQL; + $stm = $this->bd->prepare($sql); + $stm->execute(); + $res = $stm->fetchAll(PDO::FETCH_ASSOC); + + return $this->convertToPieSerie($res); + } + + private function convertToSerie($data) { + $serie = array(); + + foreach ($data as $key => $value) { + $serie[] = array($key, $value); + } + + return json_encode($serie); + } + + private function convertToPieSerie($data) { + $serie = array(); + + foreach ($data as $value) { + $value['data'] = array(array(0, (int)$value['data'])); + $serie[] = $value; + } + + return json_encode($serie); + } + +} diff --git a/app/i18n/en.php b/app/i18n/en.php index 5d9b01f0b..963105f35 100644 --- a/app/i18n/en.php +++ b/app/i18n/en.php @@ -13,6 +13,7 @@ return array ( 'category' => 'Category', 'shortcuts' => 'Shortcuts', 'about' => 'About', + 'stats' => 'Statistics', 'your_rss_feeds' => 'Your RSS feeds', 'add_rss_feed' => 'Add a RSS feed', @@ -20,7 +21,8 @@ return array ( 'import_export_opml' => 'Import / export (OPML)', 'subscription_management' => 'Subscriptions management', - 'all_feeds' => 'Main stream', + 'main_stream' => 'Main stream', + 'all_feeds' => 'All feeds', 'favorite_feeds' => 'Favourites (%d)', 'not_read' => '%d unread', 'not_reads' => '%d unread', @@ -297,4 +299,14 @@ return array ( // format for date() function, %s allows to indicate month in letter 'format_date' => '%s j\<\s\u\p\>S\<\/\s\u\p\> Y', 'format_date_hour' => '%s j\<\s\u\p\>S\<\/\s\u\p\> Y \a\t H\:i', + + 'status_favorites' => 'favourites', + 'status_read' => 'read', + 'status_unread' => 'unread', + 'status_total' => 'total', + + 'stats_entry_repartition' => 'Entry repartition', + 'stats_entry_per_day' => 'Entry per day (last 30 days)', + 'stats_feed_per_category' => 'Feed per category', + 'stats_entry_per_category' => 'Entry per category', ); diff --git a/app/i18n/fr.php b/app/i18n/fr.php index fd2257c70..c8b03ef8b 100644 --- a/app/i18n/fr.php +++ b/app/i18n/fr.php @@ -13,6 +13,7 @@ return array ( 'category' => 'Catégorie', 'shortcuts' => 'Raccourcis', 'about' => 'À propos', + 'stats' => 'Statistiques', 'your_rss_feeds' => 'Vos flux RSS', 'add_rss_feed' => 'Ajouter un flux RSS', @@ -20,7 +21,8 @@ return array ( 'import_export_opml' => 'Importer / exporter (OPML)', 'subscription_management' => 'Gestion des abonnements', - 'all_feeds' => 'Flux principal', + 'main_stream' => 'Flux principal', + 'all_feeds' => 'Tous les flux', 'favorite_feeds' => 'Favoris (%d)', 'not_read' => '%d non lu', 'not_reads' => '%d non lus', @@ -297,4 +299,14 @@ return array ( // format pour la fonction date(), %s permet d'indiquer le mois en toutes lettres 'format_date' => 'j %s Y', 'format_date_hour' => 'j %s Y \à H\:i', + + 'status_favorites' => 'favoris', + 'status_read' => 'lus', + 'status_unread' => 'non lus', + 'status_total' => 'total', + + 'stats_entry_repartition' => 'Répartition des articles', + 'stats_entry_per_day' => 'Nombre d’articles par jour (30 derniers jours)', + 'stats_feed_per_category' => 'Flux par categorie', + 'stats_entry_per_category' => 'Article par categorie', ); diff --git a/app/layout/aside_flux.phtml b/app/layout/aside_flux.phtml index f7d8b12b9..1e3211886 100644 --- a/app/layout/aside_flux.phtml +++ b/app/layout/aside_flux.phtml @@ -23,7 +23,7 @@
  • diff --git a/app/layout/header.phtml b/app/layout/header.phtml index b00c30e71..eef53a3fd 100644 --- a/app/layout/header.phtml +++ b/app/layout/header.phtml @@ -74,6 +74,7 @@ if (Minz_Configuration::canLogIn()) {
  • +
  • + + +

    + +
    +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
     
    repartition['main_stream']['total']?>repartition['all_feeds']['total']?>
    repartition['main_stream']['read']?>repartition['all_feeds']['read']?>
    repartition['main_stream']['unread']?>repartition['all_feeds']['unread']?>
    repartition['main_stream']['favorite']?>repartition['all_feeds']['favorite']?>
    +
    + +
    +

    +
    +
    + +
    +

    +
    +
    +
    + +
    +

    +
    +
    +
    + +
    + + \ No newline at end of file diff --git a/p/scripts/flotr2.min.js b/p/scripts/flotr2.min.js new file mode 100644 index 000000000..c3601e827 --- /dev/null +++ b/p/scripts/flotr2.min.js @@ -0,0 +1,27 @@ +/*! + * bean.js - copyright Jacob Thornton 2011 + * https://github.com/fat/bean + * MIT License + * special thanks to: + * dean edwards: http://dean.edwards.name/ + * dperini: https://github.com/dperini/nwevents + * the entire mootools team: github.com/mootools/mootools-core + *//*global module:true, define:true*/ +!function(e,t,n){typeof module!="undefined"?module.exports=n(e,t):typeof define=="function"&&typeof define.amd=="object"?define(n):t[e]=n(e,t)}("bean",this,function(e,t){var n=window,r=t[e],i=/over|out/,s=/[^\.]*(?=\..*)\.|.*/,o=/\..*/,u="addEventListener",a="attachEvent",f="removeEventListener",l="detachEvent",c=document||{},h=c.documentElement||{},p=h[u],d=p?u:a,v=Array.prototype.slice,m=/click|mouse|menu|drag|drop/i,g=/^touch|^gesture/i,y={one:1},b=function(e,t,n){for(n=0;n0){t=t.split(" ");for(f=t.length;f--;)_(e,t[f],n);return e}u=c&&t.replace(o,""),u&&w[u]&&(u=w[u].type);if(!t||c){if(a=c&&t.replace(s,""))a=a.split(".");l(e,u,n,a)}else if(typeof t=="function")l(e,null,t);else for(r in t)t.hasOwnProperty(r)&&_(e,r,t[r]);return e},D=function(e,t,n,r,i){var s,o,u,a,f=n,l=n&&typeof n=="string";if(t&&!n&&typeof t=="object")for(s in t)t.hasOwnProperty(s)&&D.apply(this,[e,s,t[s]]);else{a=arguments.length>3?v.call(arguments,3):[],o=(l?n:t).split(" "),l&&(n=M(t,f=r,i))&&(a=v.call(a,1)),this===y&&(n=L(_,e,t,n,f));for(u=o.length;u--;)O(e,o[u],n,f,a)}return e},P=function(){return D.apply(y,arguments)},H=p?function(e,t,r){var i=c.createEvent(e?"HTMLEvents":"UIEvents");i[e?"initEvent":"initUIEvent"](t,!0,!0,n,1),r.dispatchEvent(i)}:function(e,t,n){n=S(n,e),e?n.fireEvent("on"+t,c.createEventObject()):n["_on"+t]++},B=function(e,t,n){var r,i,u,a,f,l=t.split(" ");for(r=l.length;r--;){t=l[r].replace(o,"");if(a=l[r].replace(s,""))a=a.split(".");if(!a&&!n&&e[d])H(b[t],t,e);else{f=T.get(e,t),n=[!1].concat(n);for(i=0,u=f.length;i=r.computed&&(r={value:e,computed:o})}),r.value},S.min=function(e,t,n){if(!t&&S.isArray(e))return Math.min.apply(Math,e);var r={computed:Infinity};return x(e,function(e,i,s){var o=t?t.call(n,e,i,s):e;or?1:0}),"value")},S.groupBy=function(e,t){var n={};return x(e,function(e,r){var i=t(e,r);(n[i]||(n[i]=[])).push(e)}),n},S.sortedIndex=function(e,t,n){n||(n=S.identity);var r=0,i=e.length;while(r>1;n(e[s])=0})})},S.difference=function(e,t){return S.filter(e,function(e){return!S.include(t,e)})},S.zip=function(){var e=o.call(arguments),t=S.max(S.pluck(e,"length")),n=new Array(t);for(var r=0;r=0;n--)t=[e[n].apply(this,t)];return t[0]}},S.after=function(e,t){return function(){if(--e<1)return t.apply(this,arguments)}},S.keys=w||function(e){if(e!==Object(e))throw new TypeError("Invalid object");var t=[];for(var n in e)f.call(e,n)&&(t[t.length]=n);return t},S.values=function(e){return S.map(e,S.identity)},S.functions=S.methods=function(e){var t=[];for(var n in e)S.isFunction(e[n])&&t.push(n);return t.sort()},S.extend=function(e){return x(o.call(arguments,1),function(t){for(var n in t)t[n]!==void 0&&(e[n]=t[n])}),e},S.defaults=function(e){return x(o.call(arguments,1),function(t){for(var n in t)e[n]==null&&(e[n]=t[n])}),e},S.clone=function(e){return S.isArray(e)?e.slice():S.extend({},e)},S.tap=function(e,t){return t(e),e},S.isEqual=function(e,t){if(e===t)return!0;var n=typeof e,r=typeof t;if(n!=r)return!1;if(e==t)return!0;if(!e&&t||e&&!t)return!1;e._chain&&(e=e._wrapped),t._chain&&(t=t._wrapped);if(e.isEqual)return e.isEqual(t);if(t.isEqual)return t.isEqual(e);if(S.isDate(e)&&S.isDate(t))return e.getTime()===t.getTime();if(S.isNaN(e)&&S.isNaN(t))return!1;if(S.isRegExp(e)&&S.isRegExp(t))return e.source===t.source&&e.global===t.global&&e.ignoreCase===t.ignoreCase&&e.multiline===t.multiline;if(n!=="object")return!1;if(e.length&&e.length!==t.length)return!1;var i=S.keys(e),s=S.keys(t);if(i.length!=s.length)return!1;for(var o in e)if(!(o in t)||!S.isEqual(e[o],t[o]))return!1;return!0},S.isEmpty=function(e){if(S.isArray(e)||S.isString(e))return e.length===0;for(var t in e)if(f.call(e,t))return!1;return!0},S.isElement=function(e){return!!e&&e.nodeType==1},S.isArray=b||function(e){return a.call(e)==="[object Array]"},S.isObject=function(e){return e===Object(e)},S.isArguments=function(e){return!!e&&!!f.call(e,"callee")},S.isFunction=function(e){return!!(e&&e.constructor&&e.call&&e.apply)},S.isString=function(e){return!!(e===""||e&&e.charCodeAt&&e.substr)},S.isNumber=function(e){return!!(e===0||e&&e.toExponential&&e.toFixed)},S.isNaN=function(e){return e!==e},S.isBoolean=function(e){return e===!0||e===!1},S.isDate=function(e){return!!(e&&e.getTimezoneOffset&&e.setUTCFullYear)},S.isRegExp=function(e){return!(!(e&&e.test&&e.exec)||!e.ignoreCase&&e.ignoreCase!==!1)},S.isNull=function(e){return e===null},S.isUndefined=function(e){return e===void 0},S.noConflict=function(){return e._=t,this},S.identity=function(e){return e},S.times=function(e,t,n){for(var r=0;r/g,interpolate:/<%=([\s\S]+?)%>/g},S.template=function(e,t){var n=S.templateSettings,r="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+e.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(n.interpolate,function(e,t){return"',"+t.replace(/\\'/g,"'")+",'"}).replace(n.evaluate||null,function(e,t){return"');"+t.replace(/\\'/g,"'").replace(/[\r\n\t]/g," ")+"__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",i=new Function("obj",r);return t?i(t):i};var k=function(e){this._wrapped=e};S.prototype=k.prototype;var L=function(e,t){return t?S(e).chain():e},A=function(e,t){k.prototype[e]=function(){var e=o.call(arguments);return u.call(e,this._wrapped),L(t.apply(S,e),this._chain)}};S.mixin(S),x(["pop","push","reverse","shift","sort","splice","unshift"],function(e){var t=r[e];k.prototype[e]=function(){return t.apply(this._wrapped,arguments),L(this._wrapped,this._chain)}}),x(["concat","join","slice"],function(e){var t=r[e];k.prototype[e]=function(){return L(t.apply(this._wrapped,arguments),this._chain)}}),k.prototype.chain=function(){return this._chain=!0,this},k.prototype.value=function(){return this._wrapped}})(); +/** + * Flotr2 (c) 2012 Carl Sutherland + * MIT License + * Special thanks to: + * Flotr: http://code.google.com/p/flotr/ (fork) + * Flot: https://github.com/flot/flot (original fork) + */ +(function(){var e=this,t=this.Flotr,n;n={_:_,bean:bean,isIphone:/iphone/i.test(navigator.userAgent),isIE:navigator.appVersion.indexOf("MSIE")!=-1?parseFloat(navigator.appVersion.split("MSIE")[1]):!1,graphTypes:{},plugins:{},addType:function(e,t){n.graphTypes[e]=t,n.defaultOptions[e]=t.options||{},n.defaultOptions.defaultType=n.defaultOptions.defaultType||e},addPlugin:function(e,t){n.plugins[e]=t,n.defaultOptions[e]=t.options||{}},draw:function(e,t,r,i){return i=i||n.Graph,new i(e,t,r)},merge:function(e,t){var r,i,s=t||{};for(r in e)i=e[r],i&&typeof i=="object"?i.constructor===Array?s[r]=this._.clone(i):i.constructor!==RegExp&&!this._.isElement(i)&&!i.jquery?s[r]=n.merge(i,t?t[r]:undefined):s[r]=i:s[r]=i;return s},clone:function(e){return n.merge(e,{})},getTickSize:function(e,t,r,i){var s=(r-t)/e,o=n.getMagnitude(s),u=10,a=s/o;return a<1.5?u=1:a<2.25?u=2:a<3?u=i===0?2:2.5:a<7.5&&(u=5),u*o},defaultTickFormatter:function(e,t){return e+""},defaultTrackFormatter:function(e){return"("+e.x+", "+e.y+")"},engineeringNotation:function(e,t,n){var r=["Y","Z","E","P","T","G","M","k",""],i=["y","z","a","f","p","n","µ","m",""],s=r.length;n=n||1e3,t=Math.pow(10,t||2);if(e===0)return 0;if(e>1)while(s--&&e>=n)e/=n;else{r=i,s=r.length;while(s--&&e<1)e*=n}return Math.round(e*t)/t+r[s]},getMagnitude:function(e){return Math.pow(10,Math.floor(Math.log(e)/Math.LN10))},toPixel:function(e){return Math.floor(e)+.5},toRad:function(e){return-e*(Math.PI/180)},floorInBase:function(e,t){return t*Math.floor(e/t)},drawText:function(e,t,r,i,s){if(!e.fillText){e.drawText(t,r,i,s);return}s=this._.extend({size:n.defaultOptions.fontSize,color:"#000000",textAlign:"left",textBaseline:"bottom",weight:1,angle:0},s),e.save(),e.translate(r,i),e.rotate(s.angle),e.fillStyle=s.color,e.font=(s.weight>1?"bold ":"")+s.size*1.3+"px sans-serif",e.textAlign=s.textAlign,e.textBaseline=s.textBaseline,e.fillText(t,0,0),e.restore()},getBestTextAlign:function(e,t){return t=t||{textAlign:"center",textBaseline:"middle"},e+=n.getTextAngleFromAlign(t),Math.abs(Math.cos(e))>.01&&(t.textAlign=Math.cos(e)>0?"right":"left"),Math.abs(Math.sin(e))>.01&&(t.textBaseline=Math.sin(e)>0?"top":"bottom"),t},alignTable:{"right middle":0,"right top":Math.PI/4,"center top":Math.PI/2,"left top":3*(Math.PI/4),"left middle":Math.PI,"left bottom":-3*(Math.PI/4),"center bottom":-Math.PI/2,"right bottom":-Math.PI/4,"center middle":0},getTextAngleFromAlign:function(e){return n.alignTable[e.textAlign+" "+e.textBaseline]||0},noConflict:function(){return e.Flotr=t,this}},e.Flotr=n})(),Flotr.defaultOptions={colors:["#00A8F0","#C0D800","#CB4B4B","#4DA74D","#9440ED"],ieBackgroundColor:"#FFFFFF",title:null,subtitle:null,shadowSize:4,defaultType:null,HtmlText:!0,fontColor:"#545454",fontSize:7.5,resolution:1,parseFloat:!0,preventDefault:!0,xaxis:{ticks:null,minorTicks:null,showLabels:!0,showMinorLabels:!1,labelsAngle:0,title:null,titleAngle:0,noTicks:5,minorTickFreq:null,tickFormatter:Flotr.defaultTickFormatter,tickDecimals:null,min:null,max:null,autoscale:!1,autoscaleMargin:0,color:null,mode:"normal",timeFormat:null,timeMode:"UTC",timeUnit:"millisecond",scaling:"linear",base:Math.E,titleAlign:"center",margin:!0},x2axis:{},yaxis:{ticks:null,minorTicks:null,showLabels:!0,showMinorLabels:!1,labelsAngle:0,title:null,titleAngle:90,noTicks:5,minorTickFreq:null,tickFormatter:Flotr.defaultTickFormatter,tickDecimals:null,min:null,max:null,autoscale:!1,autoscaleMargin:0,color:null,scaling:"linear",base:Math.E,titleAlign:"center",margin:!0},y2axis:{titleAngle:270},grid:{color:"#545454",backgroundColor:null,backgroundImage:null,watermarkAlpha:.4,tickColor:"#DDDDDD",labelMargin:3,verticalLines:!0,minorVerticalLines:null,horizontalLines:!0,minorHorizontalLines:null,outlineWidth:1,outline:"nsew",circular:!1},mouse:{track:!1,trackAll:!1,position:"se",relative:!1,trackFormatter:Flotr.defaultTrackFormatter,margin:5,lineColor:"#FF3F19",trackDecimals:1,sensibility:2,trackY:!0,radius:3,fillColor:null,fillOpacity:.4}},function(){function t(e,t,n,r){this.rgba=["r","g","b","a"];var i=4;while(-1<--i)this[this.rgba[i]]=arguments[i]||(i==3?1:0);this.normalize()}var e=Flotr._,n={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]};t.prototype={scale:function(t,n,r,i){var s=4;while(-1<--s)e.isUndefined(arguments[s])||(this[this.rgba[s]]*=arguments[s]);return this.normalize()},alpha:function(t){return!e.isUndefined(t)&&!e.isNull(t)&&(this.a=t),this.normalize()},clone:function(){return new t(this.r,this.b,this.g,this.a)},limit:function(e,t,n){return Math.max(Math.min(e,n),t)},normalize:function(){var e=this.limit;return this.r=e(parseInt(this.r,10),0,255),this.g=e(parseInt(this.g,10),0,255),this.b=e(parseInt(this.b,10),0,255),this.a=e(this.a,0,1),this},distance:function(e){if(!e)return;e=new t.parse(e);var n=0,r=3;while(-1<--r)n+=Math.abs(this[this.rgba[r]]-e[this.rgba[r]]);return n},toString:function(){return this.a>=1?"rgb("+[this.r,this.g,this.b].join(",")+")":"rgba("+[this.r,this.g,this.b,this.a].join(",")+")"},contrast:function(){var e=1-(.299*this.r+.587*this.g+.114*this.b)/255;return e<.5?"#000000":"#ffffff"}},e.extend(t,{parse:function(e){if(e instanceof t)return e;var r;if(r=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(e))return new t(parseInt(r[1],16),parseInt(r[2],16),parseInt(r[3],16));if(r=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(e))return new t(parseInt(r[1],10),parseInt(r[2],10),parseInt(r[3],10));if(r=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(e))return new t(parseInt(r[1]+r[1],16),parseInt(r[2]+r[2],16),parseInt(r[3]+r[3],16));if(r=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(e))return new t(parseInt(r[1],10),parseInt(r[2],10),parseInt(r[3],10),parseFloat(r[4]));if(r=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(e))return new t(parseFloat(r[1])*2.55,parseFloat(r[2])*2.55,parseFloat(r[3])*2.55);if(r=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(e))return new t(parseFloat(r[1])*2.55,parseFloat(r[2])*2.55,parseFloat(r[3])*2.55,parseFloat(r[4]));var i=(e+"").replace(/^\s*([\S\s]*?)\s*$/,"$1").toLowerCase();return i=="transparent"?new t(255,255,255,0):(r=n[i])?new t(r[0],r[1],r[2]):new t(0,0,0,0)},processColor:function(n,r){var i=r.opacity;if(!n)return"rgba(0, 0, 0, 0)";if(n instanceof t)return n.alpha(i).toString();if(e.isString(n))return t.parse(n).alpha(i).toString();var s=n.colors?n:{colors:n};if(!r.ctx)return e.isArray(s.colors)?t.parse(e.isArray(s.colors[0])?s.colors[0][1]:s.colors[0]).alpha(i).toString():"rgba(0, 0, 0, 0)";s=e.extend({start:"top",end:"bottom"},s),/top/i.test(s.start)&&(r.x1=0),/left/i.test(s.start)&&(r.y1=0),/bottom/i.test(s.end)&&(r.x2=0),/right/i.test(s.end)&&(r.y2=0);var o,u,a,f=r.ctx.createLinearGradient(r.x1,r.y1,r.x2,r.y2);for(o=0;o=h)break}h=i[v][0],p=i[v][1],p=="year"&&(h=Flotr.getTickSize(s.noTicks*r.year,a,f,0),h==.5&&(p="month",h=6)),e.tickUnit=p,e.tickSize=h;var g=h*r[p];m=new Date(a);switch(p){case"millisecond":y("Milliseconds");break;case"second":y("Seconds");break;case"minute":y("Minutes");break;case"hour":y("Hours");break;case"month":y("Month");break;case"year":y("FullYear")}g>=r.second&&t(m,"Milliseconds",o,0),g>=r.minute&&t(m,"Seconds",o,0),g>=r.hour&&t(m,"Minutes",o,0),g>=r.day&&t(m,"Hours",o,0),g>=r.day*4&&t(m,"Date",o,1),g>=r.year&&t(m,"Month",o,0);var b=0,w=NaN,E;do{E=w,w=m.getTime(),c.push({v:w/u,label:d(w/u,e)});if(p=="month")if(h<1){t(m,"Date",o,1);var S=m.getTime();t(m,"Month",o,n(m,"Month",o)+1);var x=m.getTime();m.setTime(w+b*r.hour+(x-S)*h),b=n(m,"Hours",o),t(m,"Hours",o,0)}else t(m,"Month",o,n(m,"Month",o)+h);else p=="year"?t(m,"FullYear",o,n(m,"FullYear",o)+h):m.setTime(w+g)}while(w0)return{x:t.touches[0].pageX,y:t.touches[0].pageY};if(!e._.isUndefined(t.changedTouches)&&t.changedTouches.length>0)return{x:t.changedTouches[0].pageX,y:t.changedTouches[0].pageY};if(t.pageX||t.pageY)return{x:t.pageX,y:t.pageY};if(t.clientX||t.clientY){var n=document,r=n.body,i=n.documentElement;return{x:t.clientX+r.scrollLeft+i.scrollLeft,y:t.clientY+r.scrollTop+i.scrollTop}}}}}(),function(){var e=Flotr,t=e.DOM,n=e._,r=function(e){this.o=e};r.prototype={dimensions:function(e,t,n,r){return e?this.o.html?this.html(e,this.o.element,n,r):this.canvas(e,t):{width:0,height:0}},canvas:function(t,n){if(!this.o.textEnabled)return;n=n||{};var r=this.measureText(t,n),i=r.width,s=n.size||e.defaultOptions.fontSize,o=n.angle||0,u=Math.cos(o),a=Math.sin(o),f=2,l=6,c;return c={width:Math.abs(u*i)+Math.abs(a*s)+f,height:Math.abs(a*i)+Math.abs(u*s)+l},c},html:function(e,n,r,i){var s=t.create("div");return t.setStyles(s,{position:"absolute",top:"-10000px"}),t.insert(s,'
    '+e+"
    "),t.insert(this.o.element,s),t.size(s)},measureText:function(t,r){var i=this.o.ctx,s;return!i.fillText||e.isIphone&&i.measure?{width:i.measure(t,r)}:(r=n.extend({size:e.defaultOptions.fontSize,weight:1,angle:0},r),i.save(),i.font=(r.weight>1?"bold ":"")+r.size*1.3+"px sans-serif",s=i.measureText(t),i.restore(),s)}},Flotr.Text=r}(),function(){function i(e,n,r){return t.observe.apply(this,arguments),this._handles.push(arguments),this}var e=Flotr.DOM,t=Flotr.EventAdapter,n=Flotr._,r=Flotr;Graph=function(e,i,s){this._setEl(e),this._initMembers(),this._initPlugins(),t.fire(this.el,"flotr:beforeinit",[this]),this.data=i,this.series=r.Series.getSeries(i),this._initOptions(s),this._initGraphTypes(),this._initCanvas(),this._text=new r.Text({element:this.el,ctx:this.ctx,html:this.options.HtmlText,textEnabled:this.textEnabled}),t.fire(this.el,"flotr:afterconstruct",[this]),this._initEvents(),this.findDataRanges(),this.calculateSpacing(),this.draw(n.bind(function(){t.fire(this.el,"flotr:afterinit",[this])},this))},Graph.prototype={destroy:function(){t.fire(this.el,"flotr:destroy"),n.each(this._handles,function(e){t.stopObserving.apply(this,e)}),this._handles=[],this.el.graph=null},observe:i,_observe:i,processColor:function(e,t){var i={x1:0,y1:0,x2:this.plotWidth,y2:this.plotHeight,opacity:1,ctx:this.ctx};return n.extend(i,t),r.Color.processColor(e,i)},findDataRanges:function(){var e=this.axes,t,i,s;n.each(this.series,function(e){s=e.getRange(),s&&(t=e.xaxis,i=e.yaxis,t.datamin=Math.min(s.xmin,t.datamin),t.datamax=Math.max(s.xmax,t.datamax),i.datamin=Math.min(s.ymin,i.datamin),i.datamax=Math.max(s.ymax,i.datamax),t.used=t.used||s.xused,i.used=i.used||s.yused)},this),!e.x.used&&!e.x2.used&&(e.x.used=!0),!e.y.used&&!e.y2.used&&(e.y.used=!0),n.each(e,function(e){e.calculateRange()});var o=n.keys(r.graphTypes),u=!1;n.each(this.series,function(e){if(e.hide)return;n.each(o,function(t){e[t]&&e[t].show&&(this.extendRange(t,e),u=!0)},this),u||this.extendRange(this.options.defaultType,e)},this)},extendRange:function(e,t){this[e].extendRange&&this[e].extendRange(t,t.data,t[e],this[e]),this[e].extendYRange&&this[e].extendYRange(t.yaxis,t.data,t[e],this[e]),this[e].extendXRange&&this[e].extendXRange(t.xaxis,t.data,t[e],this[e])},calculateSpacing:function(){var e=this.axes,t=this.options,r=this.series,i=t.grid.labelMargin,s=this._text,o=e.x,u=e.x2,a=e.y,f=e.y2,l=t.grid.outlineWidth,c,h,p,d;n.each(e,function(e){e.calculateTicks(),e.calculateTextDimensions(s,t)}),d=s.dimensions(t.title,{size:t.fontSize*1.5},"font-size:1em;font-weight:bold;","flotr-title"),this.titleHeight=d.height,d=s.dimensions(t.subtitle,{size:t.fontSize},"font-size:smaller;","flotr-subtitle"),this.subtitleHeight=d.height;for(h=0;h1&&(this.multitouches=n.touches),t.fire(e,"flotr:mousedown",[event,this]),this.observe(document,"touchend",r)},this)),this.observe(this.overlay,"touchmove",n.bind(function(n){var r=this.getEventPosition(n);this.options.preventDefault&&n.preventDefault(),i=!0,this.multitouches||n.touches&&n.touches.length>1?this.multitouches=n.touches:s||t.fire(e,"flotr:mousemove",[event,r,this]),this.lastMousePos=r},this))):this.observe(this.overlay,"mousedown",n.bind(this.mouseDownHandler,this)).observe(e,"mousemove",n.bind(this.mouseMoveHandler,this)).observe(this.overlay,"click",n.bind(this.clickHandler,this)).observe(e,"mouseout",function(n){t.fire(e,"flotr:mouseout",n)})},_initCanvas:function(){function l(i,s){return i||(i=e.create("canvas"),typeof FlashCanvas!="undefined"&&typeof i.getContext=="function"&&(FlashCanvas.initElement(i),this.isFlashCanvas=!0),i.className="flotr-"+s,i.style.cssText="position:absolute;left:0px;top:0px;",e.insert(t,i)),n.each(a,function(t,n){e.show(i);if(s=="canvas"&&i.getAttribute(n)===t)return;i.setAttribute(n,t*r.resolution),i.style[n]=t+"px"}),i.context_=null,i}function c(e){window.G_vmlCanvasManager&&window.G_vmlCanvasManager.initElement(e);var t=e.getContext("2d");return window.G_vmlCanvasManager||t.scale(r.resolution,r.resolution),t}var t=this.el,r=this.options,i=t.children,s=[],o,u,a,f;for(u=i.length;u--;)o=i[u],!this.canvas&&o.className==="flotr-canvas"?this.canvas=o:!this.overlay&&o.className==="flotr-overlay"?this.overlay=o:s.push(o);for(u=s.length;u--;)t.removeChild(s[u]);e.setStyles(t,{position:"relative"}),a={},a.width=t.clientWidth,a.height=t.clientHeight;if(a.width<=0||a.height<=0||r.resolution<=0)throw"Invalid dimensions for plot, width = "+a.width+", height = "+a.height+", resolution = "+r.resolution;this.canvas=l(this.canvas,"canvas"),this.overlay=l(this.overlay,"overlay"),this.ctx=c(this.canvas),this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height),this.octx=c(this.overlay),this.octx.clearRect(0,0,this.overlay.width,this.overlay.height),this.canvasHeight=a.height,this.canvasWidth=a.width,this.textEnabled=!!this.ctx.drawText||!!this.ctx.fillText},_initPlugins:function(){n.each(r.plugins,function(e,t){n.each(e.callbacks,function(e,t){this.observe(this.el,t,n.bind(e,this))},this),this[t]=r.clone(e),n.each(this[t],function(e,r){n.isFunction(e)&&(this[t][r]=n.bind(e,this))},this)},this)},_initOptions:function(e){var i=r.clone(r.defaultOptions);i.x2axis=n.extend(n.clone(i.xaxis),i.x2axis),i.y2axis=n.extend(n.clone(i.yaxis),i.y2axis),this.options=r.merge(e||{},i),this.options.grid.minorVerticalLines===null&&this.options.xaxis.scaling==="logarithmic"&&(this.options.grid.minorVerticalLines=!0),this.options.grid.minorHorizontalLines===null&&this.options.yaxis.scaling==="logarithmic"&&(this.options.grid.minorHorizontalLines=!0),t.fire(this.el,"flotr:afterinitoptions",[this]),this.axes=r.Axis.getAxes(this.options);var s=[],o=[],u=this.series.length,a=this.series.length,f=this.options.colors,l=[],c=0,h,p,d,v;for(p=a-1;p>-1;--p)h=this.series[p].color,h&&(--a,n.isNumber(h)?s.push(h):l.push(r.Color.parse(h)));for(p=s.length-1;p>-1;--p)a=Math.max(a,s[p]+1);for(p=0;o.length=f.length&&(p=0,++c)}for(p=0,d=0;p10?t.minorTickFreq=0:o-u>5?t.minorTickFreq=2:t.minorTickFreq=5)}else e.tickSize=Flotr.getTickSize(t.noTicks,n,r,t.tickDecimals);e.min=n,e.max=r,t.min===null&&t.autoscale&&(e.min-=e.tickSize*i,e.min<0&&e.datamin>=0&&(e.min=0),e.min=e.tickSize*Math.floor(e.min/e.tickSize)),t.max===null&&t.autoscale&&(e.max+=e.tickSize*i,e.max>0&&e.datamax<=0&&e.datamax!=e.datamin&&(e.max=0),e.max=e.tickSize*Math.ceil(e.max/e.tickSize)),e.min==e.max&&(e.max=e.min+1)},calculateTextDimensions:function(e,t){var n="",r,i;if(this.options.showLabels)for(i=0;in.length&&(n=this.ticks[i].label);this.maxLabel=e.dimensions(n,{size:t.fontSize,angle:Flotr.toRad(this.options.labelsAngle)},"font-size:smaller;","flotr-grid-label"),this.titleSize=e.dimensions(this.options.title,{size:t.fontSize*1.2,angle:Flotr.toRad(this.options.titleAngle)},"font-weight:bold;","flotr-axis-title")},_cleanUserTicks:function(t,n){var r=this,i=this.options,s,o,u,a;e.isFunction(t)&&(t=t({min:r.min,max:r.max}));for(o=0;o1?a[1]:i.tickFormatter(s,{min:r.min,max:r.max})):(s=a,u=i.tickFormatter(s,{min:this.min,max:this.max})),n[o]={v:s,label:u}},_calculateTimeTicks:function(){this.ticks=Flotr.Date.generator(this)},_calculateLogTicks:function(){var e=this,t=e.options,n,r,s=Math.log(e.max);t.base!=Math.E&&(s/=Math.log(t.base)),s=Math.ceil(s);var o=Math.log(e.min);t.base!=Math.E&&(o/=Math.log(t.base)),o=Math.ceil(o);for(i=o;ii&&(i=a,o=!0)),f!==null&&(fs&&(s=f,u=!0));return{xmin:n,xmax:i,ymin:r,ymax:s,xused:o,yused:u}}},e.extend(t,{getSeries:function(n){return e.map(n,function(n){var r;return n.data?(r=new t,e.extend(r,n)):r=new t({data:n}),r})}}),Flotr.Series=t}(),Flotr.addType("lines",{options:{show:!1,lineWidth:2,fill:!1,fillBorder:!1,fillColor:null,fillOpacity:.4,steps:!1,stacked:!1},stack:{values:[]},draw:function(e){var t=e.context,n=e.lineWidth,r=e.shadowSize,i;t.save(),t.lineJoin="round",r&&(t.lineWidth=r/2,i=n/2+t.lineWidth/2,t.strokeStyle="rgba(0,0,0,0.1)",this.plot(e,i+r/2,!1),t.strokeStyle="rgba(0,0,0,0.2)",this.plot(e,i,!1)),t.lineWidth=n,t.strokeStyle=e.color,this.plot(e,0,!0),t.restore()},plot:function(e,t,n){function S(){!t&&e.fill&&d&&(v=o(d[0]),r.fillStyle=e.fillStyle,r.lineTo(m,p),r.lineTo(v,p),r.lineTo(v,u(d[1])),r.fill(),e.fillBorder&&r.stroke())}var r=e.context,i=e.width,s=e.height,o=e.xScale,u=e.yScale,a=e.data,f=e.stacked?this.stack:!1,l=a.length-1,c=null,h=null,p=u(0),d=null,v,m,g,y,b,w,E;if(l<1)return;r.beginPath();for(E=0;E0&&a[E][1]!==null&&(r.stroke(),S(),d=null,r.closePath(),r.beginPath());continue}v=o(a[E][0]),m=o(a[E+1][0]),d===null&&(d=a[E]),f?(b=f.values[a[E][0]]||0,w=f.values[a[E+1][0]]||f.values[a[E][0]]||0,g=u(a[E][1]+b),y=u(a[E+1][1]+w),n&&(a[E].y0=b,f.values[a[E][0]]=a[E][1]+b,E==l-1&&(a[E+1].y0=w,f.values[a[E+1][0]]=a[E+1][1]+w))):(g=u(a[E][1]),y=u(a[E+1][1]));if(g>s&&y>s||g<0&&y<0||v<0&&m<0||v>i&&m>i)continue;(c!=v||h!=g+t)&&r.moveTo(v,g+t),c=m,h=y+t,e.steps?(r.lineTo(c+t/2,g+t),r.lineTo(c+t/2,h)):r.lineTo(c,h)}(!e.fill||e.fill&&!e.fillBorder)&&r.stroke(),S(),r.closePath()},extendYRange:function(e,t,n,r){var i=e.options;if(n.stacked&&(!i.max&&i.max!==0||!i.min&&i.min!==0)){var s=e.max,o=e.min,u=r.positiveSums||{},a=r.negativeSums||{},f,l;for(l=0;l0?(u[f]=(u[f]||0)+t[l][1],s=Math.max(s,u[f])):(a[f]=(a[f]||0)+t[l][1],o=Math.min(o,a[f]));r.negativeSums=a,r.positiveSums=u,e.max=s,e.min=o}n.steps&&(this.hit=function(e){var t=e.data,n=e.args,r=e.yScale,i=n[0],s=t.length,o=n[1],u=e.xInverse(i.relX),a=i.relY,f;for(f=0;f=t[f][0]&&u<=t[f+1][0]){Math.abs(r(t[f][1])-a)<8&&(o.x=t[f][0],o.y=t[f][1],o.index=f,o.seriesIndex=e.index);break}},this.drawHit=function(e){var t=e.context,n=e.args,r=e.data,i=e.xScale,s=n.index,o=i(n.x),u=e.yScale(n.y),a;r.length-1>s&&(a=e.xScale(r[s+1][0]),t.save(),t.strokeStyle=e.color,t.lineWidth=e.lineWidth,t.beginPath(),t.moveTo(o,u),t.lineTo(a,u),t.stroke(),t.closePath(),t.restore())},this.clearHit=function(e){var t=e.context,n=e.args,r=e.data,i=e.xScale,s=e.lineWidth,o=n.index,u=i(n.x),a=e.yScale(n.y),f;r.length-1>o&&(f=e.xScale(r[o+1][0]),t.clearRect(u-s,a-s,f-u+2*s,2*s))})}}),Flotr.addType("bars",{options:{show:!1,lineWidth:2,barWidth:1,fill:!0,fillColor:null,fillOpacity:.4,horizontal:!1,stacked:!1,centered:!0,topPadding:.1,grouped:!1},stack:{positive:[],negative:[],_positive:[],_negative:[]},draw:function(e){var t=e.context;this.current+=1,t.save(),t.lineJoin="miter",t.lineWidth=e.lineWidth,t.strokeStyle=e.color,e.fill&&(t.fillStyle=e.fillStyle),this.plot(e),t.restore()},plot:function(e){var t=e.data,n=e.context,r=e.shadowSize,i,s,o,u,a,f;if(t.length<1)return;this.translate(n,e.horizontal);for(i=0;i0?o.positive:o.negative,p=d[c]||p,d[c]=p+h),v=f(c-a),m=f(c+i-a),g=l(h+p),y=l(p),y<0&&(y=0),e===null||t===null?null:{x:c,y:h,xScale:f,yScale:l,top:g,left:Math.min(v,m)-u/2,width:Math.abs(m-v)-u,height:y-g}},hit:function(e){var t=e.data,n=e.args,r=n[0],i=n[1],s=e.xInverse(r.relX),o=e.yInverse(r.relY),u=this.getBarGeometry(s,o,e),a=u.width/2,f=u.left,l=u.y,c,h;for(h=t.length;h--;)c=this.getBarGeometry(t[h][0],t[h][1],e),(l>0&&lc.y)&&Math.abs(f-c.left)0?(f[c]=(f[c]||0)+h,o=Math.max(o,f[c])):(l[c]=(l[c]||0)+h,s=Math.min(s,l[c]));(a==1&&u||a==-1&&!u)&&n.topPadding&&(e.max===e.datamax||n.stacked&&this.stackMax!==o)&&(o+=n.topPadding*(o-s)),this.stackMin=s,this.stackMax=o,this.negativeSums=l,this.positiveSums=f,e.max=o,e.min=s}}),Flotr.addType("bubbles",{options:{show:!1,lineWidth:2,fill:!0,fillOpacity:.4,baseRadius:2},draw:function(e){var t=e.context,n=e.shadowSize;t.save(),t.lineWidth=e.lineWidth,t.fillStyle="rgba(0,0,0,0.05)",t.strokeStyle="rgba(0,0,0,0.05)",this.plot(e,n/2),t.strokeStyle="rgba(0,0,0,0.1)",this.plot(e,n/4),t.strokeStyle=e.color,t.fillStyle=e.fillStyle,this.plot(e),t.restore()},plot:function(e,t){var n=e.data,r=e.context,i,s,o,u,a;t=t||0;for(s=0;sg?"downFillColor":"upFillColor"],e.fill&&!e.barcharts&&(n.fillStyle="rgba(0,0,0,0.05)",n.fillRect(y+o,x+o,b-y,S-x),n.save(),n.globalAlpha=e.fillOpacity,n.fillStyle=l,n.fillRect(y,x+u,b-y,S-x),n.restore());if(u||a)h=Math.floor((y+b)/2)+f,n.strokeStyle=l,n.beginPath(),e.barcharts?(n.moveTo(h,Math.floor(E+u)),n.lineTo(h,Math.floor(w+u)),T=dv&&ag&&fs.max||yo.max)continue;vs.max&&(m=s.max,s.lastSerie!=e&&(p=!1)),go.max&&(y=o.max,o.lastSerie!=e&&(p=!1)),r&&(u.beginPath(),u.moveTo(s.d2p(v),o.d2p(g)+n),u.lineTo(s.d2p(v),o.d2p(y)+n),u.lineTo(s.d2p(m),o.d2p(y)+n),u.lineTo(s.d2p(m),o.d2p(g)+n),u.fill(),u.closePath()),e.gantt.lineWidth&&(h||d||p)&&(u.beginPath(),u.moveTo(s.d2p(v),o.d2p(g)+n),u[h?"lineTo":"moveTo"](s.d2p(v),o.d2p(y)+n),u[p?"lineTo":"moveTo"](s.d2p(m),o.d2p(y)+n),u[d?"lineTo":"moveTo"](s.d2p(m),o.d2p(g)+n),u.stroke(),u.closePath())}},plotShadows:function(e,t,n){var r=e.data;if(r.length<1)return;var i,s,o,u,a=e.xaxis,f=e.yaxis,l=this.ctx,c=this.options.shadowSize;for(i=0;ia.max||vf.max)continue;ha.max&&(p=a.max),df.max&&(v=f.max);var m=a.d2p(p)-a.d2p(h)-(a.d2p(p)+c<=this.plotWidth?0:c),g=f.d2p(d)-f.d2p(v)-(f.d2p(d)+c<=this.plotHeight?0:c);l.fillStyle="rgba(0,0,0,0.05)",l.fillRect(Math.min(a.d2p(h)+c,this.plotWidth),Math.min(f.d2p(v)+c,this.plotHeight),m,g)}},extendXRange:function(e){if(e.options.max===null){var t=e.min,n=e.max,r,i,s,o,u,a={},f={},l=null;for(r=0;rt&&(t=e.max+o.barWidth)}}e.lastSerie=f,e.max=t,e.min=n,e.tickSize=Flotr.getTickSize(e.options.noTicks,n,t,e.options.tickDecimals)}}}),function(){function e(e){return typeof e=="object"&&e.constructor&&(Image?!0:e.constructor===Image)}Flotr.defaultMarkerFormatter=function(e){return Math.round(e.y*100)/100+""},Flotr.addType("markers",{options:{show:!1,lineWidth:1,color:"#000000",fill:!1,fillColor:"#FFFFFF",fillOpacity:.4,stroke:!1,position:"ct",verticalMargin:0,labelFormatter:Flotr.defaultMarkerFormatter,fontSize:Flotr.defaultOptions.fontSize,stacked:!1,stackingType:"b",horizontal:!1},stack:{positive:[],negative:[],values:[]},draw:function(e){function h(e,t){return o=r.negative[e]||0,s=r.positive[e]||0,t>0?(r.positive[e]=o+t,o+t):(r.negative[e]=s+t,s+t)}var t=e.data,n=e.context,r=e.stacked?e.stack:!1,i=e.stackingType,s,o,u,a,f,l,c;n.save(),n.lineJoin="round",n.lineWidth=e.lineWidth,n.strokeStyle="rgba(0,0,0,0.5)",n.fillStyle=e.fillStyle;for(a=0;a0?"top":"bottom",C,k,L;n.save(),n.translate(u/2,o/2),n.scale(1,v),k=Math.cos(b)*a,L=Math.sin(b)*a,i>0&&(this.plotSlice(k+i,L+i,h,g,y,n),l&&(n.fillStyle="rgba(0,0,0,0.1)",n.fill())),this.plotSlice(k,L,h,g,y,n),l&&(n.fillStyle=c,n.fill()),n.lineWidth=r,n.strokeStyle=f,n.stroke(),C={size:e.fontSize*1.2,color:e.fontColor,weight:1.5},w&&(e.htmlText||!e.textEnabled?(divStyle="position:absolute;"+N+":"+(o/2+(N==="top"?x:-x))+"px;",divStyle+=T+":"+(u/2+(T==="right"?-S:S))+"px;",d.push('
    ',w,"
    ")):(C.textAlign=T,C.textBaseline=N,Flotr.drawText(n,w,S,x,C)));if(e.htmlText||!e.textEnabled){var A=Flotr.DOM.node('
    ');Flotr.DOM.insert(A,d.join("")),Flotr.DOM.insert(e.element,A)}n.restore(),this.startAngle=y,this.slices=this.slices||[],this.slices.push({radius:h,x:k,y:L,explode:a,start:g,end:y})},plotSlice:function(e,t,n,r,i,s){s.beginPath(),s.moveTo(e,t),s.arc(e,t,n,r,i,!1),s.lineTo(e,t),s.closePath()},hit:function(e){var t=e.data[0],n=e.args,r=e.index,i=n[0],s=n[1],o=this.slices[r],u=i.relX-e.width/2,a=i.relY-e.height/2,f=Math.sqrt(u*u+a*a),l=Math.atan(a/u),c=Math.PI*2,h=o.explode||e.explode,p=o.start%c,d=o.end%c,v=e.epsilon;u<0?l+=Math.PI:u>0&&a<0&&(l+=c),fh&&(l>p&&ld&&(lp)||p===d&&(o.start===o.end&&Math.abs(l-p)v))&&(s.x=t[0],s.y=t[1],s.sAngle=p,s.eAngle=d,s.index=0,s.seriesIndex=r,s.fraction=t[1]/this.total)},drawHit:function(e){var t=e.context,n=this.slices[e.args.seriesIndex];t.save(),t.translate(e.width/2,e.height/2),this.plotSlice(n.x,n.y,n.radius,n.start,n.end,t),t.stroke(),t.restore()},clearHit:function(e){var t=e.context,n=this.slices[e.args.seriesIndex],r=2*e.lineWidth,i=n.radius+r;t.save(),t.translate(e.width/2,e.height/2),t.clearRect(n.x-i,n.y-i,2*i+r,2*i+r),t.restore()},extendYRange:function(e,t){this.total=(this.total||0)+t[0][1]}})}(),Flotr.addType("points",{options:{show:!1,radius:3,lineWidth:2,fill:!0,fillColor:"#FFFFFF",fillOpacity:1,hitRadius:null},draw:function(e){var t=e.context,n=e.lineWidth,r=e.shadowSize;t.save(),r>0&&(t.lineWidth=r/2,t.strokeStyle="rgba(0,0,0,0.1)",this.plot(e,r/2+t.lineWidth/2),t.strokeStyle="rgba(0,0,0,0.2)",this.plot(e,t.lineWidth/2)),t.lineWidth=e.lineWidth,t.strokeStyle=e.color,e.fill&&(t.fillStyle=e.fillStyle),this.plot(e),t.restore()},plot:function(e,t){var n=e.data,r=e.context,i=e.xScale,s=e.yScale,o,u,a;for(o=n.length-1;o>-1;--o){a=n[o][1];if(a===null)continue;u=i(n[o][0]),a=s(a);if(u<0||u>e.width||a<0||a>e.height)continue;r.beginPath(),t?r.arc(u,a+t,e.radius,0,Math.PI,!1):(r.arc(u,a,e.radius,0,2*Math.PI,!0),e.fill&&r.fill()),r.stroke(),r.closePath()}}}),Flotr.addType("radar",{options:{show:!1,lineWidth:2,fill:!0,fillOpacity:.4,radiusRatio:.9,sensibility:2},draw:function(e){var t=e.context,n=e.shadowSize;t.save(),t.translate(e.width/2,e.height/2),t.lineWidth=e.lineWidth,t.fillStyle="rgba(0,0,0,0.05)",t.strokeStyle="rgba(0,0,0,0.05)",this.plot(e,n/2),t.strokeStyle="rgba(0,0,0,0.1)",this.plot(e,n/4),t.strokeStyle=e.color,t.fillStyle=e.fillStyle,this.plot(e),t.restore()},plot:function(e,t){var n=e.data,r=e.context,i=Math.min(e.height,e.width)*e.radiusRatio/2,s=2*Math.PI/n.length,o=-Math.PI/2,u,a;t=t||0,r.beginPath();for(u=0;uthis.plotWidth||t.relY>this.plotHeight){this.el.style.cursor=null,e.removeClass(this.el,"flotr-crosshair");return}r.hideCursor&&(this.el.style.cursor="none",e.addClass(this.el,"flotr-crosshair")),n.save(),n.strokeStyle=r.color,n.lineWidth=1,n.beginPath(),r.mode.indexOf("x")!=-1&&(n.moveTo(s,i.top),n.lineTo(s,i.top+this.plotHeight)),r.mode.indexOf("y")!=-1&&(n.moveTo(i.left,o),n.lineTo(i.left+this.plotWidth,o)),n.stroke(),n.restore()},clearCrosshair:function(){var e=this.plotOffset,t=this.lastMousePos,n=this.octx;t&&(n.clearRect(Math.round(t.relX)+e.left,e.top,1,this.plotHeight+1),n.clearRect(e.left,Math.round(t.relY)+e.top,this.plotWidth+1,1))}})}(),function(){function n(e,t,n,r,i,s){var o="image/"+e,u=n.getImageData(0,0,r,i),a=new Image;return n.save(),n.globalCompositeOperation="destination-over",n.fillStyle=s,n.fillRect(0,0,r,i),a.src=t.toDataURL(o),n.restore(),n.clearRect(0,0,r,i),n.putImageData(u,0,0),a}var e=Flotr.DOM,t=Flotr._;Flotr.addPlugin("download",{saveImage:function(r,i,s,o){var u=this.options.grid,a;if(Flotr.isIE&&Flotr.isIE<9)return a=""+this.canvas.firstChild.innerHTML+"",window.open().document.write(a);if(r!=="jpeg"&&r!=="png")return;a=n(r,this.canvas,this.ctx,this.canvasWidth,this.canvasHeight,u&&u.backgroundColor||"#ffffff");if(!t.isElement(a)||!o)return window.open(a.src);this.download.restoreCanvas(),e.hide(this.canvas),e.hide(this.overlay),e.setStyles({position:"absolute"}),e.insert(this.el,a),this.saveImageElement=a},restoreCanvas:function(){e.show(this.canvas),e.show(this.overlay),this.saveImageElement&&this.el.removeChild(this.saveImageElement),this.saveImageElement=null}})}(),function(){var e=Flotr.EventAdapter,t=Flotr._;Flotr.addPlugin("graphGrid",{callbacks:{"flotr:beforedraw":function(){this.graphGrid.drawGrid()},"flotr:afterdraw":function(){this.graphGrid.drawOutline()}},drawGrid:function(){function v(e){for(p=0;p=c.max||(e==c.min||e==c.max)&&i.outlineWidth)return;r(Math.floor(c.d2p(e))+n.lineWidth/2)})}function g(e){n.moveTo(e,0),n.lineTo(e,f)}function y(e){n.moveTo(0,e),n.lineTo(l,e)}var n=this.ctx,r=this.options,i=r.grid,s=i.verticalLines,o=i.horizontalLines,u=i.minorVerticalLines,a=i.minorHorizontalLines,f=this.plotHeight,l=this.plotWidth,c,h,p,d;(s||u||o||a)&&e.fire(this.el,"flotr:beforegrid",[this.axes.x,this.axes.y,r,this]),n.save(),n.lineWidth=1,n.strokeStyle=i.tickColor;if(i.circular){n.translate(this.plotOffset.left+l/2,this.plotOffset.top+f/2);var b=Math.min(f,l)*r.radar.radiusRatio/2,w=this.axes.x.ticks.length,S=2*(Math.PI/w),x=-Math.PI/2;n.beginPath(),c=this.axes.y,o&&v(c.ticks),a&&v(c.minorTicks),s&&t.times(w,function(e){n.moveTo(0,0),n.lineTo(Math.cos(e*S+x)*b,Math.sin(e*S+x)*b)}),n.stroke()}else n.translate(this.plotOffset.left,this.plotOffset.top),i.backgroundColor&&(n.fillStyle=this.processColor(i.backgroundColor,{x1:0,y1:0,x2:l,y2:f}),n.fillRect(0,0,l,f)),n.beginPath(),c=this.axes.x,s&&m(c.ticks,g),u&&m(c.minorTicks,g),c=this.axes.y,o&&m(c.ticks,y),a&&m(c.minorTicks,y),n.stroke();n.restore(),(s||u||o||a)&&e.fire(this.el,"flotr:aftergrid",[this.axes.x,this.axes.y,r,this])},drawOutline:function(){var e=this,t=e.options,n=t.grid,r=n.outline,s=e.ctx,o=n.backgroundImage,u=e.plotOffset,a=u.left,f=u.top,l=e.plotWidth,c=e.plotHeight,h,p,d,v,m,g;if(!n.outlineWidth)return;s.save();if(n.circular){s.translate(a+l/2,f+c/2);var y=Math.min(c,l)*t.radar.radiusRatio/2,b=this.axes.x.ticks.length,w=2*(Math.PI/b),E=-Math.PI/2;s.beginPath(),s.lineWidth=n.outlineWidth,s.strokeStyle=n.color,s.lineJoin="round";for(i=0;i<=b;++i)s[i===0?"moveTo":"lineTo"](Math.cos(i*w+E)*y,Math.sin(i*w+E)*y);s.stroke()}else{s.translate(a,f);var S=n.outlineWidth,x=.5-S+(S+1)%2/2,T="lineTo",N="moveTo";s.lineWidth=S,s.strokeStyle=n.color,s.lineJoin="miter",s.beginPath(),s.moveTo(x,x),l-=S/2%1,c+=S/2,s[r.indexOf("n")!==-1?T:N](l,x),s[r.indexOf("e")!==-1?T:N](l,c),s[r.indexOf("s")!==-1?T:N](x,c),s[r.indexOf("w")!==-1?T:N](x,x),s.stroke(),s.closePath()}s.restore(),o&&(d=o.src||o,v=(parseInt(o.left,10)||0)+u.left,m=(parseInt(o.top,10)||0)+u.top,p=new Image,p.onload=function(){s.save(),o.alpha&&(s.globalAlpha=o.alpha),s.globalCompositeOperation="destination-over",s.drawImage(p,0,0,p.width,p.height,v,m,l,c),s.restore()},p.src=d)}})}(),function(){var e=Flotr.DOM,t=Flotr._,n=Flotr,r="opacity:0.7;background-color:#000;color:#fff;position:absolute;padding:2px 8px;-moz-border-radius:4px;border-radius:4px;white-space:nowrap;";Flotr.addPlugin("hit",{callbacks:{"flotr:mousemove":function(e,t){this.hit.track(t)},"flotr:click":function(e){var n=this.hit.track(e);n&&!t.isUndefined(n.index)&&(e.hit=n)},"flotr:mouseout":function(e){e.relatedTarget!==this.mouseTrack&&this.hit.clearHit()},"flotr:destroy":function(){this.options.mouse.container&&e.remove(this.mouseTrack),this.mouseTrack=null}},track:function(e){if(this.options.mouse.track||t.any(this.series,function(e){return e.mouse&&e.mouse.track}))return this.hit.hit(e)},executeOnType:function(e,r,i){function u(e,u){t.each(t.keys(n.graphTypes),function(t){e[t]&&e[t].show&&!e.hide&&this[t][r]&&(o=this.getOptions(e,t),o.fill=!!e.mouse.fillColor,o.fillStyle=this.processColor(e.mouse.fillColor||"#ffffff",{opacity:e.mouse.fillOpacity}),o.color=e.mouse.lineColor,o.context=this.octx,o.index=u,i&&(o.args=i),this[t][r].call(this[t],o),s=!0)},this)}var s=!1,o;return t.isArray(e)||(e=[e]),t.each(e,u,this),s},drawHit:function(e){var t=this.octx,n=e.series;if(n.mouse.lineColor){t.save(),t.lineWidth=n.points?n.points.lineWidth:1,t.strokeStyle=n.mouse.lineColor,t.fillStyle=this.processColor(n.mouse.fillColor||"#ffffff",{opacity:n.mouse.fillOpacity}),t.translate(this.plotOffset.left,this.plotOffset.top);if(!this.hit.executeOnType(n,"drawHit",e)){var r=e.xaxis,i=e.yaxis;t.beginPath(),t.arc(r.d2p(e.x),i.d2p(e.y),n.points.hitRadius||n.points.radius||n.mouse.radius,0,2*Math.PI,!0),t.fill(),t.stroke(),t.closePath()}t.restore(),this.clip(t)}this.prevHit=e},clearHit:function(){var t=this.prevHit,n=this.octx,r=this.plotOffset;n.save(),n.translate(r.left,r.top);if(t){if(!this.hit.executeOnType(t.series,"clearHit",this.prevHit)){var i=t.series,s=i.points?i.points.lineWidth:1;offset=(i.points.hitRadius||i.points.radius||i.mouse.radius)+s,n.clearRect(t.xaxis.d2p(t.x)-offset,t.yaxis.d2p(t.y)-offset,offset*2,offset*2)}e.hide(this.mouseTrack),this.prevHit=null}n.restore()},hit:function(e){var n=this.options,r=this.prevHit,i,s,o,u,a,f,l,c,h;if(this.series.length===0)return;h={relX:e.relX,relY:e.relY,absX:e.absX,absY:e.absY,series:this.series};if(n.mouse.trackY&&!n.mouse.trackAll&&this.hit.executeOnType(this.series,"hit",[e,h])&&!t.isUndefined(h.seriesIndex))a=this.series[h.seriesIndex],h.series=a,h.mouse=a.mouse,h.xaxis=a.xaxis,h.yaxis=a.yaxis;else{i=this.hit.closest(e);if(i){i=n.mouse.trackY?i.point:i.x,u=i.seriesIndex,a=this.series[u],l=a.xaxis,c=a.yaxis,s=2*a.mouse.sensibility;if(n.mouse.trackAll||i.distanceXl.xaxis.max)continue;p=Math.abs(g-v),d=Math.abs(y-m),h=p*p+d*d,h
    '),this.mouseTrack=c,e.insert(b||this.el,c));if(!g||g<0)g=0;a&&a.toFixed&&(a=a.toFixed(g)),f&&f.toFixed&&(f=f.toFixed(g)),T=n.mouse.trackFormatter({x:a,y:f,series:n.series,index:n.index,nearest:n,fraction:n.fraction});if(t.isNull(T)||t.isUndefined(T)){e.hide(c);return}c.innerHTML=T,e.show(c);if(!o)return;x=e.size(c),b&&(S=e.position(this.el),w=S.top,E=S.left);if(!n.mouse.relative)i+="top:",o.charAt(0)=="n"?i+=w+u+m:o.charAt(0)=="s"&&(i+=w-u+m+this.plotHeight-x.height),i+="px;bottom:auto;left:",o.charAt(1)=="e"?i+=E-u+p+this.plotWidth-x.width:o.charAt(1)=="w"&&(i+=E+u+p),i+="px;right:auto;";else if(s.pie&&s.pie.show){var N={x:this.plotWidth/2,y:this.plotHeight/2},C=Math.min(this.canvasWidth,this.canvasHeight)*s.pie.sizeRatio/2,k=n.sAngle=5||Math.abs(e.second.y-e.first.y)>=5}})}(),function(){var e=Flotr.DOM;Flotr.addPlugin("labels",{callbacks:{"flotr:afterdraw":function(){this.labels.draw()}},draw:function(){function b(e,t,r){var i=r?t.minorTicks:t.ticks,s=t.orientation===1,u=t.n===1,l,h;l={color:t.options.color||d.grid.color,angle:Flotr.toRad(t.options.labelsAngle),textBaseline:"middle"};for(c=0;c(s?e.plotWidth:e.plotHeight))continue;Flotr.drawText(v,n.label,l(e,s,o,a),h(e,s,o,a),u),!s&&!o&&(v.save(),v.strokeStyle=u.color,v.beginPath(),v.moveTo(e.plotOffset.left+e.plotWidth-8,e.plotOffset.top+t.d2p(n.v)),v.lineTo(e.plotOffset.left+e.plotWidth,e.plotOffset.top+t.d2p(n.v)),v.stroke(),v.restore())}}function E(e,t){var r=t.orientation===1,i=t.n===1,o="",u,a,f,l=e.plotOffset;!r&&!i&&(v.save(),v.strokeStyle=t.options.color||d.grid.color,v.beginPath());if(t.options.showLabels&&(i?!0:t.used))for(c=0;c(r?e.canvasWidth:e.canvasHeight))continue;f=l.top+(r?(i?1:-1)*(e.plotHeight+d.grid.labelMargin):t.d2p(n.v)-t.maxLabel.height/2),u=r?l.left+t.d2p(n.v)-s/2:0,o="",c===0?o=" first":c===t.ticks.length-1&&(o=" last"),o+=r?" flotr-grid-label-x":" flotr-grid-label-y",h+=['
    '+n.label+"
    "].join(" "),!r&&!i&&(v.moveTo(l.left+e.plotWidth-8,l.top+t.d2p(n.v)),v.lineTo(l.left+e.plotWidth,l.top+t.d2p(n.v)))}}var t,n,r,i,s,o,u,a,f,l,c,h="",p=0,d=this.options,v=this.ctx,m=this.axes,g={size:d.fontSize};for(c=0;c-1;--p){if(!n[p].label||n[p].hide)continue;d=s.labelFormatter(n[p].label),E=Math.max(E,this._text.measureText(d,S).width)}var x=Math.round(m+y*3+E),T=Math.round(f*(y+g)+y);!h&&h!==0&&(h=.1);if(!i.HtmlText&&this.textEnabled&&!s.container){l.charAt(0)=="s"&&(w=r.top+this.plotHeight-(c+T)),l.charAt(0)=="c"&&(w=r.top+this.plotHeight/2-(c+T/2)),l.charAt(1)=="e"&&(b=r.left+this.plotWidth-(c+x)),v=this.processColor(s.backgroundColor,{opacity:h}),a.fillStyle=v,a.fillRect(b,w,x,T),a.strokeStyle=s.labelBoxBorderColor,a.strokeRect(Flotr.toPixel(b),Flotr.toPixel(w),x,T);var N=b+y,C=w+y;for(p=0;p":""),u=!0);var k=n[p],L=s.labelBoxWidth,A=s.labelBoxHeight;d=s.labelFormatter(k.label),v="background-color:"+(k.bars&&k.bars.show&&k.bars.fillColor&&k.bars.fill?k.bars.fillColor:k.color)+";",o.push('','
    ','
    ','
    ',"
    ","
    ","",'',d,"")}u&&o.push("");if(o.length>0){var O=''+o.join("")+"
    ";if(s.container)O=e.node(O),this.legend.markup=O,e.insert(s.container,O);else{var M={position:"absolute",zIndex:"2",border:"1px solid "+s.labelBoxBorderColor};l.charAt(0)=="n"?(M.top=c+r.top+"px",M.bottom="auto"):l.charAt(0)=="c"?(M.top=c+(this.plotHeight-T)/2+"px",M.bottom="auto"):l.charAt(0)=="s"&&(M.bottom=c+r.bottom+"px",M.top="auto"),l.charAt(1)=="e"?(M.right=c+r.right+"px",M.left="auto"):l.charAt(1)=="w"&&(M.left=c+r.left+"px",M.right="auto");var P=e.create("div"),H;P.className="flotr-legend",e.setStyles(P,M),e.insert(P,O),e.insert(this.el,P);if(!h)return;var B=s.backgroundColor||i.grid.backgroundColor||"#ffffff";t.extend(M,e.size(P),{backgroundColor:B,zIndex:"",border:""}),M.width+="px",M.height+="px",P=e.create("div"),P.className="flotr-legend-bg",e.setStyles(P,M),e.opacity(P,h),e.insert(P," "),e.insert(this.el,P)}}}}}})}(),function(){function e(e){if(this.options.spreadsheet.tickFormatter)return this.options.spreadsheet.tickFormatter(e);var t=n.find(this.axes.x.ticks,function(t){return t.v==e});return t?t.label:e}var t=Flotr.DOM,n=Flotr._;Flotr.addPlugin("spreadsheet",{options:{show:!1,tabGraphLabel:"Graph",tabDataLabel:"Data",toolbarDownload:"Download CSV",toolbarSelectAll:"Select all",csvFileSeparator:",",decimalSeparator:".",tickFormatter:null,initialTab:"graph"},callbacks:{"flotr:afterconstruct":function(){if(!this.options.spreadsheet.show)return;var e=this.spreadsheet,n=t.node('
    '),r=t.node('
    '+this.options.spreadsheet.tabGraphLabel+"
    "),i=t.node('
    '+this.options.spreadsheet.tabDataLabel+"
    "),s;e.tabsContainer=n,e.tabs={graph:r,data:i},t.insert(n,r),t.insert(n,i),t.insert(this.el,n),s=t.size(i).height+2,this.plotOffset.bottom+=s,t.setStyles(n,{top:this.canvasHeight-s+"px"}),this.observe(r,"click",function(){e.showTab("graph")}).observe(i,"click",function(){e.showTab("data")}),this.options.spreadsheet.initialTab!=="graph"&&e.showTab(this.options.spreadsheet.initialTab)}},loadDataGrid:function(){if(this.seriesData)return this.seriesData;var e=this.series,t={};return n.each(e,function(e,r){n.each(e.data,function(e){var n=e[0],s=e[1],o=t[n];if(o)o[r+1]=s;else{var u=[];u[0]=n,u[r+1]=s,t[n]=u}})}),this.seriesData=n.sortBy(t,function(e,t){return parseInt(t,10)}),this.seriesData},constructDataGrid:function(){if(this.spreadsheet.datagrid)return this.spreadsheet.datagrid;var r=this.series,i=this.spreadsheet.loadDataGrid(),s=[""],o,u,a,f=[''];f.push(""),n.each(r,function(e,t){f.push('"),s.push("")}),f.push(""),n.each(i,function(t){f.push(""),n.times(r.length+1,function(r){var i="td",s=t[r],o=n.isUndefined(s)?"":Math.round(s*1e5)/1e5;if(r===0){i="th";var u=e.call(this,o);u&&(o=u)}f.push("<"+i+(i=="th"?' scope="row"':"")+">"+o+"")},this),f.push("")},this),s.push(""),a=t.node(f.join("")),o=t.node('"),u=t.node('"),this.observe(o,"click",n.bind(this.spreadsheet.downloadCSV,this)).observe(u,"click",n.bind(this.spreadsheet.selectAllData,this));var l=t.node('
    ');t.insert(l,o),t.insert(l,u);var c=this.canvasHeight-t.size(this.spreadsheet.tabsContainer).height-2,h=t.node('
    ');return t.insert(h,l),t.insert(h,a),t.insert(this.el,h),this.spreadsheet.datagrid=a,this.spreadsheet.container=h,a},showTab:function(e){if(this.spreadsheet.activeTab===e)return;switch(e){case"graph":t.hide(this.spreadsheet.container),t.removeClass(this.spreadsheet.tabs.data,"selected"),t.addClass(this.spreadsheet.tabs.graph,"selected");break;case"data":this.spreadsheet.datagrid||this.spreadsheet.constructDataGrid(),t.show(this.spreadsheet.container),t.addClass(this.spreadsheet.tabs.data,"selected"),t.removeClass(this.spreadsheet.tabs.graph,"selected");break;default:throw"Illegal tab name: "+e}this.spreadsheet.activeTab=e},selectAllData:function(){if(this.spreadsheet.tabs){var e,t,n,r,i=this.spreadsheet.constructDataGrid();return this.spreadsheet.showTab("data"),setTimeout(function(){(n=i.ownerDocument)&&(r=n.defaultView)&&r.getSelection&&n.createRange&&(e=window.getSelection())&&e.removeAllRanges?(t=n.createRange(),t.selectNode(i),e.removeAllRanges(),e.addRange(t)):document.body&&document.body.createTextRange&&(t=document.body.createTextRange())&&(t.moveToElementText(i),t.select())},0),!0}return!1},downloadCSV:function(){var t="",r=this.series,i=this.options,s=this.spreadsheet.loadDataGrid(),o=encodeURIComponent(i.spreadsheet.csvFileSeparator);if(i.spreadsheet.decimalSeparator===i.spreadsheet.csvFileSeparator)throw"The decimal separator is the same as the column separator ("+i.spreadsheet.decimalSeparator+")";n.each(r,function(e,n){t+=o+'"'+(e.label||String.fromCharCode(65+n)).replace(/\"/g,'\\"')+'"'}),t+="%0D%0A",t+=n.reduce(s,function(t,n){var r=e.call(this,n[0])||"";r='"'+(r+"").replace(/\"/g,'\\"')+'"';var s=n.slice(1).join(o);return i.spreadsheet.decimalSeparator!=="."&&(s=s.replace(/\./g,i.spreadsheet.decimalSeparator)),t+r+o+s+"%0D%0A"},"",this),Flotr.isIE&&Flotr.isIE<9?(t=t.replace(new RegExp(o,"g"),decodeURIComponent(o)).replace(/%0A/g,"\n").replace(/%0D/g,"\r"),window.open().document.write(t)):window.open("data:text/csv,"+t)}})}(),function(){var e=Flotr.DOM;Flotr.addPlugin("titles",{callbacks:{"flotr:afterdraw":function(){this.titles.drawTitles()}},drawTitles:function(){var t,n=this.options,r=n.grid.labelMargin,i=this.ctx,s=this.axes;if(!n.HtmlText&&this.textEnabled){var o={size:n.fontSize,color:n.grid.color,textAlign:"center"};n.subtitle&&Flotr.drawText(i,n.subtitle,this.plotOffset.left+this.plotWidth/2,this.titleHeight+this.subtitleHeight-2,o),o.weight=1.5,o.size*=1.5,n.title&&Flotr.drawText(i,n.title,this.plotOffset.left+this.plotWidth/2,this.titleHeight-2,o),o.weight=1.8,o.size*=.8,s.x.options.title&&s.x.used&&(o.textAlign=s.x.options.titleAlign||"center",o.textBaseline="top",o.angle=Flotr.toRad(s.x.options.titleAngle),o=Flotr.getBestTextAlign(o.angle,o),Flotr.drawText(i,s.x.options.title,this.plotOffset.left+this.plotWidth/2,this.plotOffset.top+s.x.maxLabel.height+this.plotHeight+2*r,o)),s.x2.options.title&&s.x2.used&&(o.textAlign=s.x2.options.titleAlign||"center",o.textBaseline="bottom",o.angle=Flotr.toRad(s.x2.options.titleAngle),o=Flotr.getBestTextAlign(o.angle,o),Flotr.drawText(i,s.x2.options.title,this.plotOffset.left+this.plotWidth/2,this.plotOffset.top-s.x2.maxLabel.height-2*r,o)),s.y.options.title&&s.y.used&&(o.textAlign=s.y.options.titleAlign||"right",o.textBaseline="middle",o.angle=Flotr.toRad(s.y.options.titleAngle),o=Flotr.getBestTextAlign(o.angle,o),Flotr.drawText(i,s.y.options.title,this.plotOffset.left-s.y.maxLabel.width-2*r,this.plotOffset.top+this.plotHeight/2,o)),s.y2.options.title&&s.y2.used&&(o.textAlign=s.y2.options.titleAlign||"left",o.textBaseline="middle",o.angle=Flotr.toRad(s.y2.options.titleAngle),o=Flotr.getBestTextAlign(o.angle,o),Flotr.drawText(i,s.y2.options.title,this.plotOffset.left+this.plotWidth+s.y2.maxLabel.width+2*r,this.plotOffset.top+this.plotHeight/2,o))}else{t=[],n.title&&t.push('
    ',n.title,"
    "),n.subtitle&&t.push('
    ',n.subtitle,"
    "),t.push(""),t.push('
    '),s.x.options.title&&s.x.used&&t.push('
    ',s.x.options.title,"
    "),s.x2.options.title&&s.x2.used&&t.push('
    ',s.x2.options.title,"
    "),s.y.options.title&&s.y.used&&t.push('
    ',s.y.options.title,"
    "),s.y2.options.title&&s.y2.used&&t.push('
    ',s.y2.options.title,"
    "),t=t.join("");var u=e.create("div");e.setStyles({color:n.grid.color}),u.className="flotr-titles",e.insert(this.el,u),e.insert(u,t)}}})}(); diff --git a/p/themes/Origine/freshrss.css b/p/themes/Origine/freshrss.css index 495477a86..78adbeff0 100644 --- a/p/themes/Origine/freshrss.css +++ b/p/themes/Origine/freshrss.css @@ -898,3 +898,46 @@ input.extend { text-decoration: underline; } } + +.stat{ + border:1px solid #aaa; + border-radius:10px; + box-shadow:2px 2px 5px #aaa; + margin: 10px 0; +} +.stat h2{ + border-bottom:1px solid #aaa; + margin:0; + padding-left:5px; +} +.stat h2 + *{ + margin:5px; +} +.stat h2 + div + div{ + margin:0 5px 5px 5px; +} +.stat h2 + table{ + border-collapse:collapse; + width:calc(100% - 10px); +} +.stat h2 + table th{ + text-transform:capitalize; +} +.stat h2 + table td{ + text-align:center; +} +.stat h2 + table thead th{ + border-bottom:2px solid #aaa; +} +.stat h2 + table tbody tr *{ + border-bottom:1px solid #aaa; +} +.stat h2 + table tbody tr:last-child *{ + border-bottom:0; +} +.stat h2 + table tr *{ + border-left:2px solid #aaa; +} +.stat h2 + table tr *:first-child{ + border-left:0; +} \ No newline at end of file -- cgit v1.2.3 From b0641fd0ebcaa02dcbeb2983703bff6516f93487 Mon Sep 17 00:00:00 2001 From: Alexis Degrugillier Date: Sun, 26 Jan 2014 08:54:52 -0500 Subject: Modification des statistiques. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ajout de la liste des 10 plus gros contributeurs Ajout de règles CSS pour les thèmes Flat et Dark Modification de l'affichage des camemberts (tri par nombre qui remplace le tri alphabétique) --- app/Controllers/indexController.php | 1 + app/Models/StatsDAO.php | 29 +++++++++++++++++++++++- app/i18n/en.php | 4 ++++ app/i18n/fr.php | 4 ++++ app/views/index/stats.phtml | 37 +++++++++++++++++++++++------- p/themes/Dark/freshrss.css | 45 +++++++++++++++++++++++++++++++++++++ p/themes/Flat/freshrss.css | 45 +++++++++++++++++++++++++++++++++++++ p/themes/Origine/freshrss.css | 5 ++++- 8 files changed, 160 insertions(+), 10 deletions(-) (limited to 'app/Models') diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php index 74883962d..cb6be6049 100755 --- a/app/Controllers/indexController.php +++ b/app/Controllers/indexController.php @@ -200,6 +200,7 @@ class FreshRSS_index_Controller extends Minz_ActionController { $this->view->count = ($statsDAO->calculateEntryCount()); $this->view->feedByCategory = $statsDAO->calculateFeedByCategory(); $this->view->entryByCategory = $statsDAO->calculateEntryByCategory(); + $this->view->topFeed = $statsDAO->calculateTopFeed(); } public function aboutAction () { diff --git a/app/Models/StatsDAO.php b/app/Models/StatsDAO.php index a317c8f27..60cec7847 100644 --- a/app/Models/StatsDAO.php +++ b/app/Models/StatsDAO.php @@ -122,6 +122,7 @@ FROM {$this->prefix}category AS c, {$this->prefix}feed AS f WHERE c.id = f.category GROUP BY label +ORDER BY data DESC SQL; $stm = $this->bd->prepare($sql); $stm->execute(); @@ -146,6 +147,7 @@ FROM {$this->prefix}category AS c, WHERE c.id = f.category AND f.id = e.id_feed GROUP BY label +ORDER BY data DESC SQL; $stm = $this->bd->prepare($sql); $stm->execute(); @@ -154,6 +156,31 @@ SQL; return $this->convertToPieSerie($res); } + /** + * Calculates the 10 top feeds based on their number of entries + * + * @return array + */ + public function calculateTopFeed() { + $sql = <<prefix}category AS c, +{$this->prefix}feed AS f, +{$this->prefix}entry AS e +WHERE c.id = f.category +AND f.id = e.id_feed +GROUP BY id +ORDER BY count DESC +LIMIT 10 +SQL; + $stm = $this->bd->prepare($sql); + $stm->execute(); + return $stm->fetchAll(PDO::FETCH_ASSOC); + } + private function convertToSerie($data) { $serie = array(); @@ -168,7 +195,7 @@ SQL; $serie = array(); foreach ($data as $value) { - $value['data'] = array(array(0, (int)$value['data'])); + $value['data'] = array(array(0, (int) $value['data'])); $serie[] = $value; } diff --git a/app/i18n/en.php b/app/i18n/en.php index 70fd8a74b..e5cf623c7 100644 --- a/app/i18n/en.php +++ b/app/i18n/en.php @@ -11,6 +11,8 @@ return array ( 'users' => 'Users', 'categories' => 'Categories', 'category' => 'Category', + 'feed' => 'Feed', + 'feeds' => 'Feeds', 'shortcuts' => 'Shortcuts', 'about' => 'About', 'stats' => 'Statistics', @@ -309,4 +311,6 @@ return array ( 'stats_entry_per_day' => 'Entries per day (last 30 days)', 'stats_feed_per_category' => 'Feeds per category', 'stats_entry_per_category' => 'Entries per category', + 'stats_top_feed' => 'Top ten feeds', + 'stats_entry_count' => 'Entry count', ); diff --git a/app/i18n/fr.php b/app/i18n/fr.php index 53645477b..693dce5d1 100644 --- a/app/i18n/fr.php +++ b/app/i18n/fr.php @@ -11,6 +11,8 @@ return array ( 'users' => 'Utilisateurs', 'categories' => 'Catégories', 'category' => 'Catégorie', + 'feed' => 'Flux', + 'feeds' => 'Flux', 'shortcuts' => 'Raccourcis', 'about' => 'À propos', 'stats' => 'Statistiques', @@ -309,4 +311,6 @@ return array ( 'stats_entry_per_day' => 'Nombre d’articles par jour (30 derniers jours)', 'stats_feed_per_category' => 'Flux par catégorie', 'stats_entry_per_category' => 'Articles par catégorie', + 'stats_top_feed' => 'Les dix plus gros flux', + 'stats_entry_count' => 'Nombre d’articles', ); diff --git a/app/views/index/stats.phtml b/app/views/index/stats.phtml index 487c50a4b..ae9db172b 100644 --- a/app/views/index/stats.phtml +++ b/app/views/index/stats.phtml @@ -16,23 +16,23 @@
    - - + + - - + + - - + + - - + +
     '+(e.label||String.fromCharCode(65+t))+"
    repartition['main_stream']['total']?>repartition['all_feeds']['total']?>repartition['main_stream']['total']?>repartition['all_feeds']['total']?>
    repartition['main_stream']['read']?>repartition['all_feeds']['read']?>repartition['main_stream']['read']?>repartition['all_feeds']['read']?>
    repartition['main_stream']['unread']?>repartition['all_feeds']['unread']?>repartition['main_stream']['unread']?>repartition['all_feeds']['unread']?>
    repartition['main_stream']['favorite']?>repartition['all_feeds']['favorite']?>repartition['main_stream']['favorite']?>repartition['all_feeds']['favorite']?>
    @@ -55,6 +55,27 @@
    +
    +

    + + + + + + + + + + topFeed as $feed):?> + + + + + + + +
    +