diff options
Diffstat (limited to 'common/app')
| -rwxr-xr-x | common/app/app.php | 37 | ||||
| -rw-r--r-- | common/app/classes/CSRF.php | 55 | ||||
| -rw-r--r-- | common/app/classes/Cache.php | 254 | ||||
| -rw-r--r-- | common/app/classes/Opml.php | 70 | ||||
| -rw-r--r-- | common/app/classes/OpmlManager.php | 50 | ||||
| -rw-r--r-- | common/app/classes/Planet.php | 266 | ||||
| -rw-r--r-- | common/app/classes/PlanetConfig.php | 166 | ||||
| -rw-r--r-- | common/app/classes/PlanetError.php | 23 | ||||
| -rw-r--r-- | common/app/classes/PlanetFeed.php | 20 | ||||
| -rw-r--r-- | common/app/classes/PlanetItem.php | 11 | ||||
| -rwxr-xr-x | common/app/classes/Simplel10n.php | 52 | ||||
| -rw-r--r-- | common/app/helpers.php | 130 | ||||
| -rw-r--r-- | common/app/l10n/de.lang | 219 | ||||
| -rw-r--r-- | common/app/l10n/en.lang | 4 | ||||
| -rw-r--r-- | common/app/l10n/es.lang | 244 | ||||
| -rwxr-xr-x | common/app/l10n/extract.php | 13 | ||||
| -rwxr-xr-x | common/app/l10n/fr.lang | 4 | 
17 files changed, 1543 insertions, 75 deletions
diff --git a/common/app/app.php b/common/app/app.php index 0703ac5..0797cc7 100755 --- a/common/app/app.php +++ b/common/app/app.php @@ -1,29 +1,15 @@  <?php -//Debug ? -$debug = isset($_GET['debug']) ? $_GET['debug'] : 0; -if ($debug) { -    error_reporting(E_ALL); -} else { -    error_reporting(0); -} - -include(dirname(__FILE__).'/lib/lib.opml.php'); -include(dirname(__FILE__).'/lib/simplepie/simplepie.inc'); -include(dirname(__FILE__).'/lib/spyc-0.5/spyc.php'); +error_reporting(0); -include_once(dirname(__FILE__).'/classes/PlanetConfig.php'); -include_once(dirname(__FILE__).'/classes/PlanetFeed.php'); -include_once(dirname(__FILE__).'/classes/PlanetItem.php'); -include_once(dirname(__FILE__).'/classes/PlanetError.php'); -include_once(dirname(__FILE__).'/classes/Planet.class.php'); -include_once(dirname(__FILE__).'/classes/Simplel10n.class.php'); +require_once __DIR__.'/../vendor/autoload.php'; -$savedConfig  = dirname(__FILE__).'/../custom/config.yml'; -$moon_version = file_get_contents(dirname(__FILE__).'/../VERSION'); +$savedConfig  = __DIR__.'/../custom/config.yml'; +$moon_version = file_get_contents(__DIR__.'/../VERSION'); -if (is_file($savedConfig)){ +session_start(); +if (is_installed()) {      $conf = Spyc::YAMLLoad($savedConfig);      // this is a check to upgrade older config file without l10n @@ -35,13 +21,12 @@ if (is_file($savedConfig)){      $PlanetConfig = new PlanetConfig($conf);      $Planet = new Planet($PlanetConfig); -} -$l10n = new Simplel10n($conf['locale']); +    if ($conf['debug']) { +        error_reporting(E_ALL); +    } -// this is an helper function. We will usually use that function and not Simplel10n::getString() -function _g($str, $comment='') { -    return Simplel10n::getString($str);  } - +$l10n = new Simplel10n($conf['locale']); +$csrf = new CSRF(); diff --git a/common/app/classes/CSRF.php b/common/app/classes/CSRF.php new file mode 100644 index 0000000..9a700cf --- /dev/null +++ b/common/app/classes/CSRF.php @@ -0,0 +1,55 @@ +<?php + +class CSRF +{ +    /** @var string */ +    const HMAC_ALGORITHM = 'sha1'; + +    /** @var string */ +    const SESSION_KEY_NAME = '_csrf_key'; + +    /** +     * Ensure that a CSRF token is valid for a given action. +     * +     * @param  string $token +     * @param  string $action +     * @return bool +     */ +    public static function verify($token = '', $action = null) +    { +        if (!is_string($token) || !is_string($action)) { +            return false; +        } + +        $known = self::generate($action); +        return hash_equals($known, $token); +    } + +    /** +     * Generate a CSRF token for a given action. +     * +     * @param  string $action +     * @throws InvalidArgumentException +     * @return string +     */ +    public static function generate($action = null) +    { +        if (!is_string($action)) { +            throw new InvalidArgumentException('A valid action must be defined.'); +        } +        return hash_hmac(self::HMAC_ALGORITHM, $action, self::getKey()); +    } + +    /** +     * Get HMAC key. +     * +     * @return string +     */ +    public static function getKey() +    { +        if (empty($_SESSION[self::SESSION_KEY_NAME])) { +            $_SESSION[self::SESSION_KEY_NAME] = random_bytes(16); +        } +        return $_SESSION[self::SESSION_KEY_NAME]; +    } +} diff --git a/common/app/classes/Cache.php b/common/app/classes/Cache.php new file mode 100644 index 0000000..b73182e --- /dev/null +++ b/common/app/classes/Cache.php @@ -0,0 +1,254 @@ +<?php +/** +* This library is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this software; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA +*  +* © Copyright 2005 Richard Heyes +*/ + +/** +* Caching Libraries for PHP5 +*  +* Handles data and output caching. Defaults to /dev/shm +* (shared memory). All methods are static. +*  +* Eg: (output caching) +*  +* if (!OutputCache::Start('group', 'unique id', 600)) { +*  +*   // ... Output +*  +*   OutputCache::End(); +* } +*  +* Eg: (data caching) +*  +* if (!$data = DataCache::Get('group', 'unique id')) { +*  +*   $data = time(); +*  +*   DataCache::Put('group', 'unique id', 10, $data); +* } +*  +* echo $data; +*/ +    class Cache +    { +        /** +        * Whether caching is enabled +        * @var bool +        */ +        public static $enabled = true; + +        /** +        * Place to store the cache files +        * @var string +        */ +        protected static $store = '/dev/shm/'; +         +        /** +        * Prefix to use on cache files +        * @var string +        */ +        protected static $prefix = 'cache_'; + +        /** +        * Stores data +        *  +        * @param string $group Group to store data under +        * @param string $id    Unique ID of this data +        * @param int    $ttl   How long to cache for (in seconds) +        */ +        protected static function write($group, $id, $ttl, $data) +        { +            $filename = self::getFilename($group, $id); +             +            if (self::$enabled && $fp = fopen($filename, 'xb')) { +             +                if (flock($fp, LOCK_EX)) { +                    fwrite($fp, $data); +                } +                fclose($fp); +                 +                // Set filemtime +                touch($filename, time() + $ttl); +            } +        } +         +        /** +        * Reads data +        *  +        * @param string $group Group to store data under +        * @param string $id    Unique ID of this data +        */ +        protected static function read($group, $id) +        { +            $filename = self::getFilename($group, $id); +             +            return file_get_contents($filename); +        } +         +        /** +        * Determines if an entry is cached +        *  +        * @param string $group Group to store data under +        * @param string $id    Unique ID of this data +        */ +        protected static function isCached($group, $id) +        { +            $filename = self::getFilename($group, $id); + +            if (self::$enabled && file_exists($filename) && filemtime($filename) > time()) { +                return true; +            } + +            @unlink($filename); + +            return false; +        } +         +        /** +        * Builds a filename/path from group, id and +        * store. +        *  +        * @param string $group Group to store data under +        * @param string $id    Unique ID of this data +        */ +        protected static function getFilename($group, $id) +        { +            $id = md5($id); + +            return self::$store . self::$prefix . "{$group}_{$id}"; +        } +         +        /** +        * Sets the filename prefix to use +        *  +        * @param string $prefix Filename Prefix to use +        */ +        public static function setPrefix($prefix) +        { +            self::$prefix = $prefix; +        } + +        /** +        * Sets the store for cache files. Defaults to +        * /dev/shm. Must have trailing slash. +        *  +        * @param string $store The dir to store the cache data in +        */ +        public static function setStore($store) +        { +            self::$store = $store; +        } +    } +     +    /** +    * Output Cache extension of base caching class +    */ +    class OutputCache extends Cache +    { +        /** +        * Group of currently being recorded data +        * @var string +        */ +        private static $group; +         +        /** +        * ID of currently being recorded data +        * @var string +        */ +        private static $id; +         +        /** +        * Ttl of currently being recorded data +        * @var int +        */ +        private static $ttl; + +        /** +        * Starts caching off. Returns true if cached, and dumps +        * the output. False if not cached and start output buffering. +        *  +        * @param  string $group Group to store data under +        * @param  string $id    Unique ID of this data +        * @param  int    $ttl   How long to cache for (in seconds) +        * @return bool          True if cached, false if not +        */ +        public static function Start($group, $id, $ttl) +        { +            if (self::isCached($group, $id)) { +                echo self::read($group, $id); +                return true; +             +            } else { +                 +                ob_start(); +                 +                self::$group = $group; +                self::$id    = $id; +                self::$ttl   = $ttl; +                 +                return false; +            } +        } +         +        /** +        * Ends caching. Writes data to disk. +        */ +        public static function End() +        { +            $data = ob_get_contents(); +            ob_end_flush(); +             +            self::write(self::$group, self::$id, self::$ttl, $data); +        } +    } +     +    /** +    * Data cache extension of base caching class +    */ +    class DataCache extends Cache +    { +     +        /** +        * Retrieves data from the cache +        *  +        * @param  string $group Group this data belongs to +        * @param  string $id    Unique ID of the data +        * @return mixed         Either the resulting data, or null +        */ +        public static function Get($group, $id) +        { +            if (self::isCached($group, $id)) { +                return unserialize(self::read($group, $id)); +            } +             +            return null; +        } +         +        /** +        * Stores data in the cache +        *  +        * @param string $group Group this data belongs to +        * @param string $id    Unique ID of the data +        * @param int    $ttl   How long to cache for (in seconds) +        * @param mixed  $data  The data to store +        */ +        public static function Put($group, $id, $ttl, $data) +        { +            self::write($group, $id, $ttl, serialize($data)); +        } +    } +?>
\ No newline at end of file diff --git a/common/app/classes/Opml.php b/common/app/classes/Opml.php new file mode 100644 index 0000000..ae9e8b1 --- /dev/null +++ b/common/app/classes/Opml.php @@ -0,0 +1,70 @@ +<?php + +class Opml +{ +    var $_xml = null; +    var $_currentTag = ''; + +    var $title = ''; +    var $entries = array(); +    var $map  = +        array( +            'URL'         => 'website', +            'HTMLURL'     => 'website', +            'TEXT'        => 'name', +            'TITLE'       => 'name', +            'XMLURL'      => 'feed', +            'DESCRIPTION' => 'description', +            'ISDOWN'      => 'isDown' +        ); + + +    function parse($data) +    { +        $this->_xml = xml_parser_create('UTF-8'); +        //xml_parser_set_option($this->_xml, XML_OPTION_CASE_FOLDING, false); +        //xml_parser_set_option($this->_xml, XML_OPTION_SKIP_WHITE, true); +        xml_set_object($this->_xml, $this); +        xml_set_element_handler($this->_xml,'_openTag','_closeTag'); +        xml_set_character_data_handler ($this->_xml, '_cData'); + +        xml_parse($this->_xml,$data); +        xml_parser_free($this->_xml); +        return $this->entries; +    } + + +    function _openTag($p,$tag,$attrs) +    { +        $this->_currentTag = $tag; + +        if ($tag == 'OUTLINE') +        { +            $i = count($this->entries); +            foreach (array_keys($this->map) as $key) +            { +                if (isset($attrs[$key])) { +                    $this->entries[$i][$this->map[$key]] = $attrs[$key]; +                } +            } +        } +    } + +    function _closeTag($p, $tag){ +        $this->_currentTag = ''; +    } + +    function _cData($p, $cdata){ +        if ($this->_currentTag == 'TITLE'){ +            $this->title = $cdata; +        } +    } + +    function getTitle(){ +        return $this->title; +    } + +    function getPeople(){ +        return $this->entries; +    } +}
\ No newline at end of file diff --git a/common/app/classes/OpmlManager.php b/common/app/classes/OpmlManager.php new file mode 100644 index 0000000..d3940b2 --- /dev/null +++ b/common/app/classes/OpmlManager.php @@ -0,0 +1,50 @@ +<?php + + +class OpmlManager +{ +    public static function load($file) +    { +        if (!file_exists($file)) { +            throw new Exception('OPML file not found!'); +        } + +        $opml = new Opml(); + +        //Remove BOM if needed +        $BOM = '/^/'; +        $fileContent = file_get_contents($file); +        $fileContent = preg_replace($BOM, '', $fileContent, 1); + +        //Parse +        $opml->parse($fileContent); + +        return $opml; +    } + +    /** +     * @param Opml   $opml +     * @param string $file +     */ +    public static function save($opml, $file){ +        $out = '<?xml version="1.0"?>'."\n"; +        $out.= '<opml version="1.1">'."\n"; +        $out.= '<head>'."\n"; +        $out.= '<title>'.htmlspecialchars($opml->getTitle()).'</title>'."\n"; +        $out.= '<dateCreated>'.date('c').'</dateCreated>'."\n"; +        $out.= '<dateModified>'.date('c').'</dateModified>'."\n"; +        $out.= '</head>'."\n"; +        $out.= '<body>'."\n"; +        foreach ($opml->entries as $person) { +            $out.= '<outline text="' . htmlspecialchars($person['name'], ENT_QUOTES) . '" htmlUrl="' . htmlspecialchars($person['website'], ENT_QUOTES) . '" xmlUrl="' . htmlspecialchars($person['feed'], ENT_QUOTES) . '" isDown="' . htmlspecialchars($person['isDown'], ENT_QUOTES) . '"/>'."\n"; +        } +        $out.= '</body>'."\n"; +        $out.= '</opml>'; + +        file_put_contents($file, $out); +    } + +    public static function backup($file){ +        copy($file, $file.'.bak'); +    } +} diff --git a/common/app/classes/Planet.php b/common/app/classes/Planet.php new file mode 100644 index 0000000..b146eaf --- /dev/null +++ b/common/app/classes/Planet.php @@ -0,0 +1,266 @@ +<?php +/* +Copyright (c) 2006, Maurice Svay +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +* Neither the name of Maurice Svay nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** + * Planet, main app class + */ +class Planet +{ +    /** @var PlanetConfig */ +    public $config; + +    /** @var PlanetItem[] */ +    public $items; + +    /** @var PlanetFeed[] */ +    public $people; + +    /** @var PlanetError[] */ +    public $errors; + +    /** +     * Planet constructor. +     * +     * @param PlanetConfig $config +     */ +    public function __construct($config=null) +    { +        $this->config = $config === null ? new PlanetConfig() : $config; + +        $this->items  = []; +        $this->people = []; +        $this->errors = []; +    } + +    /** +     * Compare the supplied password with the known one. +     * +     * This functions uses a type-safe and timing-safe comparison, in order to +     * improve the security of the authentication. +     * +     * Read more about this sort of attacks (used for the < PHP 5.6.0 implementation): +     *  - https://security.stackexchange.com/questions/83660/simple-string-comparisons-not-secure-against-timing-attacks +     *  - https://github.com/laravel/framework/blob/a1dc78820d2dbf207dbdf0f7075f17f7021c4ee8/src/Illuminate/Support/Str.php#L289 +     *  - https://github.com/symfony/security-core/blob/master/Util/StringUtils.php#L39 +     * +     * @param  string $known +     * @param  string $supplied +     * @return bool +     */ +    public static function authenticateUser($known = '', $supplied = '') +    { +        // The hash_equals function was introduced in PHP 5.6.0. If it's not +        // existing in the current context (PHP version too old), and to ensure +        // compatibility with those old interpreters, we'll have to provide +        // an PHP implementation of this function. +        if (function_exists('hash_equals')) { +            return hash_equals($known, $supplied); +        } + +        // Some implementation references can be found on the function comment. +        $knownLen = mb_strlen($known); +        if ($knownLen !== mb_strlen($supplied)) { +            return false; +        } + +        // Ensure that all the characters are the same, and continue until the +        // end of the string even if an difference was found. +        for ($i = 0, $comparison = 0; $i < $knownLen; $i++) { +            $comparison |= ord($known[$i]) ^ ord($supplied[$i]); +        } + +        return ($comparison === 0); +    } + +    /** +     * Getters +     */ +    public function getItems() +    { +        $this->items = $this->_filterItemsByCategory( +            $this->items, +            $this->config->getCategories()); + +        return $this->items; +    } + +    public function getPeople() +    { +        return $this->people; +    } + +    /** +     * Adds a feed to the planet. +     * +     * @param PlanetFeed $feed +     */ +    public function addPerson(&$feed) +    { +        $this->people[] = $feed; +    } + +    /** +     * Load people from an OPML. +     * +     * @param  string  $file File to load the OPML from. +     * @return integer Number of people loaded. +     */ +    public function loadOpml($file) +    { +        if (!is_file($file)) { +            $this->errors[] = new PlanetError(3, $file.' is missing.'); +            return 0; +        } + +        $opml = OpmlManager::load($file); +        $opml_people = $opml->getPeople(); +        foreach ($opml_people as $opml_person){ +            $person = new PlanetFeed( +                $opml_person['name'], +                $opml_person['feed'], +                $opml_person['website'], +                $opml_person['isDown'] +            ); +            $this->addPerson($person); +        } +        return count($opml_people); +    } + +    /** +     * Load feeds +     */ +    public function loadFeeds() +    { +        foreach ($this->people as $feed) { +            //Is down it's filled by cron.php, $Planet->download(1.0) proccess +            if (!$feed->isDown) { +                $feed->set_timeout(-1); +                $feed->init(); +                $this->items = array_merge($this->items, $feed->get_items()); +            } + +        } +        $this->sort(); +    } + +    /** +     * Fetch feeds and see if new data is present. +     * +     * @param float $max_load Percentage of feeds to load +     */ +    public function download($max_load=0.1) +    { +        $max_load_feeds = ceil(count($this->people) * $max_load); +        $opml = OpmlManager::load(__DIR__.'/../../custom/people.opml'); + +        foreach ($this->people as $feed) { +            //Avoid mass loading with variable cache duration +            $feed->set_cache_duration($this->config->getCacheTimeout()); + +            //Load only a few feeds, force other to fetch from the cache +            if (0 > $max_load_feeds--) { +                $feed->set_timeout(-1); +                $this->errors[] = new PlanetError(1, 'Forced from cache : '.$feed->getFeed()); +            } + +            // Bypass remote's SSL/TLS certificate if the user explicitly +            // asked for it in the configuration. +            if ($this->config->checkcerts === false) { +                $feed->set_curl_options([ +                    CURLOPT_SSL_VERIFYHOST => false, +                    CURLOPT_SSL_VERIFYPEER => false +                ]); +            } + +            $feed->init(); +            $isDown = ''; + +            // http://simplepie.org/wiki/reference/simplepie/merge_items ? +            if (($feed->data) && ($feed->get_item_quantity() > 0)){ +                $items = $feed->get_items(); +                $this->items = array_merge($this->items, $items); +            } else { +                $this->errors[] = new PlanetError(1, 'No items or down : ' . $feed->getFeed()); +                $isDown = '1'; +            } + +            foreach ($opml->entries as $key => $entrie) { +                if ($feed->getFeed() === $entrie['feed']) { +                    $opml->entries[$key]['isDown'] = $isDown; +                } +            } +        } + +        OpmlManager::save($opml, __DIR__.'/../../custom/people.opml'); +    } + +    public function sort() +    { +        usort($this->items, array('PlanetItem','compare')); +    } + +    /** +     * Filter out items that do not match at least one +     * of the defined categories. +     * +     * If there's no category, return all items. +     * +     * @param array  $items to filter +     * @param string $categories to filter against; may be a single word +     * or a comma-separated list of words. +     * +     * @return array resulting list of items +    */ +    public function _filterItemsByCategory($items, $categories = null) +    { +        $categories = trim($categories); + +        if (empty($categories)) +            return $items; + +        $categories         = array_map('trim', explode(',', strtolower($categories))); +        $cb_category_filter = +            function ($item) use ($categories) +            { +                if (!is_array($item_categories = $item->get_categories())) +                    return false; + +                $item_categories = array_map( +                    function ($i) { return strtolower($i->get_label()); }, +                    $item_categories +                ); + +                return array_intersect($categories, $item_categories); +            }; + +        return array_values(array_filter($items, $cb_category_filter)); +    } +} diff --git a/common/app/classes/PlanetConfig.php b/common/app/classes/PlanetConfig.php index a31938e..f3928bc 100644 --- a/common/app/classes/PlanetConfig.php +++ b/common/app/classes/PlanetConfig.php @@ -6,32 +6,45 @@  class PlanetConfig  { -    public $conf; - -    public function __construct($array) -    { -        $defaultConfig = array( -            'url'           => 'http://www.example.com/', -            'name'          => '', -            'locale'        => 'en', -            'items'         => 10, -            'shuffle'       => 0, -            'refresh'       => 240, -            'cache'         => 10, -            'nohtml'        => 0, -            'postmaxlength' => 0, -            'cachedir'      => './cache', -        ); - -        // User config -        $this->conf = $array; - -        // Complete config with default config -        foreach ($defaultConfig as $key => $value) { -            if (!isset($this->conf[$key])) { -                $this->conf[$key] = $value; -            } -        } +    protected $conf = []; + +    public static $defaultConfig = [ +        'url'           => 'http://www.example.com/', +        'name'          => '', +        'locale'        => 'en', +        'items'         => 10, +        'shuffle'       => 0, +        'refresh'       => 240, +        'cache'         => 10, +        'nohtml'        => 0, +        'postmaxlength' => 0, +        'categories'    => '', +        'cachedir'      => './cache', +        'debug'         => false, +        'checkcerts'    => true, +    ]; + +    /** +     * PlanetConfig constructor. +     * @param array $userConfig +     * @param bool  $useDefaultConfig +     */ +    public function __construct($userConfig = [], $useDefaultConfig = true) +    { +        $default = $useDefaultConfig ? self::$defaultConfig : array(); +        $this->conf = $this->merge($default, $userConfig); +    } + +    /** +     * Merge the configuration of the user in the default one. +     * +     * @param array $default +     * @param array $user +     * @return array +     */ +    protected function merge($default = [], $user = []) +    { +        return array_merge($default, $this->normalizeArrayKeys($user));      }      public function getUrl() @@ -58,7 +71,10 @@ class PlanetConfig          return $this->conf['cache'];      } -    //@TODO: drop this pref +    /** +     * @deprecated +     * @return mixed +     */      public function getShuffle()      {          return $this->conf['shuffle']; @@ -69,20 +85,110 @@ class PlanetConfig          return $this->conf['items'];      } -    //@TODO: drop this pref +    /** +     * @deprecated +     * @return mixed +     */      public function getNoHTML()      {          return $this->conf['nohtml'];      } -    //@TODO: drop this pref +    /** +     * @deprecated +     * @return mixed +     */      public function getPostMaxLength()      {          return $this->conf['postmaxlength'];      } +    public function getCategories() +    { +        return $this->conf['categories']; +    } +      public function toYaml()      { -        return Spyc::YAMLDump($this->conf,4); +        return Spyc::YAMLDump($this->conf, 4); +    } + +    /** +     * @return array +     */ +    public function toArray() +    { +        return $this->conf; +    } + +    public function getDebug() +    { +        return $this->conf['debug']; +    } + +    /** +     * @return array +     */ +    public function getDefaultConfig() +    { +        return self::$defaultConfig; +    } + +    /** +     * Normalize the name of a configuration key. +     * +     * @param  string $key +     * @return string +     */ +    protected function normalizeKeyName($key = null) +    { +        return strtolower($key); +    } + +    /** +     * Normalize all the keys of the array. +     * +     * @param  array $array +     * @return array +     */ +    protected function normalizeArrayKeys($array = []) +    { +        foreach ($array as $key => $value) { +            $normalized = $this->normalizeKeyName($key); +            if ($normalized !== $key) { +                $array[$this->normalizeKeyName($key)] = $value; +                unset($array[$key]); +            } +        } + +        return $array;      } + +    /** +     * Generic configuration getter. +     * +     * @return mixed|null +     */ +    public function __get($key) +    { +        $key = $this->normalizeKeyName($key); + +        return array_key_exists($key, $this->conf) ? +            $this->conf[$key] : +            null; +    } + +    /** +     * Generic configuration setter. +     * +     * @param $key +     * @param $value +     */ +    public function __set($key, $value) +    { +        $key = $this->normalizeKeyName($key); + +        $this->conf[$key] = $value; +    } +  } diff --git a/common/app/classes/PlanetError.php b/common/app/classes/PlanetError.php index 31923a3..e47ab1d 100644 --- a/common/app/classes/PlanetError.php +++ b/common/app/classes/PlanetError.php @@ -3,21 +3,30 @@  class PlanetError  {      public $level; +    public $levels = array( +        1 => 'notice', +        2 => 'warning', +        3 => 'error', +    );      public $message; +    /** +     * PlanetError constructor. +     * @param $level +     * @param $message +     */      public function __construct($level, $message)      {          $this->level = (int) $level;          $this->message = $message;      } -    public function toString($format = '%1$s : %2$s') +    /** +     * @param  string $format +     * @return string +     */ +    public function toString($format = '%1$s: %2$s')      { -        $levels = array( -            1 => 'notice', -            2 => 'warning', -            3 => 'error', -        ); -        return sprintf($format, $levels[$this->level], $this->message); +        return sprintf($format, $this->levels[$this->level], $this->message);      }  } diff --git a/common/app/classes/PlanetFeed.php b/common/app/classes/PlanetFeed.php index a6c7aab..3d2ea2f 100644 --- a/common/app/classes/PlanetFeed.php +++ b/common/app/classes/PlanetFeed.php @@ -9,15 +9,17 @@ class PlanetFeed extends SimplePie      public $name;      public $feed;      public $website; +    public $isDown; -    public function __construct($name, $feed, $website) +    public function __construct($name, $feed, $website, $isDown)      {          $this->name    = $name;          $this->feed    = $feed;          $this->website = $website; +        $this->isDown  = $isDown;          parent::__construct();          $this->set_item_class('PlanetItem'); -        $this->set_cache_location(dirname(__FILE__).'/../../cache'); +        $this->set_cache_location(__DIR__.'/../../cache');          $this->set_autodiscovery_level(SIMPLEPIE_LOCATOR_NONE);          $this->set_feed_url($this->getFeed());          $this->set_timeout(5); @@ -39,7 +41,19 @@ class PlanetFeed extends SimplePie          return $this->website;      } -    public function compare($person1, $person2) +    public function getIsDown() +    { +        return $this->isDown; +    } + +    /** +     * Compare two Person by their name. +     * +     * @param  $person1 +     * @param  $person2 +     * @return int +     */ +    public static function compare($person1, $person2)      {          return strcasecmp($person1->name, $person2->name);      } diff --git a/common/app/classes/PlanetItem.php b/common/app/classes/PlanetItem.php index 039d0ca..7d55781 100644 --- a/common/app/classes/PlanetItem.php +++ b/common/app/classes/PlanetItem.php @@ -4,14 +4,19 @@   * Planet item   */ -class PlanetItem +class PlanetItem extends SimplePie_Item  {      public function __construct($feed, $data)      { -        parent::SimplePie_Item($feed, $data); +        parent::__construct($feed, $data);      } -    public function compare($item1, $item2) +    /** +     * @param PlanetItem $item1 +     * @param PlanetItem $item2 +     * @return int +     */ +    public static function compare($item1, $item2)      {          $item1_date = $item1->get_date('U');          $item2_date = $item2->get_date('U'); diff --git a/common/app/classes/Simplel10n.php b/common/app/classes/Simplel10n.php new file mode 100755 index 0000000..79313b3 --- /dev/null +++ b/common/app/classes/Simplel10n.php @@ -0,0 +1,52 @@ +<?php + + +class Simplel10n { + +    public $locale; +    public $l10nFolder; + +    public function __construct($locale='en') { +        $GLOBALS['locale'] = array(); +        $this->locale      = $locale; +        $this->l10nFolder  = __DIR__ . '/../l10n/'; +        $this->load($this->l10nFolder . $this->locale); +    } + +    public function setL1OnFolder($path) { +        $this->l10nFolder = $path; +    } + +    static function getString($str, $comment='') { +        if(array_key_exists($str, $GLOBALS['locale'])) { +            return trim(str_replace('{ok}', '', $GLOBALS['locale'][$str])); +        } else { +            return $str; +        } +    } + +    /* +     * This is the same as getString except that we don't remove the {ok} string +     * This is needed only for the extraction script +     */ +    static function extractString($str, $comment='') { +        if(array_key_exists($str, $GLOBALS['locale'])) { +            return $GLOBALS['locale'][$str]; +        } else { +            return $str; +        } +    } + +    static function load($pathToFile) { + +        if (!file_exists($pathToFile . '.lang')) return false; + +        $file = file($pathToFile . '.lang'); + +        foreach ($file as $k => $v) { +            if (substr($v,0,1) == ';' && !empty($file[$k+1])) { +                $GLOBALS['locale'][trim(substr($v,1))] = trim($file[$k+1]); +            } +        } +    } +} diff --git a/common/app/helpers.php b/common/app/helpers.php new file mode 100644 index 0000000..e943252 --- /dev/null +++ b/common/app/helpers.php @@ -0,0 +1,130 @@ +<?php + +/** + * Register polyfills for old PHP versions. + *  + * This way, the real function will only be called if it + * is available, and we won't force the use of our own  + * implementation. + */ +function register_polyfills() +{ +    if (!function_exists('hash_equals')) { +        function hash_equals($known_string, $user_string) { +            call_user_func_array('_hash_equals', func_get_args()); +        } +    } + +    if (!function_exists('random_bytes')) { +        // If this function does not exist, it will be exposed +        // automatically by paragonie/random_compat. +    } +} + +register_polyfills(); + +/** + * Path to the _custom_ directory. + * + * @param  string $file Append this filename to the returned path. + * @return string + */ +function custom_path($file = '') +{ +    return __DIR__.'/../custom' . (!empty($file) ? '/'.$file : ''); +} + +/** + * Path to the _views_ directory. + * + * @param  string $file Append this filename to the returned path. + * @return string + */ +function views_path($file = '') +{ +    return custom_path('views/' . $file); +} + +/** + * Path to the _admin_ directory. + * + * @param  string $file Append this filename to the returned path. + * @return string + */ +function admin_path($file = '') +{ +    return __DIR__.'/../admin' . (!empty($file) ? '/'.$file : ''); +} + +/** + * Is moonmoon installed? + * + * @return bool + */ +function is_installed() +{ +    return file_exists(custom_path('config.yml')) && file_exists(custom_path('people.opml')); +} + +/** + * Shortcut to Simplel10n::getString(). + * + * @param  string $str + * @param  string $comment + * @return string + */ +function _g($str, $comment='') +{ +    return Simplel10n::getString($str, $comment); +} + +/** + * Reset the moonmoon instance. + */ +function removeCustomFiles() +{ +    $toRemove = [ +        custom_path('config.yml'), +        custom_path('people.opml'), +        custom_path('people.opml.bak'), +        custom_path('cache') +    ]; + +    foreach ($toRemove as $path) { +        if (file_exists($path)) { +            unlink($path); +        } +    } +} + +/** + * Compare two strings in a constant-time manner. + * + * It returns `true` if both strings are exactly the same + * (same size and same value). + * + * @param  string $known_string + * @param  string $user_string + * @return bool + */ +function _hash_equals($known_string = '', $user_string = '') +{ +    // In our case, it's not problematic if `$known_string`'s  +    // size leaks, we will only compare password hashes and  +    // CSRF tokens—their size is already somehow public. +    if (!is_string($known_string) || !is_string($user_string)  +         || strlen($known_string) !== strlen($user_string)) { +        return false; +    } + +    $ret = 0;     + +    // Do not stop the comparison when a difference is found, +    // always completely compare them. +    for ($i = 0; $i < strlen($known_string); $i++) { +        $ret |=  (ord($known_string[$i]) ^ ord($user_string[$i])); +    }    + +    return !$ret; +} + diff --git a/common/app/l10n/de.lang b/common/app/l10n/de.lang new file mode 100644 index 0000000..628a12c --- /dev/null +++ b/common/app/l10n/de.lang @@ -0,0 +1,219 @@ +;Are you sure you want to purge the cache? +Sind Sie sicher, dass sie den Cache säubern wollen? + + +;Clear cache +Cache Löschen + + +;Clear cache: +Cache löschen: + + +;Clear +Löschen + + +;Clearing the cache will make moonmoon reload all feeds. +Wenn der Cache gelöscht wird, werden alle Feeds von moonmoon neu geladen. + + +;Change administrator password +Administratorpasswort ändern + + +;New password: +Neues Passwort: + + +;Change password +Passwort ändern + + +;Add Feed +Feed hinzufügen + + +;Link: +Link: + + +;Accepted formats are RSS and ATOM. If the link is not a feed, moonmoon will try to autodiscover the feed. +Akzeptierte  Formate sind RSS und ATOM. Wenn der link kein Feed ist, wird moonmoon versuchen, den Feed selbst zu finden. + + +;Manage existing feeds +Verwalte bestehende Feeds + + +;Number of feeds: %s +Anzahl der feeds: %s + + +;Save changes +Änderungen Speichern + + +;Delete selected Feeds +Ausgewählte Feeds löschen + + +;Select : +Auswählen + + +;All +Alle + + +;None +Nichts + + +;Selection +Auswahl + + +;Name +Name + + +;Last entry +Letzter Eintrag + + +;Website link +Link der Webseite + + +;Feed link +Link des Feeds + + +;Unavailable +Nicht verfügbar + +;Not in cache +Nicht im cache + + +;Password: +Passwort: + + +;OK +OK + + +;moonmoon administration +moonmoon Administration + + +;Back to main page +Zurück zur Hauptseite + + +;Logout +Abmelden + + +;Feeds +Feeds + + +;Administration +Administration + + +;Powered by <a %s>moonmoon</a> +Powered by <a %s>moonmoon</a> + + +;No article +Kein Artikel + + +;No news, good news. +Keine Neuigkeiten, gute Neuigkeiten. + + +;Today +Heute + + +;Go to original place +Gehe zur Originalseite + + +;This week +Diese Woche + + +;This month +Diesen Monat + + +;Older items +Ältere Einträge + + +;People +Personen + + +;Feed +Feed + + +;Website +Webseite + + +;All feeds in OPML format +Alle Feeds im OPML-Format + + +;Syndicate +Zusammenfassung + + +;Feed (ATOM) +Feed (ATOM) + + +;Archives +Archive + + +;See all headlines +Alle Schlagzeilen ansehen + + +;Source: +Quelle: + + +;You might want to <a href="install.php">install moonmoon</a>. +Sie könnten  <a href="install.php">moonmoon installieren</a> wollen. + + +;moonmoon installation +moonmoon Installation + + +;Congratulations! Your moonmoon is ready. +Gratulation! Ihr moonmoon ist bereit. + + +;What's next? +Was nun? + + +;<strong>Delete</strong> <code>install.php</code> with your FTP software. +<strong>Entfernen Sie</strong> <code>install.php</code> mit Ihrer FTP-Software. + + +;Use your password to go to the <a href="./admin/">administration panel</a> +Verwenden Sie Ihr Passwort, um zum <a href="./admin/">Administrationsbereich</a> zu gehen. + + diff --git a/common/app/l10n/en.lang b/common/app/l10n/en.lang index 64d5a82..ff49307 100644 --- a/common/app/l10n/en.lang +++ b/common/app/l10n/en.lang @@ -114,6 +114,10 @@ Feed link  # Translation note:  ** String needs translation ** +;Unavailable +Unavailable + +# Translation note:  ** String needs translation **  ;Not in cache  Not in cache diff --git a/common/app/l10n/es.lang b/common/app/l10n/es.lang new file mode 100644 index 0000000..11b80df --- /dev/null +++ b/common/app/l10n/es.lang @@ -0,0 +1,244 @@ +# Translation note:  ¿Está seguro que desea vaciar la caché? +;Are you sure you want to purge the cache? +¿Está seguro de que desea vaciar la caché? + + +# Translation note:  Vaciar la caché +;Clear cache +Vaciar la caché + + +# Translation note:  Vaciar la caché: +;Clear cache: +Vaciar la caché: + + +;Clear +Vaciar + + +# Translation note:  Vaciar la caché hará que moonmoon recargue todas las fuentes web. +;Clearing the cache will make moonmoon reload all feeds. +Vaciar la caché hará que moonmoon recargue todas las fuentes web. + + +# Translation note:  Cambiar la contraseña de administración +;Change administrator password +Cambiar la contraseña de administración + + +# Translation note: Nueva contraseña: +;New password: +Nueva contraseña: + + +# Translation note:  Cambiar la contraseña +;Change password +Cambiar la contraseña + + +# Translation note:  Añadir una nueva fuente web - 'feed' = 'fuente web', 'fuente' or 'canal' in spanish, but you can use 'feed' anyway. +;Add a new feed +Añadir una nueva fuente web + + +# Translation note: Añadir fuente  +;Add Feed +Añadir fuente + + +;Link: +Enlace: + + +# Translation note: Los formatos aceptados son RSS y ATOM. Si el enlace no es una fuente web, moonmoon tratará de encontrar la fuente de forma automática. +;Accepted formats are RSS and ATOM. If the link is not a feed, moonmoon will try to autodiscover the feed. +Los formatos aceptados son RSS y ATOM. Si el enlace no es una fuente web, moonmoon tratará de encontrar la fuente de forma automática. + + +;Manage existing feeds +Administrar las fuentes existentes + + +# Translation note:  Número de fuentes +;Number of feeds: %s +Número de fuentes: %s + + +;Save changes +Guardar cambios + + +;Delete selected Feeds +Borrar seleccionadas + + +;Select : +Seleccionar: + + +;All +Todas + + +;None +Ninguna + + +# Translation note:  Selección +;Selection +Selección + + +;Name +Nombre + +# Translation note: Última entrada +;Last entry +Última entrada + + +;Website link +Enlace a la web + + +;Feed link +Enlace a la fuente + + +# Translation note:  'No está en caché' or 'No en caché' +;Not in cache +No está en caché + + +# Translation note:  Contraseña: +;Password: +Contraseña: + + +;OK +Entrar + + +# Translation note:  Administración de moonmoon +;moonmoon administration +Administración de moonmoon + + +# Translation note:  Volver a la página principal +;Back to main page +Volver a la página principal + + +;Logout +Salir + + +;Feeds +Fuentes + + +# Translation note:  Administración +;Administration +Administración + + +;Powered by <a %s>moonmoon</a> +Desarrollado por <a %s>moonmoon</a> + + +# Translation note:  Sin artículos +;No article +Sin artículos + + +;No news, good news. +No hay noticias, buena noticia. + + +;Today +Hoy + + +# Translation note:  Ir a la ubicación original +;Go to original place +Ir a la ubicación original + + +;This week +Esta semana + + +;This month +Este mes + + +;Older items +Titulares anteriores + + +# Translation note: 'People' = 'Personas', but 'fuentes'(sources), which in Spanish means origins, is more generic. +;People +Fuentes + + +;Feed +Fuente + + +;Website +Sitio web + + +;All feeds in OPML format +Todas las fuentes (OPML) + + +# Translation note:  Redifusión +;Syndicate +Redifusión + + +;Feed (ATOM) +Fuente (ATOM) + + +;Archives +Archivos + + +;See all headlines +Ver todos los titulares + + +# Translation note:  You may use 'Source' = 'Origen' too. +;Source: +Fuente: + + +;You might want to <a href="install.php">install moonmoon</a>. +Es posible que desee <a href="install.php">instalar moonmoon</a>. + + +# Translation note:  Instalación de moonmoon +;moonmoon installation +Instalación de moonmoon + + +# Translation note:  ¡Felicidades! Su moonmoon está preparado. +;Congratulations! Your moonmoon is ready. +¡Felicidades! Su moonmoon está preparado. + + +# Translation note: ¿Y ahora? +;What's next? +¿Y ahora? + + +;<strong>Delete</strong> <code>install.php</code> with your FTP software. +<strong>Borre</strong> <code>install.php</code> con su programa de FTP. + + +# Translation note:  Utilice su contraseña para acceder al panel de administración. +;Use your password to go to the <a href="./admin/">administration panel</a> +Utilice su contraseña para acceder al <a href="./admin/">panel de administración</a>. diff --git a/common/app/l10n/extract.php b/common/app/l10n/extract.php index 1a04f00..1384827 100755 --- a/common/app/l10n/extract.php +++ b/common/app/l10n/extract.php @@ -13,13 +13,15 @@   * The script scans for the files in the l10n/ folder to know which locales are supported   */ -// released versions of moonmoon should immediately return for security -// return; +$root = __DIR__ . '/../../'; -$root = dirname(__FILE__) . '/../../'; -$GLOBALS['english'] = array(); +require_once __DIR__ . '/../app.php'; -include_once $root . '/app/classes/Simplel10n.class.php'; +if ($conf['debug'] !== true) { +    die('Please enable the debug mode to use extract.php.'); +} + +$GLOBALS['english'] = array();  /*   * This is a file parser to extract localizable strings (in .php files) @@ -27,7 +29,6 @@ include_once $root . '/app/classes/Simplel10n.class.php';   *   */ -  function extract_l10n_strings($file) {      $lines    = file($file);      $patterns = array('/_g\([\'"](.*?)[\'"]\)/', '/getString\([\'"](.*?)[\'"]\)/',); diff --git a/common/app/l10n/fr.lang b/common/app/l10n/fr.lang index 6247652..4e2a568 100755 --- a/common/app/l10n/fr.lang +++ b/common/app/l10n/fr.lang @@ -90,6 +90,10 @@ Lien du site  Lien du Flux +;Unavailable +Indisponible + +  ;Not in cache  Pas en cache  | 
