'); // Suffix to encapsulate data in php code. define('MIN_TIME_UPDATE', 5); // Minimum accepted time for update define('ERROR_NO_ERROR', 0); define('ERROR_NO_XML', 1); define('ERROR_ITEMS_MISSED', 2); define('ERROR_LAST_UPDATE', 3); define('ERROR_UNKNOWN', 4); // fix some warning date_default_timezone_set('Europe/Paris'); if (!is_dir(DATA_DIR)) { if (!@mkdir(DATA_DIR, 0755)) { echo ' '; exit(); } @chmod(DATA_DIR, 0755); if (!is_file(DATA_DIR.'/.htaccess')) { if (!@file_put_contents( DATA_DIR.'/.htaccess', "Allow from none\nDeny from all\n" )) { echo ' '; exit(); } } } /* function grabFavicon */ function grabFavicon($url, $feedHash){ $url = 'http://getfavicon.appspot.com/'.$url.'?defaulticon=bluepng'; $file = FAVICON_DIR.'/favicon.'.$feedHash.'.ico'; if(!file_exists($file) && in_array('curl', get_loaded_extensions()) && Session::isLogged()){ $ch = curl_init ($url); curl_setopt($ch, CURLOPT_HEADER, false); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_BINARYTRANSFER, true); $raw = curl_exec($ch); if (curl_getinfo($ch, CURLINFO_HTTP_CODE) == 200) { $fp = fopen($file, 'x'); fwrite($fp, $raw); fclose($fp); } curl_close ($ch); } if (file_exists($file)) { return $file; } else { return $url; } } class FeedConf { private $_file = ''; public $login = ''; public $hash = ''; public $disableSessionProtection = false; public $salt = ''; public $title = "Kriss feed"; public $redirector = ''; public $locale = 'en_GB'; public $shaarli = ''; public $maxItems = 100; public $maxUpdate = 60; public $order = 'newerFirst'; public $autoreadItem = false; public $autoreadPage = false; public $autoUpdate = false; public $autohide = false; public $autofocus = true; public $addFavicon = false; public $public = false; public $version; public $view = 'list'; public $filter = 'unread'; public $listFeeds = 'show'; public $byPage = 10; public $currentHash = 'all'; public $currentPage = 1; public $menuView = 1; public $menuListFeeds = 2; public $menuFilter = 3; public $menuOrder = 4; public $menuUpdate = 5; public $menuRead = 6; public $menuUnread = 7; public $menuEdit = 8; public $menuAdd = 9; public $menuHelp = 10; public $pagingItem = 1; public $pagingPage = 2; public $pagingByPage = 3; public $pagingMarkAs = 4; public function __construct($configFile, $version) { $this->_file = $configFile; $this->version = $version; // Loading user config if (file_exists($this->_file)) { include_once $this->_file; } else { $this->_install(); } Session::$disableSessionProtection = $this->disableSessionProtection; if ($this->addFavicon) { /* favicon dir */ if (!is_dir(INC_DIR)) { if (!@mkdir(INC_DIR, 0755)) { die("Can not create inc dir: ".INC_DIR); } } if (!is_dir(FAVICON_DIR)) { if (!@mkdir(FAVICON_DIR, 0755)) { die("Can not create inc dir: ".FAVICON_DIR); } } } if (Session::isLogged()) { unset($_SESSION['view']); unset($_SESSION['listFeeds']); unset($_SESSION['filter']); unset($_SESSION['order']); unset($_SESSION['byPage']); } $view = $this->getView(); $listFeeds = $this->getListFeeds(); $filter = $this->getFilter(); $order = $this->getOrder(); $byPage = $this->getByPage(); if ($this->view != $view || $this->listFeeds != $listFeeds || $this->filter != $filter || $this->order != $order || $this->byPage != $byPage ) { $this->view = $view; $this->listFeeds = $listFeeds; $this->filter = $filter; $this->order = $order; $this->byPage = $byPage; if (Session::isLogged()) { $this->write(); } } if (!Session::isLogged()) { $_SESSION['view'] = $view; $_SESSION['listFeeds'] = $listFeeds; $_SESSION['filter'] = $filter; $_SESSION['order'] = $order; $_SESSION['byPage'] = $byPage; } } private function _install() { if (!empty($_POST['setlogin']) && !empty($_POST['setpassword'])) { $this->setSalt(sha1(uniqid('', true).'_'.mt_rand())); $this->setLogin($_POST['setlogin']); $this->setHash($_POST['setpassword']); if ($this->write()) { echo ' '; exit(); } else { echo ' '; exit(); } Session::logout(); } else { FeedPage::init( array( 'version' => $this->version, 'pagetitle' => 'KrISS feed installation' ) ); FeedPage::installTpl(); } exit(); } public function hydrate(array $data) { foreach ($data as $key => $value) { // get setter $method = 'set'.ucfirst($key); // if setter exists just call it // (php is not case-sensitive with functions) if (method_exists($this, $method)) { $this->$method($value); } } if (!$this->write()) { die("Can't write to ".CONFIG_FILE); } } public function getView() { $view = $this->view; if (isset($_GET['view'])) { if ($_GET['view'] == 'expanded') { $view = 'expanded'; } if ($_GET['view'] == 'list') { $view = 'list'; } } else if (isset($_SESSION['view'])) { $view = $_SESSION['view']; } return $view; } public function getFilter() { $filter = $this->filter; if (isset($_GET['filter'])) { if ($_GET['filter'] == 'unread') { $filter = 'unread'; } if ($_GET['filter'] == 'all') { $filter = 'all'; } } else if (isset($_SESSION['filter'])) { $filter = $_SESSION['filter']; } return $filter; } public function getListFeeds() { $listFeeds = $this->listFeeds; if (isset($_GET['listFeeds'])) { if ($_GET['listFeeds'] == 'show') { $listFeeds = 'show'; } if ($_GET['listFeeds'] == 'hide') { $listFeeds = 'hide'; } } else if (isset($_SESSION['listFeeds'])) { $listFeeds = $_SESSION['listFeeds']; } return $listFeeds; } public function getByPage() { $byPage = $this->byPage; if (isset($_GET['byPage']) && is_numeric($_GET['byPage']) && $_GET['byPage'] > 0) { $byPage = $_GET['byPage']; } else if (isset($_SESSION['byPage'])) { $byPage = $_SESSION['byPage']; } return $byPage; } public function getOrder() { $order = $this->order; if (isset($_GET['order'])) { if ($_GET['order'] === 'newerFirst') { $order = 'newerFirst'; } if ($_GET['order'] === 'olderFirst') { $order = 'olderFirst'; } } else if (isset($_SESSION['order'])) { $order = $_SESSION['order']; } return $order; } public function getCurrentHash() { $currentHash = $this->currentHash; if (isset($_GET['currentHash'])) { $currentHash = preg_replace('/[^a-zA-Z0-9-_@]/', '', substr(trim($_GET['currentHash'], '/'), 0, 6)); } if (empty($currentHash)) { $currentHash = 'all'; } return $currentHash; } public function getCurrentPage() { $currentPage = $this->currentPage; if (isset($_GET['page']) && !empty($_GET['page'])) { $currentPage = (int)$_GET['page']; } else if (isset($_GET['previousPage']) && !empty($_GET['previousPage'])) { $currentPage = (int)$_GET['previousPage'] - 1; if ($currentPage < 1) { $currentPage = 1; } } else if (isset($_GET['nextPage']) && !empty($_GET['nextPage'])) { $currentPage = (int)$_GET['nextPage'] + 1; } return $currentPage; } public function setDisableSessionProtection($disableSessionProtection) { $this->disableSessionProtection = $disableSessionProtection; } public function setLogin($login) { $this->login = $login; } public function setPublic($public) { $this->public = $public; } public function setHash($pass) { $this->hash = sha1($pass.$this->login.$this->salt); } public function setSalt($salt) { $this->salt = $salt; } public function setTitle($title) { $this->title = $title; } public function setLocale($locale) { $this->locale = $locale; } public function setRedirector($redirector) { $this->redirector = $redirector; } public function setAutoreadPage($autoreadPage) { $this->autoreadPage = $autoreadPage; } public function setAutoUpdate($autoUpdate) { $this->autoUpdate = $autoUpdate; } public function setAutoreadItem($autoreadItem) { $this->autoreadItem = $autoreadItem; } public function setAutohide($autohide) { $this->autohide = $autohide; } public function setAutofocus($autofocus) { $this->autofocus = $autofocus; } public function setAddFavicon($addFavicon) { $this->addFavicon = $addFavicon; } public function setShaarli($url) { $this->shaarli = $url; } public function setMaxUpdate($max) { $this->maxUpdate = $max; } public function setMaxItems($max) { $this->maxItems = $max; } public function setOrder($order) { $this->order = $order; } public function getMenu() { $menu = array(); if ($this->menuView != 0) { $menu['menuView'] = $this->menuView; } if ($this->menuListFeeds != 0) { $menu['menuListFeeds'] = $this->menuListFeeds; } if ($this->menuFilter != 0) { $menu['menuFilter'] = $this->menuFilter; } if ($this->menuOrder != 0) { $menu['menuOrder'] = $this->menuOrder; } if ($this->menuUpdate != 0) { $menu['menuUpdate'] = $this->menuUpdate; } if ($this->menuRead != 0) { $menu['menuRead'] = $this->menuRead; } if ($this->menuUnread != 0) { $menu['menuUnread'] = $this->menuUnread; } if ($this->menuEdit != 0) { $menu['menuEdit'] = $this->menuEdit; } if ($this->menuAdd != 0) { $menu['menuAdd'] = $this->menuAdd; } if ($this->menuHelp != 0) { $menu['menuHelp'] = $this->menuHelp; } asort($menu); return $menu; } public function getPaging() { $paging = array(); if ($this->pagingItem != 0) { $paging['pagingItem'] = $this->pagingItem; } if ($this->pagingPage != 0) { $paging['pagingPage'] = $this->pagingPage; } if ($this->pagingByPage != 0) { $paging['pagingByPage'] = $this->pagingByPage; } if ($this->pagingMarkAs != 0) { $paging['pagingMarkAs'] = $this->pagingMarkAs; } asort($paging); return $paging; } public function setMenuView($menuView) { $this->menuView = $menuView; } public function setMenuListFeeds($menuListFeeds) { $this->menuListFeeds = $menuListFeeds; } public function setMenuFilter($menuFilter) { $this->menuFilter = $menuFilter; } public function setMenuOrder($menuOrder) { $this->menuOrder = $menuOrder; } public function setMenuUpdate($menuUpdate) { $this->menuUpdate = $menuUpdate; } public function setMenuRead($menuRead) { $this->menuRead = $menuRead; } public function setMenuUnread($menuUnread) { $this->menuUnread = $menuUnread; } public function setMenuEdit($menuEdit) { $this->menuEdit = $menuEdit; } public function setMenuAdd($menuAdd) { $this->menuAdd = $menuAdd; } public function setMenuHelp($menuHelp) { $this->menuHelp = $menuHelp; } public function setPagingItem($pagingItem) { $this->pagingItem = $pagingItem; } public function setPagingPage($pagingPage) { $this->pagingPage = $pagingPage; } public function setPagingByPage($pagingByPage) { $this->pagingByPage = $pagingByPage; } public function setPagingMarkAs($pagingMarkAs) { $this->pagingMarkAs = $pagingMarkAs; } public function write() { $data = array('login', 'hash', 'salt', 'title', 'redirector', 'shaarli', 'byPage', 'order', 'public', 'filter', 'view','locale', 'maxItems', 'autoreadItem', 'autoreadPage', 'maxUpdate', 'autohide', 'autofocus', 'listFeeds', 'autoUpdate', 'menuView', 'menuListFeeds', 'menuFilter', 'menuOrder', 'menuUpdate', 'menuRead', 'menuUnread', 'menuEdit', 'menuAdd', 'menuHelp', 'pagingItem', 'pagingPage', 'pagingByPage', 'addFavicon', 'pagingMarkAs', 'disableSessionProtection'); $out = '$key, array('$' => '\\$', '"' => '\\"')); $out .= '$this->'.$key.' = "'.$value."\";\n"; } $out .= '?>'; if (!@file_put_contents($this->_file, $out)) { return false; } return true; } } class FeedPage { public static $var = array(); private static $_instance; public static function init($var) { FeedPage::$var = $var; } public static function includesTpl() { extract(FeedPage::$var); ?> <?php echo $pagetitle;?>
KrISS feed installation
Welcome to KrISS feed
KrISS feed - A simple and smart (or stupid) feed reader. By Tontof
KrISS feed Reader information
options :
- ${url}: link of item
- ${title}: title of item
- ${via}: if domain of <link> and <guid> are different ${via} is equals to: via <guid>
- ${sel}: Only available with javascript: « selected text »
- example with shaarli : http://your-shaarli/?post=${url}&title=${title}&description=${sel}%0A%0A${via}&source=bookmarklet
http://anonym.to/? will mask the HTTP_REFERER, you can also use noreferrer to use HTML5 property
Check this if you get disconnected often or if your IP address changes often.
KrISS feed reader preferences
Need update to be taken into consideration
Not implemented yet
KrISS feed menu preferences You can order or remove elements in the menu. Set a position or leave empty if you don't want the element to appear in the menu.
If you want to switch between list and expanded view
If you want to show or hide list of feeds
If you want to filter all or unread items
If you want to order by newer or older items
If you want to update all, folder or a feed
If you want to mark all, folder or a feed as read
If you want to mark all, folder or a feed as unread
If you want to edit all, folder or a feed
If you want to add a feed
If you want to add a link to the help
KrISS feed paging menu preferences
If you want to go previous and next item
If you want to go previous and next page
If you want to modify number of items by page
If you add a mark as read button into paging
Cron configuration You can use &force to force update.
To update every 15 minutes */15 * * * * wget "" -O /tmp/kf.cron
To update every hour 0 * * * * wget "" -O /tmp/kf.cron
If you can not use wget, you may try php command line : 0 * * * * php -f > /tmp/kf.cron

Keyboard shortcut

Items navigation

'space' or 't'
When viewing items as list, let you open or close current item ('t'oggle current item)
'm'
'M'ark current item as read if unread or unread if read
'n' or right arrow
Go to 'n'ext item
'p' or left arrow
Go to 'p'revious item
'shift' + 'n'
Go to 'n'ext page
'shift' + 'p'
Go to 'p'revious page
'j'
Go to 'n'ext item and open it (in list view)
'k'
Go to 'p'revious item and open it (in list view)
'o'
'O'pen current item in new tab
'shift' + 'o'
'O'pen current item in current window
's'
'S'hare current item (go in configuration to set up you link)
'a'
Mark 'a'll items, 'a'll items from current feed or 'a'll items from current folder as read

Menu navigation

'h'
Go to 'H'ome page
'v'
Change 'v'iew as list or expanded
'f'
Show or hide list of 'f'eeds/'f'olders
'e'
'E'dit current selection (all, folder or feed)
'u'
'U'pdate current selection (all, folder or feed)
'r'
'R'eload the page as the 'F5' key in most of browsers
'?' or 'F1'
Go to Help page (actually it's shortcut to go to this page)

Check configuration

open_ssl
You should be able to load https:// rss links. You may have problems using https:// rss links.
Add a new feed
Add selected folders to feed
$folder) { ?>
Use bookmarklet to add a new feed
Add selected folders to selected feeds
$folder) { ?>
Remove selected folders to selected feeds
$folder) { ?>
List of feeds
    $feed) { ?>
Leave empty to delete
Feed main information
Feed folders $folder) { $checked = ''; if (in_array($hash, $feed['foldersHash'])) { $checked = ' checked="checked"'; } ?>
Feed preferences
'auto', 'max' or a number of minutes less than 'max' define in config

    updateFeedsHash($feedsHash, $forceUpdate, 'html')?>
Go home Go back ">Force update
Import Opml file Import an opml file as exported by Google Reader, Tiny Tiny RSS, RSS lounge...
kfc = $kfc; $this->dataFile = $dataFile; $this->cacheDir = $cacheDir; } public function getData() { return $this->_data; } public function setData($data) { $this->_data = $data; } public function loadData() { if (empty($this->_data)) { if (file_exists($this->dataFile)) { $this->_data = unserialize( gzinflate( base64_decode( substr( file_get_contents($this->dataFile), strlen(PHPPREFIX), -strlen(PHPSUFFIX) ) ) ) ); return true; } else { $this->_data['feeds'] = array( MyTool::smallHash('http://tontof.net/?rss') => array( 'title' => 'Tontof', 'foldersHash' => array(), 'timeUpdate' => 'auto', 'lastUpdate' => 0, 'nbUnread' => 0, 'nbAll' => 0, 'htmlUrl' => 'http://tontof.net', 'xmlUrl' => 'http://tontof.net/?rss', 'description' => 'A simple and smart (or stupid) blog')); $this->_data['folders'] = array(); $this->_data['items'] = array(); $this->_data['newItems'] = array(); return false; } } // data already loaded return true; } public function writeData() { if (Session::isLogged() || (isset($_GET['cron']) && $_GET['cron'] === sha1($this->kfc->salt.$this->kfc->hash))) { $write = @file_put_contents( $this->dataFile, PHPPREFIX . base64_encode(gzdeflate(serialize($this->_data))) . PHPSUFFIX ); if (!$write) { die("Can't write to " . $this->dataFile); } } } public function getFeeds() { return $this->_data['feeds']; } public function sortFeeds() { uasort( $this->_data['feeds'], 'Feed::sortByTitle' ); } public function getFeedsView() { $feedsView = array('all' => array('title' => 'All feeds', 'nbUnread' => 0, 'nbAll' => 0, 'feeds' => array()), 'folders' => array()); foreach ($this->_data['feeds'] as $feedHash => $feed) { if (isset($feed['error'])) { $feed['error'] = $this->getError($feed['error']); } $feedsView['all']['nbUnread'] += $feed['nbUnread']; $feedsView['all']['nbAll'] += $feed['nbAll']; if (empty($feed['foldersHash'])) { $feedsView['all']['feeds'][$feedHash] = $feed; } else { foreach ($feed['foldersHash'] as $folderHash) { $folder = $this->getFolder($folderHash); if ($folder !== false) { if (!isset($feedsView['folders'][$folderHash]['title'])) { $feedsView['folders'][$folderHash]['title'] = $folder['title']; $feedsView['folders'][$folderHash]['isOpen'] = $folder['isOpen']; $feedsView['folders'][$folderHash]['nbUnread'] = 0; $feedsView['folders'][$folderHash]['nbAll'] = 0; } $feedsView['folders'][$folderHash]['feeds'][$feedHash] = $feed; $feedsView['folders'][$folderHash]['nbUnread'] += $feed['nbUnread']; $feedsView['folders'][$folderHash]['nbAll'] += $feed['nbAll']; } } } } return $feedsView; } public function getFeed($feedHash) { if (isset($this->_data['feeds'][$feedHash])) { $this->_data['feeds'][$feedHash]['xmlUrl'] = htmlspecialchars($this->_data['feeds'][$feedHash]['xmlUrl']); $this->_data['feeds'][$feedHash]['htmlUrl'] = htmlspecialchars($this->_data['feeds'][$feedHash]['htmlUrl']); return $this->_data['feeds'][$feedHash]; } return false; } public function getFeedHtmlUrl($feedHash) { if (isset($this->_data['feeds'][$feedHash]['htmlUrl'])) { return $this->_data['feeds'][$feedHash]['htmlUrl']; } return false; } public function getFeedTitle($feedHash) { if (isset($this->_data['feeds'][$feedHash]['title'])) { return $this->_data['feeds'][$feedHash]['title']; } return false; } public function loadFeed($feedHash) { if (!isset($this->_data['feeds'][$feedHash]['items'])) { $this->_data['feeds'][$feedHash]['items'] = array(); if (file_exists($this->cacheDir.'/'.$feedHash.'.php')) { $items = unserialize( gzinflate( base64_decode( substr( file_get_contents($this->cacheDir.'/'.$feedHash.'.php'), strlen(PHPPREFIX), -strlen(PHPSUFFIX) ) ) ) ); $this->_data['feeds'][$feedHash]['items'] = $items; } } } public function editFeed( $feedHash, $title, $description, $foldersHash, $timeUpdate) { if (isset($this->_data['feeds'][$feedHash])) { if (!empty($title)) { $this->_data['feeds'][$feedHash]['title'] = $title; } if (!empty($description)) { $this->_data['feeds'][$feedHash]['description'] = $description; } $this->_data['feeds'][$feedHash]['foldersHash'] = $foldersHash; $this->_data['feeds'][$feedHash]['timeUpdate'] = 'auto'; if (!empty($timeUpdate)) { if ($timeUpdate == 'max') { $this->_data['feeds'][$feedHash]['timeUpdate'] = $timeUpdate; } else { $this->_data['feeds'][$feedHash]['timeUpdate'] = (int) $timeUpdate; $maxUpdate = $this->kfc->maxUpdate; if ($this->_data['feeds'][$feedHash]['timeUpdate'] < MIN_TIME_UPDATE || $this->_data['feeds'][$feedHash]['timeUpdate'] > $maxUpdate ) { $this->_data['feeds'][$feedHash]['timeUpdate'] = 'auto'; } } } } } public function removeFeed($feedHash) { if (isset($this->_data['feeds'][$feedHash])) { unset($this->_data['feeds'][$feedHash]); unlink($this->cacheDir. '/' .$feedHash.'.php' ); foreach (array_keys($this->_data['items']) as $itemHash) { if (substr($itemHash, 0, 6) === $feedHash) { unset($this->_data['items'][$itemHash]); } } foreach (array_keys($this->_data['newItems']) as $itemHash) { if (substr($itemHash, 0, 6) === $feedHash) { unset($this->_data['newItems'][$itemHash]); } } } } public function writeFeed($feedHash, $feed) { if (Session::isLogged() || (isset($_GET['cron']) && $_GET['cron'] === sha1($this->kfc->salt.$this->kfc->hash))) { if (!is_dir($this->cacheDir)) { if (!@mkdir($this->cacheDir, 0755)) { die("Can not create cache dir: ".$this->cacheDir); } @chmod($this->cacheDir, 0755); if (!is_file($this->cacheDir.'/.htaccess')) { if (!@file_put_contents( $this->cacheDir.'/.htaccess', "Allow from none\nDeny from all\n" )) { die("Can not protect cache dir: ".$this->cacheDir); } } } $write = @file_put_contents( $this->cacheDir.'/'.$feedHash.'.php', PHPPREFIX . base64_encode(gzdeflate(serialize($feed))) . PHPSUFFIX ); if (!$write) { die("Can't write to " . $this->cacheDir.'/'.$feedHash.'.php'); } } } public function orderFeedsForUpdate($feedsHash) { $newFeedsHash = array(); foreach(array_keys($this->_data['items']) as $itemHash) { $feedHash = substr($itemHash, 0, 6); if (in_array($feedHash, $feedsHash) and !in_array($feedHash, $newFeedsHash)) { $newFeedsHash[] = $feedHash; } } foreach($feedsHash as $feedHash) { if (!in_array($feedHash, $newFeedsHash)) { $newFeedsHash[] = $feedHash; } } return $newFeedsHash; } public function getFeedsHashFromFolderHash($folderHash) { $list = array(); $folders = $this->getFolders(); if (isset($folders[$folderHash])) { foreach ($this->_data['feeds'] as $feedHash => $feed) { if (in_array($folderHash, $feed['foldersHash'])) { $list[] = $feedHash; } } } return $list; } public function getFolders() { return $this->_data['folders']; } public function getFolder($folderHash) { if (isset($this->_data['folders'][$folderHash])) { return $this->_data['folders'][$folderHash]; } return false; } public function addFolder($folderTitle, $newFolderHash = '') { if (empty($newFolderHash)) { $newFolderHash = MyTool::smallHash($newFolderTitle); } $this->_data['folders'][$newFolderHash] = array( 'title' => $folderTitle, 'isOpen' => 1 ); } public function renameFolder($oldFolderHash, $newFolderTitle) { $newFolderHash = ''; if (!empty($newFolderTitle)) { $newFolderHash = MyTool::smallHash($newFolderTitle); $this->addFolder($newFolderTitle, $newFolderHash); $this->_data['folders'][$newFolderHash]['isOpen'] = $this->_data['folders'][$oldFolderHash]['isOpen']; } unset($this->_data['folders'][$oldFolderHash]); foreach ($this->_data['feeds'] as $feedHash => $feed) { $i = array_search($oldFolderHash, $feed['foldersHash']); if ($i !== false) { unset($this->_data['feeds'][$feedHash]['foldersHash'][$i]); if (!empty($newFolderTitle)) { $this->_data['feeds'][$feedHash]['foldersHash'][] = $newFolderHash; } } } } public function toggleFolder($hash) { if ($this->_data['folders'][$hash]) { $isOpen = $this->_data['folders'][$hash]['isOpen']; if ($isOpen) { $this->_data['folders'][$hash]['isOpen'] = 0; } else { $this->_data['folders'][$hash]['isOpen'] = 1; } } return true; } public function getFolderTitle($folderHash) { if (isset($this->_data['folders'][$folderHash])) { return $this->_data['folders'][$folderHash]['title']; } return false; } public function getItems($hash = 'all', $filter = 'all') { if (empty($hash) or $hash == 'all' and $filter == 'all') { return $this->_data['items']+$this->_data['newItems']; } if (empty($hash) or $hash == 'all' and $filter == 'old') { return $this->_data['items']; } if (empty($hash) or $hash == 'all' and $filter == 'new') { return $this->_data['newItems']; } $list = array(); $isRead = 1; if ($filter === 'unread') { $isRead = 0; } if (empty($hash) || $hash == 'all') { // all items foreach ($this->_data['items'] as $itemHash => $item) { if ($item[1] === $isRead) { $list[$itemHash] = $item; } } foreach ($this->_data['newItems'] as $itemHash => $item) { if ($item[1] === $isRead) { $list[$itemHash] = $item; } } } else { if (strlen($hash) === 12) { // an item if (isset($this->_data['items'][$hash])) { $list[$hash] = $this->_data['items'][$hash]; } else if (isset($this->_data['newItems'][$hash])) { $list[$hash] = $this->_data['newItems'][$hash]; } } else { $feedsHash = array(); if (isset($this->_data['feeds'][$hash])) { // a feed $feedsHash[] = $hash; } else if (isset($this->_data['folders'][$hash])) { // a folder foreach ($this->_data['feeds'] as $feedHash => $feed) { if (in_array($hash, $feed['foldersHash'])) { $feedsHash[] = $feedHash; } } } // get items from a list of feeds if (!empty($feedsHash)) { $flipFeedsHash = array_flip($feedsHash); foreach ($this->_data['items'] as $itemHash => $item) { if (isset($flipFeedsHash[substr($itemHash, 0, 6)])) { if ($filter === 'all' or $item[1] === $isRead) { $list[$itemHash] = $item; } } } foreach ($this->_data['newItems'] as $itemHash => $item) { if (isset($flipFeedsHash[substr($itemHash, 0, 6)])) { if ($filter === 'all' or $item[1] === $isRead) { $list[$itemHash] = $item; } } } } } } return $list; } public function loadItem($itemHash, $keep) { $feedHash = substr($itemHash, 0, 6); $item = array(); if (isset($this->_data['feeds'][$feedHash]['items'])) { if (isset($this->_data['feeds'][$feedHash]['items'][$itemHash])) { $item = $this->_data['feeds'][$feedHash]['items'][$itemHash]; } } else { $this->loadFeed($feedHash); return $this->loadItem($itemHash, $keep); } if (!$keep) { unset($this->_data['feeds'][$feedHash]['items']); } return $item; } public function getItem($itemHash, $keep = true) { $item = $this->loadItem($itemHash, $keep); if (!empty($item)) { $item['itemHash'] = $itemHash; $time = $item['time']; if (strftime('%Y%m%d', $time) == strftime('%Y%m%d', time())) { // Today $item['time'] = array('list' => utf8_encode(strftime('%R %p', $time)), 'expanded' => utf8_encode(strftime('%A %d %B %Y - %H:%M', $time))); } else { $item['time'] = array('list' => utf8_encode(strftime('%b %e, %Y', $time)), 'expanded' => utf8_encode(strftime('%A %d %B %Y - %H:%M', $time))); } if (isset($this->_data['items'][$itemHash])) { $item['read'] = $this->_data['items'][$itemHash][1]; } else if (isset($this->_data['newItems'][$itemHash])) { $item['read'] = $this->_data['newItems'][$itemHash][1]; $currentNewItemIndex = array_search($itemHash, array_keys($this->_data['newItems'])); if (isset($_SESSION['lastNewItemsHash'])) { $lastNewItemIndex = array_search($_SESSION['lastNewItemsHash'], array_keys($this->_data['newItems'])); if ($lastNewItemIndex < $currentNewItemIndex) { $_SESSION['lastNewItemsHash'] = $itemHash; } } else { $_SESSION['lastNewItemsHash'] = $itemHash; } } else { // FIX: data may be corrupted return false; } $item['author'] = htmlspecialchars(htmlspecialchars_decode(strip_tags($item['author']), ENT_QUOTES), ENT_NOQUOTES); $item['title'] = htmlspecialchars(htmlspecialchars_decode(strip_tags($item['title']), ENT_QUOTES), ENT_NOQUOTES); $item['link'] = htmlspecialchars($item['link']); $item['via'] = htmlspecialchars($item['via']); return $item; } return false; } public function updateItems() { if (isset($this->_data['needSort']) or (isset($this->_data['order']) and $this->_data['order'] != $this->kfc->order)) { unset($this->_data['needSort']); $this->_data['items'] = $this->_data['items']+$this->_data['newItems']; $this->_data['newItems'] = array(); // sort items if ($this->kfc->order === 'newerFirst') { arsort($this->_data['items']); } else { asort($this->_data['items']); } $this->_data['order'] = $this->kfc->order; return true; } return false; } public function formatChannel($channel) { $newChannel = array(); // list of format for each info in order of importance $formats = array('title' => array('title'), 'description' => array('description', 'subtitle'), 'htmlUrl' => array('link', 'id', 'guid')); foreach ($formats as $format => $list) { $newChannel[$format] = ''; $len = count($list); for ($i = 0; $i < $len; $i++) { if ($channel->hasChildNodes()) { $child = $channel->childNodes; for ($j = 0, $lenChannel = $child->length; $j<$lenChannel; $j++) { if (isset($child->item($j)->tagName) && $child->item($j)->tagName == $list[$i] ) { $newChannel[$format] = $child->item($j)->textContent; } } } } } return $newChannel; } public function getChannelFromXml($xml) { $channel = array(); // find feed type RSS, Atom $feed = $xml->getElementsByTagName('channel'); if ($feed->item(0)) { // RSS/rdf:RDF feed $channel = $feed->item(0); } else { $feed = $xml->getElementsByTagName('feed'); if ($feed->item(0)) { // Atom feed $channel = $feed->item(0); } else { // unknown feed } } if (!empty($channel)) { $channel = $this->formatChannel($channel); } return $channel; } public function formatItems($items, $formats) { $newItems = array(); foreach ($items as $item) { $tmpItem = array(); foreach ($formats as $format => $list) { $tmpItem[$format] = ''; $len = count($list); for ($i = 0; $i < $len; $i++) { if (is_array($list[$i])) { $tag = $item->getElementsByTagNameNS( $list[$i][0], $list[$i][1] ); } else { $tag = $item->getElementsByTagName($list[$i]); // wrong detection : e.g. media:content for content if ($tag->length != 0) { for ($j = $tag->length; --$j >= 0;) { $elt = $tag->item($j); if ($tag->item($j)->tagName != $list[$i]) { $elt->parentNode->removeChild($elt); } } } } if ($tag->length != 0) { // we find a correspondence for the current format // select first item (item(0)), (may not work) // stop to search for another one if ($format == 'link') { $tmpItem[$format] = ''; for ($j = 0; $j < $tag->length; $j++) { if ($tag->item($j)->hasAttribute('rel') && $tag->item($j)->getAttribute('rel') == 'alternate') { $tmpItem[$format] = $tag->item($j)->getAttribute('href'); $j = $tag->length; } } if ($tmpItem[$format] == '') { $tmpItem[$format] = $tag->item(0)->getAttribute('href'); } } if (empty($tmpItem[$format])) { $tmpItem[$format] = $tag->item(0)->textContent; } $i = $len; } } } if (!empty($tmpItem['link'])) { $hashUrl = MyTool::smallHash($tmpItem['link']); $newItems[$hashUrl] = array(); $newItems[$hashUrl]['title'] = $tmpItem['title']; $newItems[$hashUrl]['time'] = strtotime($tmpItem['time']) ? strtotime($tmpItem['time']) : time(); if (MyTool::isUrl($tmpItem['via']) && $tmpItem['via'] != $tmpItem['link']) { $newItems[$hashUrl]['via'] = $tmpItem['via']; } else { $newItems[$hashUrl]['via'] = ''; } $newItems[$hashUrl]['link'] = $tmpItem['link']; $newItems[$hashUrl]['author'] = $tmpItem['author']; mb_internal_encoding("UTF-8"); $newItems[$hashUrl]['description'] = mb_substr( strip_tags($tmpItem['description']), 0, 500 ); $newItems[$hashUrl]['content'] = $tmpItem['content']; } } return $newItems; } public function getItemsFromXml ($xml) { $items = array(); // find feed type RSS, Atom $feed = $xml->getElementsByTagName('channel'); if ($feed->item(0)) { // RSS/rdf:RDF feed $feed = $xml->getElementsByTagName('item'); $len = $feed->length; for ($i = 0; $i < $len; $i++) { $items[$i] = $feed->item($i); } $feed = $xml->getElementsByTagName('rss'); if (!$feed->item(0)) { $feed = $xml->getElementsByTagNameNS( "http://www.w3.org/1999/02/22-rdf-syntax-ns#", 'RDF' ); } } else { $feed = $xml->getElementsByTagName('feed'); if ($feed->item(0)) { // Atom feed $feed = $xml->getElementsByTagName('entry'); $len = $feed->length; for ($i = 0; $i < $len; $i++) { $items[$i] = $feed->item($i); } $feed = $xml->getElementsByTagName('feed'); } } // list of format for each info in order of importance $formats = array( 'author' => array('author', 'creator', 'dc:author', 'dc:creator'), 'content' => array('content:encoded', 'content', 'description', 'summary', 'subtitle'), 'description' => array('description', 'summary', 'subtitle', 'content', 'content:encoded'), 'via' => array('guid', 'id'), 'link' => array('feedburner:origLink', 'link', 'guid', 'id'), 'time' => array('pubDate', 'updated', 'lastBuildDate', 'published', 'dc:date', 'date'), 'title' => array('title')); if ($feed->item(0)) { $formats = $this->formatRDF($formats, $feed->item(0)); } return $this->formatItems($items, $formats); } public function getAttributeNS ($feed, $name) { $res = ''; if ($feed->nodeName === $name) { $ns = explode(':', $name); $res = $feed->getAttribute('xmlns:'.$ns[0]); } else { if ($feed->hasChildNodes()) { foreach ($feed->childNodes as $childNode) { if ($res === '') { $res = $this->getAttributeNS($childNode, $name); } else { break; } } } } return $res; } public function formatRDF($formats, $feed) { foreach ($formats as $format => $list) { for ($i = 0, $len = count($list); $i < $len; $i++) { $name = explode(':', $list[$i]); if (count($name)>1) { $res = $feed->getAttribute('xmlns:'.$name[0]); if (!empty($res)) { $ns = $res; } else { $ns = $this->getAttributeNS($feed, $list[$i]); } $formats[$format][$i] = array($ns, $name[1]); } } } return $formats; } public function loadUrl($url, $opts = array()){ $ch = curl_init($url); if (!empty($opts)) { curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $opts['http']['timeout']); curl_setopt($ch, CURLOPT_TIMEOUT, $opts['http']['timeout']); curl_setopt($ch, CURLOPT_USERAGENT, $opts['http']['user_agent']); } curl_setopt($ch, CURLOPT_ENCODING, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_BINARYTRANSFER, true); curl_setopt($ch, CURLOPT_URL, $url); $output = $this->curl_exec_follow($ch); curl_close($ch); return $output; } public function curl_exec_follow(&$ch, $redirects = 20, $curloptHeader = false) { if ((!ini_get('open_basedir') && !ini_get('safe_mode')) || $redirects < 1) { curl_setopt($ch, CURLOPT_HEADER, $curloptHeader); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, $redirects > 0); curl_setopt($ch, CURLOPT_MAXREDIRS, $redirects); return curl_exec($ch); } else { curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); curl_setopt($ch, CURLOPT_HEADER, true); curl_setopt($ch, CURLOPT_FORBID_REUSE, false); do { $data = curl_exec($ch); if (curl_errno($ch)) break; $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); // 301 Moved Permanently // 302 Found // 303 See Other // 307 Temporary Redirect if ($code != 301 && $code != 302 && $code!=303 && $code!=307) break; $header_start = strpos($data, "\r\n")+2; $headers = substr($data, $header_start, strpos($data, "\r\n\r\n", $header_start)+2-$header_start); if (!preg_match("!\r\n(?:Location|location|URI): *(.*?) *\r\n!", $headers, $matches)) break; curl_setopt($ch, CURLOPT_URL, $matches[1]); } while (--$redirects); if (!$redirects) trigger_error('Too many redirects. When following redirects, libcurl hit the maximum amount.', E_USER_WARNING); if (!$curloptHeader) $data = substr($data, strpos($data, "\r\n\r\n")+4); return $data; } } public function loadXml($xmlUrl) { // hide warning/error set_error_handler(array('MyTool', 'silence_errors')); // set user agent // http://php.net/manual/en/function.libxml-set-streams-context.php $opts = array( 'http' => array( 'timeout' => 4, 'user_agent' => 'KrISS feed agent '.$this->kfc->version.' by Tontof.net http://github.com/tontof/kriss_feed', ) ); $document = new DOMDocument(); if (in_array('curl', get_loaded_extensions())) { $output = $this->loadUrl($xmlUrl, $opts); $document->loadXML($output); } else { // try using libxml $context = stream_context_create($opts); libxml_set_streams_context($context); // request a file through HTTP $document->load($xmlUrl); } // show back warning/error restore_error_handler(); return $document; } public function addChannel($xmlUrl) { $feedHash = MyTool::smallHash($xmlUrl); if (!isset($this->_data['feeds'][$feedHash])) { $xml = $this->loadXml($xmlUrl); if (!$xml) { return false; } else { $channel = $this->getChannelFromXml($xml); $items = $this->getItemsFromXml($xml); if (count($items) == 0) { return false; } foreach (array_keys($items) as $itemHash) { if (empty($items[$itemHash]['via'])) { $items[$itemHash]['via'] = $channel['htmlUrl']; } if (empty($items[$itemHash]['author'])) { $items[$itemHash]['author'] = $channel['title']; } else { $items[$itemHash]['author'] = $channel['title'] . ' (' . $items[$itemHash]['author'] . ')'; } $items[$itemHash]['xmlUrl'] = $xmlUrl; $this->_data['newItems'][$feedHash . $itemHash] = array( $items[$itemHash]['time'], 0 ); $items[$feedHash . $itemHash] = $items[$itemHash]; unset($items[$itemHash]); } $channel['xmlUrl'] = $xmlUrl; $channel['foldersHash'] = array(); $channel['nbUnread'] = count($items); $channel['nbAll'] = count($items); $channel['timeUpdate'] = 'auto'; $channel['lastUpdate'] = time(); $this->_data['feeds'][$feedHash] = $channel; $this->_data['needSort'] = true; $this->writeFeed($feedHash, $items); return true; } } return false; } public function getTimeUpdate($feed) { $max = $feed['timeUpdate']; if ($max == 'auto') { $max = $this->kfc->maxUpdate; } elseif ($max == 'max') { $max = $this->kfc->maxUpdate; } elseif ((int) $max < MIN_TIME_UPDATE || (int) $max > $this->kfc->maxUpdate) { $max = $this->kfc->maxUpdate; } return (int) $max; } public function needUpdate($feed) { $diff = (int) (time()-$feed['lastUpdate']); if ($diff > $this->getTimeUpdate($feed) * 60) { return true; } return false; } public static function getError($error) { switch ($error) { case ERROR_NO_XML: return 'Feed is not in XML format'; break; case ERROR_ITEMS_MISSED: return 'Items may have been missed since last update'; break; case ERROR_LAST_UPDATE: case ERROR_UNKNOWN: return 'Problem with the last update'; break; default: return 'unknown error'; break; } } public function updateChannel($feedHash) { $error = ''; $newItems = array(); if (!isset($this->_data['feeds'][$feedHash])) { return array( 'error' => $error, 'newItems' => $newItems ); } unset($this->_data['feeds'][$feedHash]['error']); $xmlUrl = $this->_data['feeds'][$feedHash]['xmlUrl']; $xml = $this->loadXml($xmlUrl); if (!$xml) { if (file_exists($this->cacheDir.'/'.$feedHash.'.php')) { $error = ERROR_LAST_UPDATE; } else { $error = ERROR_NO_XML; } } else { // if feed description is empty try to update description // (after opml import, description is often empty) if (empty($this->_data['feeds'][$feedHash]['description'])) { $channel = $this->getChannelFromXml($xml); if (isset($channel['description'])) { $this->_data['feeds'][$feedHash]['description'] = $channel['description']; } // Check description only the first time description is empty if (empty($this->_data['feeds'][$feedHash]['description'])) { $this->_data['feeds'][$feedHash]['description'] = ' '; } } $this->loadFeed($feedHash); $oldItems = $this->_data['feeds'][$feedHash]['items']; $rssItems = $this->getItemsFromXml($xml); $rssItems = array_slice($rssItems, 0, $this->kfc->maxItems, true); $rssItemsHash = array_keys($rssItems); if (count($rssItemsHash) !== 0) { // Look for new items foreach ($rssItemsHash as $itemHash) { // itemHash is smallHash of link. To compare to item // hashes into data, we need to concatenate to feedHash. if (!isset($oldItems[$feedHash.$itemHash])) { if (empty($rssItems[$itemHash]['via'])) { $rssItems[$itemHash]['via'] = $this->_data['feeds'][$feedHash]['htmlUrl']; } if (empty($rssItems[$itemHash]['author'])) { $rssItems[$itemHash]['author'] = $this->_data['feeds'][$feedHash]['title']; } else { $rssItems[$itemHash]['author'] = $this->_data['feeds'][$feedHash]['title'] . ' (' . $rssItems[$itemHash]['author'] . ')'; } $rssItems[$itemHash]['xmlUrl'] = $xmlUrl; $newItems[$feedHash . $itemHash] = $rssItems[$itemHash]; } } $newItemsHash = array_keys($newItems); $this->_data['feeds'][$feedHash]['items'] = $newItems+$oldItems; // Check if items may have been missed if (count($oldItems) !== 0 and count($rssItemsHash) === count($newItemsHash)) { $error = ERROR_ITEMS_MISSED; } // Remove useless items foreach ($this->getItems($feedHash) as $itemHash => $item) { $itemRssHash = substr($itemHash, 6, 6); // Remove from cache already read items not any more in the feed if (!isset($rssItems[$itemRssHash]) and $item[1] == 1) { unset($this->_data['feeds'][$feedHash]['items'][$itemHash]); } if (!isset($this->_data['feeds'][$feedHash]['items'][$itemHash])) { // Remove items not any more in the cache unset($this->_data['items'][$itemHash]); unset($this->_data['newItems'][$itemHash]); } } // Check if quota exceeded $nbAll = count($this->_data['feeds'][$feedHash]['items']); if ($nbAll > $this->kfc->maxItems) { $this->_data['feeds'][$feedHash]['items'] = array_slice( $this->_data['feeds'][$feedHash]['items'], 0, $this->kfc->maxItems, true ); $nbAll = $this->kfc->maxItems; } // Update items list and feed information (nbUnread, nbAll) $this->_data['feeds'][$feedHash]['nbAll'] = $nbAll; $nbUnread = 0; foreach ($this->_data['feeds'][$feedHash]['items'] as $itemHash => $item) { if (isset($this->_data['items'][$itemHash])) { if ($this->_data['items'][$itemHash][1] === 0) { $nbUnread++; } } else if (isset($this->_data['newItems'][$itemHash])) { if ($this->_data['newItems'][$itemHash][1] === 0) { $nbUnread++; } } else { // TODO: Is appended at the end ?? $this->_data['newItems'][$itemHash] = array( $item['time'], 0 ); $nbUnread++; } } $this->_data['feeds'][$feedHash]['nbUnread'] = $nbUnread; } else { $error = ERROR_UNKNOWN; } } // update feed information $this->_data['feeds'][$feedHash]['lastUpdate'] = time(); if (!empty($error)) { $this->_data['feeds'][$feedHash]['error'] = $error; } if (empty($newItems)) { unset($this->_data['feeds'][$feedHash]['items']); $this->writeData(); } else { $this->writeFeed($feedHash, $this->_data['feeds'][$feedHash]['items']); unset($this->_data['feeds'][$feedHash]['items']); $this->_data['needSort'] = true; if (isset($_SESSION['lastNewItemsHash'])) { $lastNewItemIndex = array_search($_SESSION['lastNewItemsHash'], array_keys($this->_data['newItems'])); $this->_data['items'] = $this->_data['items']+array_slice($this->_data['newItems'], 0, $lastNewItemIndex + 1, true); $this->_data['newItems'] = array_slice($this->_data['newItems'], $lastNewItemIndex + 1, count($this->_data['newItems']) - $lastNewItemIndex, true); unset($_SESSION['lastNewItemsHash']); } if ($this->kfc->order === 'newerFirst') { arsort($this->_data['newItems']); } else { asort($this->_data['newItems']); } $this->_data['order'] = $this->kfc->order; $this->writeData(); } return array( 'error' => $error, 'newItems' => $newItems ); } public function updateFeedsHash($feedsHash, $force, $format = '') { $i = 0; $feedsHash = $this->orderFeedsForUpdate($feedsHash); ob_end_flush(); if (ob_get_level() == 0) ob_start(); $start = microtime(true); foreach ($feedsHash as $feedHash) { $i++; $feed = $this->getFeed($feedHash); $str = '
  • '.number_format(microtime(true)-$start,3).' seconds ('.$i.'/'.count($feedsHash).'): Updating: '.$feed['title'].'
  • '; echo ($format==='html'?$str:strip_tags($str)).str_pad('',4096)."\n"; ob_flush(); flush(); if ($force or $this->needUpdate($feed)) { $info = $this->updateChannel($feedHash); $str = '
  • '.number_format(microtime(true)-$start,3).' seconds: Updated: '.count($info['newItems']).' new item(s)'; if (empty($info['error'])) { $str .= '
  • '; } else { $str .= ' ('.$this->getError($info['error']).')'; } } else { $str = '
  • '.number_format(microtime(true)-$start,3).' seconds: Already up-to-date: '.$feed['title'].'
  • '; } echo ($format==='html'?$str:strip_tags($str)).str_pad('',4096)."\n"; ob_flush(); flush(); } } public function markAll($read) { $save = false; foreach (array_keys($this->_data['items']) as $itemHash) { if (!$save and $this->_data['items'][$itemHash][1] != $read) { $save = true; } $this->_data['items'][$itemHash][1] = $read; } foreach (array_keys($this->_data['newItems']) as $itemHash) { if (!$save and $this->_data['newItems'][$itemHash][1] != $read) { $save = true; } $this->_data['newItems'][$itemHash][1] = $read; } if ($save) { foreach ($this->_data['feeds'] as $feedHash => $feed) { if ($read == 1) { $this->_data['feeds'][$feedHash]['nbUnread'] = 0; } else { $this->_data['feeds'][$feedHash]['nbUnread'] = $this->_data['feeds'][$feedHash]['nbAll']; } } } return $save; } public function markItem($itemHash, $read) { $save = false; if (isset($this->_data['items'][$itemHash])) { if ($this->_data['items'][$itemHash][1] != $read) { $save = true; $this->_data['items'][$itemHash][1] = $read; } } else if (isset($this->_data['newItems'][$itemHash])) { if ($this->_data['newItems'][$itemHash][1] != $read) { $save = true; $this->_data['newItems'][$itemHash][1] = $read; } } if ($save) { $feedHash = substr($itemHash, 0, 6); if ($read == 1) { $this->_data['feeds'][$feedHash]['nbUnread']--; } else { $this->_data['feeds'][$feedHash]['nbUnread']++; } } return $save; } public function markFeeds($feedsHash, $read) { $save = false; // get items from a list of feeds $flipFeedsHash = array_flip($feedsHash); foreach ($this->_data['items'] as $itemHash => $item) { if (isset($flipFeedsHash[substr($itemHash, 0, 6)])) { if ($this->_data['items'][$itemHash][1] != $read) { $save = true; $this->_data['items'][$itemHash][1] = $read; } } } foreach ($this->_data['newItems'] as $itemHash => $item) { if (isset($flipFeedsHash[substr($itemHash, 0, 6)])) { if ($this->_data['newItems'][$itemHash][1] != $read) { $save = true; $this->_data['newItems'][$itemHash][1] = $read; } } } if ($save) { foreach (array_values($feedsHash) as $feedHash) { if ($read == 1) { $this->_data['feeds'][$feedHash]['nbUnread'] = 0; } else { $this->_data['feeds'][$feedHash]['nbUnread'] = $this->_data['feeds']['nbAll']; } } } return $save; } public function mark($hash, $read) { if (empty($hash) || $hash == 'all') { // all items return $this->markAll($read); } else { if (strlen($hash) === 12) { // an item return $this->markItem($hash, $read); } else { $feedsHash = array(); if (isset($this->_data['feeds'][$hash])) { // a feed $feedsHash[] = $hash; } else if (isset($this->_data['folders'][$hash])) { // a folder foreach ($this->_data['feeds'] as $feedHash => $feed) { if (in_array($hash, $feed['foldersHash'])) { $feedsHash[] = $feedHash; } } } return $this->markFeeds($feedsHash, $read); } } return false; } public function hashType($hash) { $type = ''; if (empty($hash) || $hash=='all') { $type = 'all'; } else { if (strlen($hash) === 12) { // should be an item $type = 'item'; } else { if (isset($this->_data['folders'][$hash])) { // a folder $type = 'folder'; } else { if (isset($this->_data['feeds'][$hash])) { // a feed $type = 'feed'; } else { $type = 'unknown'; } } } } return $type; } public static function sortByTitle($a, $b) { return strnatcasecmp($a['title'], $b['title']); } } class MyTool { public static function initPhp() { define('START_TIME', microtime(true)); if (phpversion() < 5) { die("Argh you don't have PHP 5 !"); } error_reporting(E_ALL); function stripslashesDeep($value) { return is_array($value) ? array_map('stripslashesDeep', $value) : stripslashes($value); } if (get_magic_quotes_gpc()) { $_POST = array_map('stripslashesDeep', $_POST); $_GET = array_map('stripslashesDeep', $_GET); $_COOKIE = array_map('stripslashesDeep', $_COOKIE); } ob_start(); register_shutdown_function('ob_end_flush'); } public static function isUrl($url) { // http://neo22s.com/check-if-url-exists-and-is-online-php/ $pattern='|^http(s)?://[a-z0-9-]+(.[a-z0-9-]+)*(:[0-9]+)?(/.*)?$|i'; return preg_match($pattern, $url); } public static function isEmail($email) { $pattern = "/^[A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2, 4}$/i"; return (preg_match($pattern, $email)); } public static function formatBBCode($text) { $replace = array( '/\[m\](.+?)\[\/m\]/is' => '/* moderate */', '/\[b\](.+?)\[\/b\]/is' => '$1', '/\[i\](.+?)\[\/i\]/is' => '$1', '/\[s\](.+?)\[\/s\]/is' => '$1', '/\[u\](.+?)\[\/u\]/is' => '$1', '/\[url\](.+?)\[\/url]/is' => '$1', '/\[url=(\w+:\/\/[^\]]+)\](.+?)\[\/url]/is' => '$2', '/\[quote\](.+?)\[\/quote\]/is' => '
    $1
    ', '/\[code\](.+?)\[\/code\]/is' => '$1', '/\[([^[]+)\|([^[]+)\]/is' => '$1' ); $text = preg_replace( array_keys($replace), array_values($replace), $text ); return $text; } public static function formatText($text) { $text = preg_replace_callback( '/(.*?)<\/code_html>/is', create_function( '$matches', 'return htmlspecialchars($matches[1]);' ), $text ); $text = preg_replace_callback( '/(.*?)<\/code_php>/is', create_function( '$matches', 'return highlight_string("", true);' ), $text ); $text = preg_replace('/
    /is', '', $text); $text = preg_replace( '#(^|\s)([a-z]+://([^\s\w/]?[\w/])*)(\s|$)#im', '\\1\\2\\4', $text ); $text = preg_replace( '#(^|\s)wp:?([a-z]{2}|):([\w]+)#im', '\\1\\3', $text ); $text = str_replace( 'http://.wikipedia.org/wiki/', 'http://www.wikipedia.org/wiki/', $text ); $text = str_replace('\wp:', 'wp:', $text); $text = str_replace('\http:', 'http:', $text); $text = MyTool::formatBBCode($text); $text = nl2br($text); return $text; } public static function getUrl() { $https = (!empty($_SERVER['HTTPS']) && (strtolower($_SERVER['HTTPS']) == 'on')) || (isset($_SERVER["SERVER_PORT"]) && $_SERVER["SERVER_PORT"] == '443'); // HTTPS detection. $serverport = (!isset($_SERVER["SERVER_PORT"]) || $_SERVER["SERVER_PORT"] == '80' || ($https && $_SERVER["SERVER_PORT"] == '443') ? '' : ':' . $_SERVER["SERVER_PORT"]); $scriptname = ($_SERVER["SCRIPT_NAME"] == 'index.php' ? '' : $_SERVER["SCRIPT_NAME"]); if (!isset($_SERVER["SERVER_NAME"])) { return $scriptname; } return 'http' . ($https ? 's' : '') . '://' . $_SERVER["SERVER_NAME"] . $serverport . $scriptname; } public static function rrmdir($dir) { if (is_dir($dir) && ($d = @opendir($dir))) { while (($file = @readdir($d)) !== false) { if ( $file == '.' || $file == '..' ) { continue; } else { unlink($dir . '/' . $file); } } } } public static function humanBytes($bytes) { $siPrefix = array( 'bytes', 'KB', 'MB', 'GB', 'TB', 'EB', 'ZB', 'YB' ); $base = 1024; $class = min((int) log($bytes, $base), count($siPrefix) - 1); $val = sprintf('%1.2f', $bytes / pow($base, $class)); return $val . ' ' . $siPrefix[$class]; } public static function returnBytes($val) { $val = trim($val); $last = strtolower($val[strlen($val)-1]); switch($last) { case 'g': $val *= 1024; case 'm': $val *= 1024; case 'k': $val *= 1024; } return $val; } public static function getMaxFileSize() { $sizePostMax = MyTool::returnBytes(ini_get('post_max_size')); $sizeUploadMax = MyTool::returnBytes(ini_get('upload_max_filesize')); // Return the smaller of two: return min($sizePostMax, $sizeUploadMax); } public static function smallHash($text) { $t = rtrim(base64_encode(hash('crc32', $text, true)), '='); // Get rid of characters which need encoding in URLs. $t = str_replace('+', '-', $t); $t = str_replace('/', '_', $t); $t = str_replace('=', '@', $t); return $t; } public static function renderJson($data) { header('Cache-Control: no-cache, must-revalidate'); header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); header('Content-type: application/json; charset=UTF-8'); echo json_encode($data); exit(); } public static function redirect($rurl = '') { if ($rurl === '') { // if (!empty($_SERVER['HTTP_REFERER']) && strcmp(parse_url($_SERVER['HTTP_REFERER'],PHP_URL_HOST),$_SERVER['SERVER_NAME'])==0) $rurl = (empty($_SERVER['HTTP_REFERER'])?'?':$_SERVER['HTTP_REFERER']); if (!empty($_POST)) { $rurl = $_POST['returnurl']; } } // prevent loop if (empty($rurl) || parse_url($rurl, PHP_URL_QUERY) === $_SERVER['QUERY_STRING']) { $rurl = MyTool::getUrl(); } if (substr($rurl, 0, 1) !== '?') { $ref = MyTool::getUrl(); if (substr($rurl, 0, strlen($ref)) != $ref) { $rurl = $ref; } } header('Location: '.$rurl); exit(); } public static function silence_errors($num, $str) { // No-op } } class Opml { public static function importOpml($kfData) { $feeds = $kfData['feeds']; $folders = $kfData['folders']; $filename = $_FILES['filetoupload']['name']; $filesize = $_FILES['filetoupload']['size']; $data = file_get_contents($_FILES['filetoupload']['tmp_name']); $overwrite = isset($_POST['overwrite']); $opml = new DOMDocument('1.0', 'UTF-8'); $importCount=0; if ($opml->loadXML($data)) { $body = $opml->getElementsByTagName('body'); $xmlArray = Opml::getArrayFromXml($body->item(0)); $array = Opml::convertOpmlArray($xmlArray['outline']); foreach ($array as $hashUrl => $arrayInfo) { $title = ''; if (isset($arrayInfo['title'])) { $title = $arrayInfo['title']; } else if (isset($arrayInfo['text'])) { $title = $arrayInfo['text']; } $foldersHash = array(); if (isset($arrayInfo['folders'])) { foreach ($arrayInfo['folders'] as $folder) { $folderTitle = html_entity_decode( $folder, ENT_QUOTES, 'UTF-8' ); $folderHash = MyTool::smallHash($folderTitle); if (!isset($folders[$folderHash])) { $folders[$folderHash] = array('title' => $folderTitle, 'isOpen' => true); } $foldersHash[] = $folderHash; } } $timeUpdate = 'auto'; $lastUpdate = 0; $xmlUrl = ''; if (isset($arrayInfo['xmlUrl'])) { $xmlUrl = $arrayInfo['xmlUrl']; } $htmlUrl = ''; if (isset($arrayInfo['htmlUrl'])) { $htmlUrl = $arrayInfo['htmlUrl']; } $description = ''; if (isset($arrayInfo['description'])) { $description = $arrayInfo['description']; } // create new feed if (!empty($xmlUrl)) { $oldFeed = array('nbUnread' => 0, 'nbAll' => 0); if (isset($feeds[$hashUrl])) { $oldFeed['nbUnread'] = $feeds[$hashUrl]['nbUnread']; $oldFeed['nbAll'] = $feeds[$hashUrl]['nbAll']; } $currentFeed = array( 'title' => html_entity_decode($title, ENT_QUOTES, 'UTF-8'), 'description' => html_entity_decode($description, ENT_QUOTES, 'UTF-8'), 'htmlUrl' => html_entity_decode($htmlUrl, ENT_QUOTES, 'UTF-8'), 'xmlUrl' => html_entity_decode($xmlUrl, ENT_QUOTES, 'UTF-8'), 'nbUnread' => $oldFeed['nbUnread'], 'nbAll' => $oldFeed['nbAll'], 'foldersHash' => $foldersHash, 'timeUpdate' => $timeUpdate, 'lastUpdate' => $lastUpdate); if ($overwrite || !isset($feeds[$hashUrl])) { $feeds[$hashUrl] = $currentFeed; $importCount++; } } } echo ''; $kfData['feeds'] = $feeds; $kfData['folders'] = $folders; return $kfData; } else { echo ''; exit; } } public static function exportOpml($feeds, $folders) { $withoutFolder = array(); $withFolder = array(); // get a new representation of data using folders as key foreach ($feeds as $hashUrl => $arrayInfo) { if (empty($arrayInfo['foldersHash'])) { $withoutFolder[] = $hashUrl; } else { foreach ($arrayInfo['foldersHash'] as $folderHash) { $withFolder[$folderHash][] = $hashUrl; } } } // generate opml file header('Content-Type: text/xml; charset=utf-8'); header( 'Content-disposition: attachment; filename=kriss_feed_' . strval(date('Ymd_His')) . '.opml' ); $opmlData = new DOMDocument('1.0', 'UTF-8'); // we want a nice output $opmlData->formatOutput = true; // opml node creation $opml = $opmlData->createElement('opml'); $opmlVersion = $opmlData->createAttribute('version'); $opmlVersion->value = '1.0'; $opml->appendChild($opmlVersion); // head node creation $head = $opmlData->createElement('head'); $title = $opmlData->createElement('title', 'KrISS Feed'); $head->appendChild($title); $opml->appendChild($head); // body node creation $body = $opmlData->createElement('body'); // without folder outline node foreach ($withoutFolder as $hashUrl) { $outline = $opmlData->createElement('outline'); $outlineTitle = $opmlData->createAttribute('title'); $outlineTitle->value = htmlspecialchars( $feeds[$hashUrl]['title'] ); $outline->appendChild($outlineTitle); $outlineText = $opmlData->createAttribute('text'); $outlineText->value = htmlspecialchars($feeds[$hashUrl]['title']); $outline->appendChild($outlineText); if (!empty($feeds[$hashUrl]['description'])) { $outlineDescription = $opmlData->createAttribute('description'); $outlineDescription->value = htmlspecialchars($feeds[$hashUrl]['description']); $outline->appendChild($outlineDescription); } $outlineXmlUrl = $opmlData->createAttribute('xmlUrl'); $outlineXmlUrl->value = htmlspecialchars($feeds[$hashUrl]['xmlUrl']); $outline->appendChild($outlineXmlUrl); $outlineHtmlUrl = $opmlData->createAttribute('htmlUrl'); $outlineHtmlUrl->value = htmlspecialchars( $feeds[$hashUrl]['htmlUrl'] ); $outline->appendChild($outlineHtmlUrl); $body->appendChild($outline); } // with folder outline node foreach ($withFolder as $folderHash => $arrayHashUrl) { $outline = $opmlData->createElement('outline'); $outlineTitle = $opmlData->createAttribute('title'); $outlineTitle->value = htmlspecialchars($folders[$folderHash]['title']); $outline->appendChild($outlineTitle); $outlineText = $opmlData->createAttribute('text'); $outlineText->value = htmlspecialchars($folders[$folderHash]['title']); $outline->appendChild($outlineText); foreach ($arrayHashUrl as $hashUrl) { $outlineKF = $opmlData->createElement('outline'); $outlineTitle = $opmlData->createAttribute('title'); $outlineTitle->value = htmlspecialchars($feeds[$hashUrl]['title']); $outlineKF->appendChild($outlineTitle); $outlineText = $opmlData->createAttribute('text'); $outlineText->value = htmlspecialchars($feeds[$hashUrl]['title']); $outlineKF->appendChild($outlineText); if (!empty($feeds[$hashUrl]['description'])) { $outlineDescription = $opmlData->createAttribute('description'); $outlineDescription->value = htmlspecialchars( $feeds[$hashUrl]['description'] ); $outlineKF->appendChild($outlineDescription); } $outlineXmlUrl = $opmlData->createAttribute('xmlUrl'); $outlineXmlUrl->value = htmlspecialchars($feeds[$hashUrl]['xmlUrl']); $outlineKF->appendChild($outlineXmlUrl); $outlineHtmlUrl = $opmlData->createAttribute('htmlUrl'); $outlineHtmlUrl->value = htmlspecialchars($feeds[$hashUrl]['htmlUrl']); $outlineKF->appendChild($outlineHtmlUrl); $outline->appendChild($outlineKF); } $body->appendChild($outline); } $opml->appendChild($body); $opmlData->appendChild($opml); echo $opmlData->saveXML(); exit(); } public static function getArrayFromXml($node) { $array = false; if ($node->hasAttributes()) { foreach ($node->attributes as $attr) { $array[$attr->nodeName] = $attr->nodeValue; } } if ($node->hasChildNodes()) { if ($node->childNodes->length == 1) { $array[$node->firstChild->nodeName] = $node->firstChild->nodeValue; } else { foreach ($node->childNodes as $childNode) { if ($childNode->nodeType != XML_TEXT_NODE) { $array[$childNode->nodeName][] = Opml::getArrayFromXml($childNode); } } } } return $array; } public static function convertOpmlArray($array, $listFolders = array()) { $newArray = array(); for ($i = 0, $len = count($array); $i < $len; $i++) { if (isset($array[$i]['outline']) && (isset($array[$i]['text']) || isset($array[$i]['title'])) ) { // here is a folder if (isset($array[$i]['text'])) { $listFolders[] = $array[$i]['text']; } else { $listFolders[] = $array[$i]['title']; } $newArray = array_merge( $newArray, Opml::convertOpmlArray( $array[$i]['outline'], $listFolders ) ); array_pop($listFolders); } else { if (isset($array[$i]['xmlUrl'])) { // here is a feed $xmlUrl = MyTool::smallHash($array[$i]['xmlUrl']); if (isset($newArray[$xmlUrl])) { //feed already exists foreach ($listFolders as $val) { // add folder to the feed if (!in_array( $val, $newArray[$xmlUrl]['folders'] )) { $newArray[$xmlUrl]['folders'][] = $val; } } } else { // here is a new feed foreach ($array[$i] as $attr => $val) { $newArray[$xmlUrl][$attr] = $val; } $newArray[$xmlUrl]['folders'] = $listFolders; } } } } return $newArray; } } class PageBuilder { private $tpl; // For lazy initialization private $pageClass; public $var = array(); public function __construct($pageClass) { $this->tpl = false; $this->pageClass = $pageClass; } private function initialize() { $this->tpl = true; $ref = empty($_SERVER['HTTP_REFERER']) ? '' : $_SERVER['HTTP_REFERER']; $this->assign('referer', $ref); } // public function assign($variable, $value = null) { if ($this->tpl === false) { $this->initialize(); // Lazy initialization } if (is_array($variable)) { $this->var += $variable; } else { $this->var[$variable] = $value; } } public function renderPage($page) { if ($this->tpl===false) { $this->initialize(); // Lazy initialization } $method = $page.'Tpl'; if (method_exists($this->pageClass, $method)) { $this->assign('template', $page); $classPage = new $this->pageClass; $classPage->init($this->var); ob_start(); $classPage->$method(); ob_end_flush(); } else { die("renderPage does not exist: ".$page); } } } class Session { private static $_instance; public static $inactivityTimeout = 3600; public static $disableSessionProtection = false; public static $banFile = 'ipbans.php'; public static $banAfter = 4; public static $banDuration = 1800; private function __construct($banFile) { // Check ban configuration self::$banFile = $banFile; if (!is_file(self::$banFile)) { file_put_contents(self::$banFile, "array(),'BANS'=>array()),true).";\n?>"); } include self::$banFile; // Force cookie path (but do not change lifetime) $cookie=session_get_cookie_params(); // Default cookie expiration and path. $cookiedir = ''; if(dirname($_SERVER['SCRIPT_NAME'])!='/') { $cookiedir = dirname($_SERVER["SCRIPT_NAME"]).'/'; } session_set_cookie_params($cookie['lifetime'], $cookiedir); // Use cookies to store session. ini_set('session.use_cookies', 1); // Force cookies for session (phpsessionID forbidden in URL) ini_set('session.use_only_cookies', 1); if (!session_id()) { // Prevent php to use sessionID in URL if cookies are disabled. ini_set('session.use_trans_sid', false); session_name('kriss'); session_start(); } } public static function init($banFile) { if (!isset(self::$_instance)) { self::$_instance = new Session($banFile); } } public static function banLoginFailed() { $ip = $_SERVER["REMOTE_ADDR"]; $gb = $GLOBALS['IPBANS']; if (!isset($gb['FAILURES'][$ip])) { $gb['FAILURES'][$ip] = 0; } $gb['FAILURES'][$ip]++; if ($gb['FAILURES'][$ip] > (self::$banAfter-1)) { $gb['BANS'][$ip]= time() + self::$banDuration; } $GLOBALS['IPBANS'] = $gb; file_put_contents(self::$banFile, ""); } function banLoginOk() { $ip = $_SERVER["REMOTE_ADDR"]; $gb = $GLOBALS['IPBANS']; unset($gb['FAILURES'][$ip]); unset($gb['BANS'][$ip]); $GLOBALS['IPBANS'] = $gb; file_put_contents(self::$banFile, ""); } function banCanLogin() { $ip = $_SERVER["REMOTE_ADDR"]; $gb = $GLOBALS['IPBANS']; if (isset($gb['BANS'][$ip])) { // User is banned. Check if the ban has expired: if ($gb['BANS'][$ip] <= time()) { // Ban expired, user can try to login again. unset($gb['FAILURES'][$ip]); unset($gb['BANS'][$ip]); file_put_contents(self::$banFile, ""); return true; // Ban has expired, user can login. } return false; // User is banned. } return true; // User is not banned. } private static function _allIPs() { $ip = $_SERVER["REMOTE_ADDR"]; $ip.= isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? '_'.$_SERVER['HTTP_X_FORWARDED_FOR'] : ''; $ip.= isset($_SERVER['HTTP_CLIENT_IP']) ? '_'.$_SERVER['HTTP_CLIENT_IP'] : ''; return $ip; } public static function login ( $login, $password, $loginTest, $passwordTest, $pValues = array()) { if (!self::banCanLogin()) { die('I said: NO. You are banned for the moment. Go away.'); } if ($login == $loginTest && $password==$passwordTest) { self::banLoginOk(); // Generate unique random number to sign forms (HMAC) $_SESSION['uid'] = sha1(uniqid('', true).'_'.mt_rand()); $_SESSION['ip'] = Session::_allIPs(); $_SESSION['username'] = $login; // Set session expiration. $_SESSION['expires_on'] = time() + Session::$inactivityTimeout; foreach ($pValues as $key => $value) { $_SESSION[$key] = $value; } return true; } self::banLoginFailed(); Session::logout(); return false; } public static function logout() { unset($_SESSION['uid'], $_SESSION['ip'], $_SESSION['expires_on']); } public static function isLogged() { if (!isset ($_SESSION['uid']) || (Session::$disableSessionProtection == false && $_SESSION['ip']!=Session::_allIPs()) || time()>=$_SESSION['expires_on']) { Session::logout(); return false; } // User accessed a page : Update his/her session expiration date. if (time()+Session::$inactivityTimeout > $_SESSION['expires_on']) { $_SESSION['expires_on'] = time()+Session::$inactivityTimeout; } return true; } public static function getToken($salt = '') { if (!isset($_SESSION['tokens'])) { $_SESSION['tokens']=array(); } // We generate a random string and store it on the server side. $rnd = sha1(uniqid('', true).'_'.mt_rand().$salt); $_SESSION['tokens'][$rnd]=1; return $rnd; } public static function isToken($token) { if (isset($_SESSION['tokens'][$token])) { unset($_SESSION['tokens'][$token]); // Token is used: destroy it. return true; // Token is ok. } return false; // Wrong token, or already used. } }//end class // Check if php version is correct MyTool::initPHP(); // Initialize Session Session::init(BAN_FILE); // XSRF protection with token if (!empty($_POST)) { if (!Session::isToken($_POST['token'])) { die('Wrong token.'); } } unset($_SESSION['tokens']); $pb = new PageBuilder('FeedPage'); $kfp = new FeedPage(STYLE_FILE); $kfc = new FeedConf(CONFIG_FILE, FEED_VERSION); $kf = new Feed(DATA_FILE, CACHE_DIR, $kfc); // List or Expanded ? $view = $kfc->view; // show or hide list of feeds ? $listFeeds = $kfc->listFeeds; // All or Unread ? $filter = $kfc->filter; // newerFirst or olderFirst $order = $kfc->order; // number of item by page $byPage = $kfc->getByPage(); // Hash : 'all', feed hash or folder hash $currentHash = $kfc->getCurrentHash(); // Query $query = '?'; if (!empty($currentHash) and $currentHash !== 'all') { $query = '?currentHash='.$currentHash.'&'; } $pb->assign('view', $view); $pb->assign('listFeeds', $listFeeds); $pb->assign('filter', $filter); $pb->assign('order', $order); $pb->assign('byPage', $byPage); $pb->assign('currentHash', $currentHash); $pb->assign('query', $query); $pb->assign('redirector', $kfc->redirector); $pb->assign('shaarli', htmlspecialchars($kfc->shaarli)); $pb->assign('autoreadItem', $kfc->autoreadItem); $pb->assign('autoreadPage', $kfc->autoreadPage); $pb->assign('autohide', $kfc->autohide); $pb->assign('autofocus', $kfc->autofocus); $pb->assign('autoupdate', $kfc->autoUpdate); $pb->assign('addFavicon', $kfc->addFavicon); $pb->assign('version', FEED_VERSION); $pb->assign('kfurl', MyTool::getUrl()); if (isset($_GET['login'])) { // Login if (!empty($_POST['login']) && !empty($_POST['password']) ) { if (Session::login( $kfc->login, $kfc->hash, $_POST['login'], sha1($_POST['password'].$_POST['login'].$kfc->salt) )) { if (!empty($_POST['longlastingsession'])) { // (31536000 seconds = 1 year) $_SESSION['longlastingsession'] = 31536000; $_SESSION['expires_on'] = time() + $_SESSION['longlastingsession']; session_set_cookie_params($_SESSION['longlastingsession']); } else { session_set_cookie_params(0); // when browser closes } session_regenerate_id(true); MyTool::redirect(); } die("Login failed !"); } else { $pb->assign('pagetitle', 'Login - '.strip_tags($kfc->title)); $pb->renderPage('login'); } } elseif (isset($_GET['logout'])) { //Logout Session::logout(); MyTool::redirect(); } elseif (isset($_GET['ajax'])) { $kf->loadData(); $needSave = false; $result = array(); if (isset($_GET['current'])) { $result['item'] = $kf->getItem($_GET['current'], false); $result['item']['itemHash'] = $_GET['current']; } if (isset($_GET['read'])) { $needSave = $kf->mark($_GET['read'], 1); if ($needSave) { $result['read'] = $_GET['read']; } } if (isset($_GET['unread'])) { $needSave = $kf->mark($_GET['unread'], 0); if ($needSave) { $result['unread'] = $_GET['unread']; } } if (isset($_GET['toggleFolder'])) { $needSave = $kf->toggleFolder($_GET['toggleFolder']); } if (isset($_GET['page'])) { $listItems = $kf->getItems($currentHash, $filter); $currentPage = $_GET['page']; $index = ($currentPage - 1) * $byPage; $results = array_slice($listItems, $index, $byPage + 1, true); $result['page'] = array(); $firstIndex = -1; if (isset($_GET['last'])) { $firstIndex = array_search($_GET['last'], array_keys($results)); if ($firstIndex === false) { $firstIndex = -1; } } $i = 0; foreach(array_slice($results, $firstIndex + 1, count($results) - $firstIndex - 1, true) as $itemHash => $item) { $result['page'][$i] = $kf->getItem($itemHash, false); $result['page'][$i]['read'] = $item[1]; $i++; } } if (isset($_GET['update'])) { if (Session::isLogged()) { if (empty($_GET['update'])) { $result['update']['feeds'] = array(); $feedsHash = $kf->orderFeedsForUpdate(array_keys($kf->getFeeds())); foreach ($feedsHash as $feedHash) { $feed = $kf->getFeed($feedHash); $result['update']['feeds'][] = array($feedHash, $feed['title'], (int) ((time() - $feed['lastUpdate']) / 60), $kf->getTimeUpdate($feed)); } } else { $feed = $kf->getFeed($_GET['update']); $info = $kf->updateChannel($_GET['update']); if (empty($info['error'])) { $info['error'] = $feed['description']; } else { $info['error'] = $kf->getError($info['error']); } $info['newItems'] = array_keys($info['newItems']); $result['update'] = $info; } } else { $result['update'] = false; } } if ($needSave) { $kf->writeData(); } MyTool::renderJson($result); } elseif (isset($_GET['help'])) { $pb->assign('pagetitle', 'Help for KrISS feed'); $pb->renderPage('help'); } elseif ((isset($_GET['update']) && (Session::isLogged() || (isset($_GET['cron']) && $_GET['cron'] === sha1($kfc->salt.$kfc->hash)))) || (isset($argv) && count($argv) >= 3 && $argv[1] == 'update' && $argv[2] == sha1($kfc->salt.$kfc->hash))) { // Update $kf->loadData(); $forceUpdate = false; if (isset($_GET['force'])) { $forceUpdate = true; } $feedsHash = array(); $hash = 'all'; if (isset($_GET['update'])) { $hash = $_GET['update']; } // type : 'feed', 'folder', 'all', 'item' $type = $kf->hashType($hash); switch($type) { case 'feed': $feedsHash[] = $hash; break; case 'folder': $feedsHash = $kf->getFeedsHashFromFolderHash($hash); break; case 'all': case '': $feedsHash = array_keys($kf->getFeeds()); break; case 'item': default: break; } if (isset($_GET['cron']) || isset($argv) && count($argv) >= 3) { $kf->updateFeedsHash($feedsHash, $forceUpdate); } else { $pb->assign('kf', $kf); $pb->assign('feedsHash', $feedsHash); $pb->assign('forceUpdate', $forceUpdate); $pb->assign('pagetitle', 'Update'); $pb->renderPage('update'); } } elseif (isset($_GET['config']) && Session::isLogged()) { // Config if (isset($_POST['save'])) { if (isset($_POST['disableSessionProtection'])) { $_POST['disableSessionProtection'] = '1'; } else { $_POST['disableSessionProtection'] = '0'; } $kfc->hydrate($_POST); MyTool::redirect(); } elseif (isset($_POST['cancel'])) { MyTool::redirect(); } else { $menu = $kfc->getMenu(); $paging = $kfc->getPaging(); $pb->assign('page', 'config'); $pb->assign('pagetitle', 'Config - '.strip_tags($kfc->title)); $pb->assign('kfctitle', htmlspecialchars($kfc->title)); $pb->assign('kfcredirector', htmlspecialchars($kfc->redirector)); $pb->assign('kfcshaarli', htmlspecialchars($kfc->shaarli)); $pb->assign('kfclocale', htmlspecialchars($kfc->locale)); $pb->assign('kfcmaxitems', htmlspecialchars($kfc->maxItems)); $pb->assign('kfcmaxupdate', htmlspecialchars($kfc->maxUpdate)); $pb->assign('kfcpublic', (int) $kfc->public); $pb->assign('kfccron', sha1($kfc->salt.$kfc->hash)); $pb->assign('kfcautoreaditem', (int) $kfc->autoreadItem); $pb->assign('kfcautoreadpage', (int) $kfc->autoreadPage); $pb->assign('kfcautoupdate', (int) $kfc->autoUpdate); $pb->assign('kfcautohide', (int) $kfc->autohide); $pb->assign('kfcautofocus', (int) $kfc->autofocus); $pb->assign('kfcaddfavicon', (int) $kfc->addFavicon); $pb->assign('kfcdisablesessionprotection', (int) $kfc->disableSessionProtection); $pb->assign('kfcmenu', $menu); $pb->assign('kfcpaging', $paging); $pb->renderPage('config'); } } elseif (isset($_GET['import']) && Session::isLogged()) { // Import if (isset($_POST['import'])) { // If file is too big, some form field may be missing. if ((!isset($_FILES)) || (isset($_FILES['filetoupload']['size']) && $_FILES['filetoupload']['size']==0) ) { $rurl = empty($_SERVER['HTTP_REFERER']) ? '?' : $_SERVER['HTTP_REFERER']; echo ''; exit; } $kf->loadData(); $kf->setData(Opml::importOpml($kf->getData())); $kf->sortFeeds(); $kf->writeData(); exit; } else if (isset($_POST['cancel'])) { MyTool::redirect(); } else { $pb->assign('pagetitle', 'Import'); $pb->renderPage('import'); } } elseif (isset($_GET['export']) && Session::isLogged()) { // Export $kf->loadData(); Opml::exportOpml($kf->getFeeds(), $kf->getFolders()); } elseif (isset($_GET['add']) && Session::isLogged()) { // Add feed $kf->loadData(); if (isset($_POST['newfeed']) && !empty($_POST['newfeed'])) { if ($kf->addChannel($_POST['newfeed'])) { // Add success $folders = array(); if (!empty($_POST['folders'])) { foreach ($_POST['folders'] as $hashFolder) { $folders[] = $hashFolder; } } if (!empty($_POST['newfolder'])) { $newFolderHash = MyTool::smallHash($_POST['newfolder']); $kf->addFolder($_POST['newfolder'], $newFolderHash); $folders[] = $newFolderHash; } $hash = MyTool::smallHash($_POST['newfeed']); $kf->editFeed($hash, '', '', $folders, ''); $kf->sortFeeds(); $kf->writeData(); MyTool::redirect('?currentHash='.$hash); } else { // Add fail $returnurl = empty($_SERVER['HTTP_REFERER']) ? MyTool::getUrl() : $_SERVER['HTTP_REFERER']; echo ''; exit; } } $newfeed = ''; if (isset($_GET['newfeed'])) { $newfeed = htmlspecialchars($_GET['newfeed']); } $pb->assign('page', 'add'); $pb->assign('pagetitle', 'Add a new feed'); $pb->assign('newfeed', $newfeed); $pb->assign('folders', $kf->getFolders()); $pb->renderPage('addFeed'); } elseif (isset($_GET['toggleFolder']) && Session::isLogged()) { $kf->loadData(); if (isset($_GET['toggleFolder'])) { $kf->toggleFolder($_GET['toggleFolder']); } $kf->writeData(); MyTool::redirect(); } elseif ((isset($_GET['read']) || isset($_GET['unread'])) && Session::isLogged()) { // mark all as read : item, feed, folder, all $kf->loadData(); $read = 1; if (isset($_GET['read'])) { $hash = $_GET['read']; $read = 1; } else { $hash = $_GET['unread']; $read = 0; } $needSave = $kf->mark($hash, $read); if ($needSave) { $kf->writeData(); } // type : 'feed', 'folder', 'all', 'item' $type = $kf->hashType($hash); if ($type === 'item') { MyTool::redirect($query.'current='.$hash); } else { if ($filter === 'unread' && $read === 1) { MyTool::redirect('?'); } else { MyTool::redirect($query); } } } elseif (isset($_GET['edit']) && Session::isLogged()) { // Edit feed, folder, all $kf->loadData(); $pb->assign('page', 'edit'); $pb->assign('pagetitle', 'edit'); $hash = substr(trim($_GET['edit'], '/'), 0, 6); // type : 'feed', 'folder', 'all', 'item' $type = $kf->hashType($currentHash); $type = $kf->hashType($hash); switch($type) { case 'feed': if (isset($_POST['save'])) { $title = $_POST['title']; $description = $_POST['description']; $folders = array(); if (!empty($_POST['folders'])) { foreach ($_POST['folders'] as $hashFolder) { $folders[] = $hashFolder; } } if (!empty($_POST['newfolder'])) { $newFolderHash = MyTool::smallHash($_POST['newfolder']); $kf->addFolder($_POST['newfolder'], $newFolderHash); $folders[] = $newFolderHash; } $timeUpdate = $_POST['timeUpdate']; $kf->editFeed($hash, $title, $description, $folders, $timeUpdate); $kf->writeData(); MyTool::redirect(); } elseif (isset($_POST['delete'])) { $kf->removeFeed($hash); $kf->writeData(); MyTool::redirect('?'); } elseif (isset($_POST['cancel'])) { MyTool::redirect(); } else { $feed = $kf->getFeed($hash); if (!empty($feed)) { $lastUpdate = 'need update'; if (!$kf->needUpdate($feed)) { $diff = (int) (time() - $feed['lastUpdate']); $lastUpdate = (int) ($diff / 60) . ' m ' . (int) ($diff % 60) . ' s'; } $pb->assign('feed', $feed); $pb->assign('folders', $kf->getFolders()); $pb->assign('lastUpdate', $lastUpdate); $pb->renderPage('editFeed'); } else { MyTool::redirect(); } } break; case 'folder': if (isset($_POST['save'])) { $oldFolderTitle = $kf->getFolderTitle($hash); $newFolderTitle = $_POST['foldertitle']; if ($oldFolderTitle !== $newFolderTitle) { $kf->renameFolder($hash, $newFolderTitle); $kf->writeData(); } if (empty($newFolderTitle)) { MyTool::redirect('?'); } else { MyTool::redirect('?currentHash='.MyTool::smallHash($newFolderTitle)); } } elseif (isset($_POST['cancel'])) { MyTool::redirect(); } else { $folderTitle = $kf->getFolderTitle($hash); $pb->assign('foldertitle', htmlspecialchars($folderTitle)); $pb->renderPage('editFolder'); } break; case 'all': if (isset($_POST['save'])) { $feedsHash = array(); foreach ($_POST['feeds'] as $feedHash) { $feedsHash[] = $feedHash; } foreach ($feedsHash as $feedHash) { $feed = $kf->getFeed($feedHash); $addFoldersHash = $feed['foldersHash']; if (!empty($_POST['addfolders'])) { foreach ($_POST['addfolders'] as $folderHash) { if (!in_array($folderHash, $addFoldersHash)) { $addFoldersHash[] = $folderHash; } } } if (!empty($_POST['addnewfolder'])) { $newFolderHash = MyTool::smallHash($_POST['addnewfolder']); $kf->addFolder($_POST['addnewfolder'], $newFolderHash); $addFoldersHash[] = $newFolderHash; } $removeFoldersHash = array(); if (!empty($_POST['removefolders'])) { foreach ($_POST['removefolders'] as $folderHash) { $removeFoldersHash[] = $folderHash; } } $addFoldersHash = array_diff($addFoldersHash, $removeFoldersHash); $kf->editFeed( $feedHash, '', '', $addFoldersHash, '' ); } $kf->writeData(); MyTool::redirect(); } elseif (isset($_POST['delete'])) { foreach ($_POST['feeds'] as $feedHash) { $kf->removeFeed($feedHash); } $kf->writeData(); MyTool::redirect(); } elseif (isset($_POST['cancel'])) { MyTool::redirect(); } else { $folders = $kf->getFolders(); $listFeeds = $kf->getFeeds(); $pb->assign('folders', $folders); $pb->assign('listFeeds', $listFeeds); $pb->renderPage('editAll'); } break; case 'item': default: MyTool::redirect(); break; } } elseif (isset($_GET['shaarli'])) { $kf->loadData(); $item = $kf->getItem($_GET['shaarli'], false); $shaarli = $kfc->shaarli; // remove sel used with javascript $shaarli = str_replace('${sel}', '', $shaarli); $url = htmlspecialchars_decode($item['link']); $via = htmlspecialchars_decode($item['via']); $title = htmlspecialchars_decode($item['title']); if (parse_url($url, PHP_URL_HOST) !== parse_url($via, PHP_URL_HOST)) { $via = 'via '.$via; } else { $via = ''; } $shaarli = str_replace('${url}', urlencode($url), $shaarli); $shaarli = str_replace('${title}', urlencode($title), $shaarli); $shaarli = str_replace('${via}', urlencode($via), $shaarli); header('Location: '.$shaarli); } else { if (Session::isLogged() || $kfc->public) { $kf->loadData(); if ($kf->updateItems()) { $kf->writeData(); } $listItems = $kf->getItems($currentHash, $filter); $listHash = array_keys($listItems); $currentItemHash = ''; if (isset($_GET['current']) && !empty($_GET['current'])) { $currentItemHash = $_GET['current']; } if (isset($_GET['next']) && !empty($_GET['next'])) { $currentItemHash = $_GET['next']; if ($kfc->autoreadItem) { if ($kf->mark($currentItemHash, 1)) { if ($filter == 'unread') { unset($listItems[$currentItemHash]); } $kf->writeData(); } } } if (isset($_GET['previous']) && !empty($_GET['previous'])) { $currentItemHash = $_GET['previous']; } if (empty($currentItemHash)) { $currentPage = $kfc->getCurrentPage(); $index = ($currentPage - 1) * $byPage; } else { $index = array_search($currentItemHash, $listHash); if (isset($_GET['next'])) { if ($index < count($listHash)-1) { $index++; } } if (isset($_GET['previous'])) { if ($index > 0) { $index--; } } } if ($index < count($listHash)) { $currentItemHash = $listHash[$index]; } else { $index = count($listHash) - 1; } $unread = 0; foreach ($listItems as $itemHash => $item) { if ($item[1] === 0) { $unread++; } } // pagination $currentPage = (int) ($index/$byPage)+1; if ($currentPage <= 0) { $currentPage = 1; } $begin = ($currentPage - 1) * $byPage; $maxPage = (count($listItems) <= $byPage) ? '1' : ceil(count($listItems) / $byPage); $nbItems = count($listItems); // list items $listItems = array_slice($listItems, $begin, $byPage, true); // type : 'feed', 'folder', 'all', 'item' $currentHashType = $kf->hashType($currentHash); $hashView = ''; switch($currentHashType){ case 'all': $hashView = ''.$unread.' unread items'; break; case 'feed': $hashView = 'Feed ('.$kf->getFeedTitle($currentHash).'): '.''.$unread.' unread items'; break; case 'folder': $hashView = 'Folder ('.$kf->getFolderTitle($currentHash).'): '.$unread.' unread items'; break; default: $hashView = ''.$unread.' unread items'; break; } $menu = $kfc->getMenu(); $paging = $kfc->getPaging(); $pb->assign('menu', $menu); $pb->assign('paging', $paging); $pb->assign('currentHashType', $currentHashType); $pb->assign('currentHashView', $hashView); $pb->assign('currentPage', (int) $currentPage); $pb->assign('maxPage', (int) $maxPage); $pb->assign('currentItemHash', $currentItemHash); $pb->assign('nbItems', $nbItems); $pb->assign('items', $listItems); if ($listFeeds == 'show') { $pb->assign('feedsView', $kf->getFeedsView()); } $pb->assign('kf', $kf); $pb->assign('pagetitle', strip_tags($kfc->title)); $pb->renderPage('index'); } else { $pb->assign('pagetitle', 'Login - '.strip_tags($kfc->title)); if (!empty($_SERVER['QUERY_STRING'])) { $pb->assign('referer', MyTool::getUrl().'?'.$_SERVER['QUERY_STRING']); } $pb->renderPage('login'); } } //print(number_format(microtime(true)-START_TIME,3).' secondes');