diff options
Diffstat (limited to 'common')
56 files changed, 3953 insertions, 355 deletions
diff --git a/common/CONTRIBUTING.md b/common/CONTRIBUTING.md new file mode 100644 index 0000000..1402a0c --- /dev/null +++ b/common/CONTRIBUTING.md @@ -0,0 +1,37 @@ +# Contributing + +First of all, thanks a lot for considering to contribute! + +## Found a bug? + +* Nice catch! +* Make sure that this bug is not already present in our + [Issues](https://github.com/moonmoon/moonmoon/issues) tracker. +* If none matches, please + [create a new ticket](https://github.com/moonmoon/moonmoon/issues/new). + +## Fixing a bug with a patch you made? + +* Awesome! +* If the bug you are fixing is present in our + [Issues tracker](https://github.com/moonmoon/moonmoon/issues), don't + forget to mention the related issue. +* Make sure to stick to the [PSR-2](http://www.php-fig.org/psr/psr-2/) + coding style. + +## Adding a new feature? + +* Marvelous! +* If it's not too late, please create a new ticket in the + [Issues tracker](https://github.com/moonmoon/moonmoon/issues/new) before + developing something. Maybe it does not really fit into moonmoon and + we may not merge it :( +* Make sure to stick to the [PSR-2](http://www.php-fig.org/psr/psr-2/) + coding style. + +## Seeking support ? + +* Is your problem already listed in our [FAQ](https://github.com/moonmoon/moonmoon/wiki/Faq)? +* If not, please create a ticket in our + [Issues tracker](https://github.com/moonmoon/moonmoon/issues/new). +* You more details you give (version, steps to reproduce…), the better we'll help you :-) diff --git a/common/LICENSE b/common/LICENSE index 8d83edd..bb0603a 100644 --- a/common/LICENSE +++ b/common/LICENSE @@ -30,3 +30,4 @@ Contributors: François Granger Pep, http://www.callmepep.org/ Pascal Chevrel +Javier Guerra diff --git a/common/README.md b/common/README.md new file mode 100644 index 0000000..0fcfda0 --- /dev/null +++ b/common/README.md @@ -0,0 +1,66 @@ +<p align="center"> + <img src="https://github.com/moonmoon/moonmoon/raw/master/custom/img/moonmoon%40128w.png"> +</p> + + +moonmoon [![Build Status](https://travis-ci.org/moonmoon/moonmoon.svg?branch=master)](https://travis-ci.org/moonmoon/moonmoon) +======== + +Moonmoon is a web based aggregator similar to planetplanet. +It can be used to blend articles from different blogs with same interests into a single page. + +Moonmoon is simple: it only aggregates feeds and spits them out in one single page. +It does not archive articles, it does not do comments nor votes. + +Requirements +------------ +You will need a web hosting with at least PHP 5.6 (PHP 7 is also supported). + +If you are installing moonmoon on a Linux private server (VPS, dedicated host), +please note that you will need to install the package `php-xml`. + +Installing +---------- + +Installation steps (shared hosting or virtual / dedicated server) can be found +[in the wiki](https://github.com/moonmoon/moonmoon/wiki/How-to-install). + +Docker images are also available in [moonmoon/docker-images](https://github.com/moonmoon/docker-images). +Theses images are probably not production-ready but should work for manual testing. + +Contributing +------------ + +You want to contribute to moonmoon? Perfect! [We wrote some guidelines to help you +craft the best Issue / Pull Request possible](https://github.com/moonmoon/moonmoon/blob/master/CONTRIBUTING.md), +don't hesitate to take a look at it :-) + +License +------- + +Moonmoon is free software and is released under the [BSD license](https://github.com/moonmoon/moonmoon/blob/master/LICENSE). +Third-party code differently licensed is included in this project, in which case mention is always made of +the applicable license. + +[The logo](https://github.com/moonmoon/moonmoon/raw/master/custom/img/moonmoon.png) was designed by [@rakujira](https://twitter.com/rakujira). + +Configuration options +--------------------- +After installation, configuration is kept in a YAML formatted `custom/config.yml`: + +```%yaml +url: http://planet.example.net # your planet base URL +name: My Planet # your planet front page name +locale: en # front page locale +items: 10 # how many items to show +refresh: 240 # feeds cache timeout (in seconds) +cache: 10 # front page cache timeout (in seconds) +cachedir: ./cache # where is cache stored +postmaxlength: 0 # deprecated +shuffle: 0 # deprecated +nohtml: 0 # deprecated +categories: # only list posts that have one + # of these (tag or category) +debug: false # debug mode (dangerous in production!) +checkcerts: true # check feeds certificates +``` diff --git a/common/VERSION b/common/VERSION index 38f8e88..2a8cbc6 100644 --- a/common/VERSION +++ b/common/VERSION @@ -1 +1 @@ -dev +9.0.0-rc diff --git a/common/admin/administration.php b/common/admin/administration.php index 1202e91..26f6710 100755 --- a/common/admin/administration.php +++ b/common/admin/administration.php @@ -1,9 +1,10 @@ <?php -require_once dirname(__FILE__) . '/inc/auth.inc.php'; -require_once dirname(__FILE__) . '/../app/app.php'; +require_once __DIR__ . '/../app/app.php'; +require_once __DIR__ . '/inc/auth.inc.php'; -$opml = OpmlManager::load(dirname(__FILE__) . '/../custom/people.opml'); + +$opml = OpmlManager::load(__DIR__ . '/../custom/people.opml'); $opml_people = $opml->getPeople(); $page_id = 'admin-admin'; $header_extra = <<<"HTML" @@ -23,6 +24,7 @@ $page_content = <<<"FRAGMENT" <div class="widget"> <h3>{$l10n->getString('Clear cache')}</h3> <form action="purgecache.php" method="post" id="frmPurge"> + <input type="hidden" value="{$csrf->generate('frmPurge')}" name="_csrf"> <p><label>{$l10n->getString('Clear cache:')}</label><input type="submit" class="submit delete" name="purge" id="purge" value="{$l10n->getString('Clear')}" /></p> <p class="help">{$l10n->getString('Clearing the cache will make moonmoon reload all feeds.')}</p> </form> @@ -31,6 +33,7 @@ $page_content = <<<"FRAGMENT" <div class="widget"> <h3>{$l10n->getString('Change administrator password')}</h3> <form action="changepassword.php" method="post" id="frmPassword"> + <input type="hidden" value="{$csrf->generate('frmPassword')}" name="_csrf"> <p><label for="password">{$l10n->getString('New password:')}</label> <input type="password" class="text" value="" name="password" id="password" size="20" /> <input type="submit" class="submit delete" name="changepwd" id="changepwd" value="{$l10n->getString('Change password')}" /></p> </form> </div> @@ -39,4 +42,4 @@ FRAGMENT; $footer_extra = ''; $admin_access = 1; -require_once dirname(__FILE__) . '/template.php'; +require_once __DIR__ . '/template.php'; diff --git a/common/admin/changepassword.php b/common/admin/changepassword.php index 1fa505e..3b4500e 100644 --- a/common/admin/changepassword.php +++ b/common/admin/changepassword.php @@ -1,9 +1,11 @@ <?php -require_once dirname(__FILE__).'/inc/auth.inc.php'; -if (isset($_POST['password']) && ('' != $_POST['password'])){ +require_once __DIR__.'/../app/app.php'; +require_once __DIR__.'/inc/auth.inc.php'; + +if ($csrf->verify($_POST['_csrf'], 'frmPassword') && isset($_POST['password']) && ('' != $_POST['password'])) { $out = '<?php $login="admin"; $password="'.md5($_POST['password']).'"; ?>'; - file_put_contents(dirname(__FILE__).'/inc/pwd.inc.php', $out); + file_put_contents(__DIR__.'/inc/pwd.inc.php', $out); die("Password changed. <a href='administration.php'>Login</a>"); } else { die('Can not change password'); diff --git a/common/admin/inc/auth.inc.php b/common/admin/inc/auth.inc.php index d21467b..0acf934 100644 --- a/common/admin/inc/auth.inc.php +++ b/common/admin/inc/auth.inc.php @@ -1,11 +1,13 @@ <?php -include (dirname(__FILE__).'/pwd.inc.php'); -if ( isset($_COOKIE['auth']) && $_COOKIE['auth'] == $password ) { - //ok, cool -} else { - setcookie('auth','', time()-3600); +include dirname(__FILE__).'/pwd.inc.php'; + +if (!class_exists('Planet')) { + require __DIR__.'/../../vendor/autoload.php'; +} + +if (!Planet::authenticateUser($_COOKIE['auth'], $password)) { + setcookie('auth', '', time() - 3600); header('Location: login.php'); - die; + die(); } -?>
\ No newline at end of file diff --git a/common/admin/index.php b/common/admin/index.php index 28f7198..0118923 100755 --- a/common/admin/index.php +++ b/common/admin/index.php @@ -1,10 +1,10 @@ <?php -require_once dirname(__FILE__) . '/inc/auth.inc.php'; -require_once dirname(__FILE__) . '/../app/app.php'; +require_once __DIR__ . '/../app/app.php'; +require_once __DIR__ . '/inc/auth.inc.php'; //Load configuration -$config_file = dirname(__FILE__) . '/../custom/config.yml'; +$config_file = __DIR__ . '/../custom/config.yml'; if (is_file($config_file)){ $conf = Spyc::YAMLLoad($config_file); @@ -17,7 +17,7 @@ if (is_file($config_file)){ $Planet = new Planet($PlanetConfig); //Load -if (0 < $Planet->loadOpml(dirname(__FILE__) . '/../custom/people.opml')) { +if (0 < $Planet->loadOpml(__DIR__ . '/../custom/people.opml')) { $Planet->loadFeeds(); $items = $Planet->getItems(); } @@ -79,6 +79,7 @@ ob_start(); <input type="submit" class="submit add" name="add" value="<?=_g('Add Feed')?>" /> </fieldset> <p class="help"><?=_g('Accepted formats are RSS and ATOM. If the link is not a feed, moonmoon will try to autodiscover the feed.')?></p> + <input type="hidden" value="<?php echo $csrf->generate('feedmanage'); ?>" name="_csrf"> </form> </div> @@ -87,6 +88,7 @@ ob_start(); <form action="subscriptions.php" method="post" id="feedmanage"> <p class="action"> <span class="count"><?php echo sprintf(_g('Number of feeds: %s'), $count_feeds)?></span> + <input type="hidden" value="<?php echo $csrf->generate('feedmanage'); ?>" name="_csrf"> <input type="submit" class="submit save" name="save" id="save" value="<?=_g('Save changes')?>" /> <input type="submit" class="submit delete" name="delete" id="delete" value="<?=_g('Delete selected Feeds')?>" /> </p> @@ -99,6 +101,7 @@ ob_start(); <th><?=_g('Last entry')?></th> <th><?=_g('Website link')?></th> <th><?=_g('Feed link')?></th> + <th><?=_g('Unavailable')?></th> </tr> </thead> <tbody> @@ -118,10 +121,12 @@ ob_start(); } else { echo _g('Not in cache'); } + $check_is_down = $opml_person->getIsDown() === '1' ? 'checked="checked"' : ''; ?> </td> <td><input type="text" size="30" class="text" name="opml[<?=$i; ?>][website]" value="<?=$opml_person->getWebsite(); ?>" /></td> <td><input type="text" size="30" class="text" name="opml[<?=$i; ?>][feed]" value="<?=$opml_person->getFeed(); ?>" /></td> + <td><input type="checkbox" readonly="readonly" name="opml[<?=$i; ?>][isDown]" <?=$check_is_down?> value="1" /></td> </tr> <?php } ?> </tbody> @@ -133,4 +138,4 @@ $page_content = ob_get_contents(); ob_end_clean(); $admin_access = 1; -require_once dirname(__FILE__) . '/template.php'; +require_once __DIR__ . '/template.php'; diff --git a/common/admin/login.php b/common/admin/login.php index 796011f..a95e59f 100755 --- a/common/admin/login.php +++ b/common/admin/login.php @@ -1,10 +1,13 @@ <?php + +require_once __DIR__ . '/../app/app.php'; + if (isset($_POST['password'])) { + session_regenerate_id(); setcookie('auth',md5($_POST['password'])); header('Location: index.php'); } -require_once dirname(__FILE__) . '/../app/app.php'; $page_content = <<<FRAGMENT <form action="" method="post" class="login"> <fieldset> @@ -31,4 +34,4 @@ FRAGMENT; $page_id = 'admin-login'; $admin_access = 0; -require_once dirname(__FILE__) . '/template.php'; +require_once __DIR__ . '/template.php'; diff --git a/common/admin/logout.php b/common/admin/logout.php index 6dd32aa..adb843f 100644 --- a/common/admin/logout.php +++ b/common/admin/logout.php @@ -1,5 +1,10 @@ <?php + +require_once __DIR__ . '/../app/app.php'; + setcookie('auth','', time()-3600); +session_destroy(); +session_regenerate_id(); + header('Location: login.php'); -die; -?>
\ No newline at end of file +die(); diff --git a/common/admin/purgecache.php b/common/admin/purgecache.php index a5af5cf..23a5712 100644 --- a/common/admin/purgecache.php +++ b/common/admin/purgecache.php @@ -1,16 +1,18 @@ <?php -require_once dirname(__FILE__).'/inc/auth.inc.php'; + +require_once __DIR__.'/../app/app.php'; +require_once __DIR__.'/inc/auth.inc.php'; if (isset($_POST['purge'])){ - $dir = dirname(__FILE__).'/../cache/'; - + $dir = __DIR__.'/../cache/'; + $dh = opendir($dir); - + while ($filename = readdir($dh)) { if ($filename == '.' OR $filename == '..') { continue; } - + if (filemtime($dir . DIRECTORY_SEPARATOR . $filename) < time()) { @unlink($dir . DIRECTORY_SEPARATOR . $filename); } @@ -18,4 +20,4 @@ if (isset($_POST['purge'])){ } header('Location: administration.php'); -die();
\ No newline at end of file +die(); diff --git a/common/admin/subscriptions.php b/common/admin/subscriptions.php index ea2f113..f0fd896 100755 --- a/common/admin/subscriptions.php +++ b/common/admin/subscriptions.php @@ -1,21 +1,24 @@ <?php -require_once dirname(__FILE__) . '/inc/auth.inc.php'; -require_once dirname(__FILE__) . '/../app/app.php'; + +require_once __DIR__ . '/../app/app.php'; +require_once __DIR__ . '/inc/auth.inc.php'; function removeSlashes(&$item, $key){ $item = stripslashes($item); } +if (!$csrf->verify($_POST['_csrf'], 'feedmanage')) { + die('Invalid CSRF token!'); +} + if (isset($_POST['opml']) || isset($_POST['add'])) { - // Load config and old OPML - $conf = Spyc::YAMLLoad(dirname(__FILE__).'/../custom/config.yml'); - $PlanetConfig = new PlanetConfig($conf); + // Load old OPML + $oldOpml = OpmlManager::load(__DIR__.'/../custom/people.opml'); if ($PlanetConfig->getName() === '') { $PlanetConfig->setName($oldOpml->getTitle()); } - $oldOpml = OpmlManager::load(dirname(__FILE__).'/../custom/people.opml'); - $newOpml = new opml(); + $newOpml = new Opml(); $newOpml->title = $PlanetConfig->getName(); // Remove slashes if needed @@ -43,11 +46,18 @@ if (isset($_POST['opml']) || isset($_POST['add'])) { $feed = new SimplePie(); $feed->enable_cache(false); $feed->set_feed_url($_POST['url']); + if ($conf['checkcerts'] === false) { + $feed->set_curl_options([ + CURLOPT_SSL_VERIFYHOST => false, + CURLOPT_SSL_VERIFYPEER => false + ]); + } $feed->init(); $feed->handle_content_type(); - $person['name'] = $feed->get_title(); + $person['name'] = html_entity_decode($feed->get_title()); $person['website'] = $feed->get_permalink(); $person['feed'] = $feed->feed_url; + $person['isDown'] = '0'; $oldOpml->entries[] = $person; } @@ -55,10 +65,10 @@ if (isset($_POST['opml']) || isset($_POST['add'])) { } // Backup old OPML - OpmlManager::backup(dirname(__FILE__).'/../custom/people.opml'); + OpmlManager::backup(__DIR__.'/../custom/people.opml'); // Save new OPML - OpmlManager::save($newOpml, dirname(__FILE__).'/../custom/people.opml'); + OpmlManager::save($newOpml, __DIR__.'/../custom/people.opml'); } header("Location: index.php"); die(); 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 diff --git a/common/atom.php b/common/atom.php index 5122158..c80d987 100644 --- a/common/atom.php +++ b/common/atom.php @@ -1,15 +1,15 @@ <?php -include_once(dirname(__FILE__).'/app/app.php'); -include_once(dirname(__FILE__).'/app/lib/Cache.php'); +include_once(__DIR__.'/app/app.php'); +include_once(__DIR__.'/app/lib/Cache.php'); -if ($Planet->loadOpml(dirname(__FILE__).'/custom/people.opml') == 0) exit; +if ($Planet->loadOpml(__DIR__.'/custom/people.opml') == 0) exit; $Planet->loadFeeds(); $items = $Planet->getItems(); $limit = $PlanetConfig->getMaxDisplay(); $count = 0; -header('Content-Type: application/atom+xml; charset=UTF-8'); +header('Content-Type: text/xml; charset=UTF-8'); echo '<?xml version="1.0" encoding="UTF-8" ?>'; ?> <feed xmlns="http://www.w3.org/2005/Atom"> diff --git a/common/bin/build_release.sh b/common/bin/build_release.sh new file mode 100755 index 0000000..2f09661 --- /dev/null +++ b/common/bin/build_release.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +set -eu + +tmp=$(mktemp -d -t moonmmon) + +echo "[*] Building into $tmp..." + +cd "$tmp" +git clone https://github.com/moonmoon/moonmoon.git --depth=1 --recursive -j8 +cd moonmoon +composer install --no-suggest --prefer-dist --no-dev +git describe --abbrev=0 --tags > VERSION +find . -name .DS_Store -exec rm {} \; +rm -rf .git .github .travis.yml .gitignore .gitmodules docs/.git/ +mkdir cache +cd .. +zip -r "moonmoon-$(cat moonmoon/VERSION).zip" . + +echo "[*] Grab the archive: ${tmp}/moonmoon-$(cat moonmoon/VERSION).zip" diff --git a/common/composer.json b/common/composer.json new file mode 100644 index 0000000..ea5032d --- /dev/null +++ b/common/composer.json @@ -0,0 +1,35 @@ +{ + "name": "mauricesvay/moonmoon", + "description": "Moonmoon is a simple feed aggregator.", + "homepage": "http://moonmoon.org/", + "type": "project", + "license": "BSD", + "authors": [ + { + "name": "Maurice Svay", + "email": "maurice@svay.com" + } + ], + "minimum-stability": "stable", + "require": { + "php": "^5.6 || ^7.0", + "mustangostang/spyc": "0.5.1", + "simplepie/simplepie": "^1.5", + "paragonie/random_compat": "^2.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "^6.3", + "phpunit/phpunit": "^5.7 || ^6.2" + }, + "scripts": { + "test": "" + }, + "autoload": { + "psr-0": { + "": "app/classes/" + }, + "files": [ + "app/helpers.php" + ] + } +} diff --git a/common/composer.lock b/common/composer.lock new file mode 100644 index 0000000..7959c9c --- /dev/null +++ b/common/composer.lock @@ -0,0 +1,1599 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "content-hash": "a4fc919a4e8ef2463ff4a336940bc993", + "packages": [ + { + "name": "mustangostang/spyc", + "version": "0.5.1", + "source": { + "type": "git", + "url": "https://github.com/mustangostang/spyc.git", + "reference": "dc4785b4d7227fd9905e086d499fb8abfadf9977" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mustangostang/spyc/zipball/dc4785b4d7227fd9905e086d499fb8abfadf9977", + "reference": "dc4785b4d7227fd9905e086d499fb8abfadf9977", + "shasum": "" + }, + "require": { + "php": ">=5.3.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.5.x-dev" + } + }, + "autoload": { + "files": [ + "Spyc.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT License" + ], + "authors": [ + { + "name": "mustangostang", + "email": "vlad.andersen@gmail.com" + } + ], + "description": "A simple YAML loader/dumper class for PHP", + "homepage": "https://github.com/mustangostang/spyc/", + "keywords": [ + "spyc", + "yaml", + "yml" + ], + "time": "2013-02-21T10:52:01+00:00" + }, + { + "name": "paragonie/random_compat", + "version": "v2.0.11", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "5da4d3c796c275c55f057af5a643ae297d96b4d8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/5da4d3c796c275c55f057af5a643ae297d96b4d8", + "reference": "5da4d3c796c275c55f057af5a643ae297d96b4d8", + "shasum": "" + }, + "require": { + "php": ">=5.2.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "autoload": { + "files": [ + "lib/random.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "pseudorandom", + "random" + ], + "time": "2017-09-27T21:40:39+00:00" + }, + { + "name": "simplepie/simplepie", + "version": "1.5", + "source": { + "type": "git", + "url": "https://github.com/simplepie/simplepie.git", + "reference": "5de5551953f95feef12cf355a7a26a70f94aa3ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/simplepie/simplepie/zipball/5de5551953f95feef12cf355a7a26a70f94aa3ab", + "reference": "5de5551953f95feef12cf355a7a26a70f94aa3ab", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4 || ~5" + }, + "suggest": { + "mf2/mf2": "Microformat module that allows for parsing HTML for microformats" + }, + "type": "library", + "autoload": { + "psr-0": { + "SimplePie": "library" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ryan Parman", + "homepage": "http://ryanparman.com/", + "role": "Creator, alumnus developer" + }, + { + "name": "Geoffrey Sneddon", + "homepage": "http://gsnedders.com/", + "role": "Alumnus developer" + }, + { + "name": "Ryan McCue", + "email": "me@ryanmccue.info", + "homepage": "http://ryanmccue.info/", + "role": "Developer" + } + ], + "description": "A simple Atom/RSS parsing library for PHP", + "homepage": "http://simplepie.org/", + "keywords": [ + "atom", + "feeds", + "rss" + ], + "time": "2017-04-17T07:29:31+00:00" + } + ], + "packages-dev": [ + { + "name": "dflydev/markdown", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-markdown.git", + "reference": "76501a808522dbe40a5a71d272bd08d54cbae03d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-markdown/zipball/76501a808522dbe40a5a71d272bd08d54cbae03d", + "reference": "76501a808522dbe40a5a71d272bd08d54cbae03d", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "type": "library", + "autoload": { + "psr-0": { + "dflydev\\markdown": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "New BSD License" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Michel Fortin", + "homepage": "http://michelf.com" + }, + { + "name": "John Gruber", + "homepage": "http://daringfireball.net" + } + ], + "description": "PHP Markdown & Extra", + "homepage": "http://github.com/dflydev/dflydev-markdown", + "keywords": [ + "markdown" + ], + "abandoned": "michelf/php-markdown", + "time": "2012-01-02T23:11:32+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.0.4", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "f976e5de371104877ebc89bd8fecb0019ed9c119" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/f976e5de371104877ebc89bd8fecb0019ed9c119", + "reference": "f976e5de371104877ebc89bd8fecb0019ed9c119", + "shasum": "" + }, + "require": { + "php": ">=5.3,<8.0-DEV" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "2.0.*@ALPHA" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Instantiator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2014-10-13T12:58:55+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "6.3.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/f4db5a78a5ea468d4831de7f0bf9d9415e348699", + "reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699", + "shasum": "" + }, + "require": { + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.4", + "php": ">=5.5" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.0 || ^5.0", + "psr/log": "^1.0" + }, + "suggest": { + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.2-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "time": "2017-06-22T18:50:49+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "01abc3232138f330d8a1eaa808fcbdf9b4292f47" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/01abc3232138f330d8a1eaa808fcbdf9b4292f47", + "reference": "01abc3232138f330d8a1eaa808fcbdf9b4292f47", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "time": "2015-05-13T05:05:10+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "04a6d1a00ea5da0727ee94309a9f0d3dbaecb569" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/04a6d1a00ea5da0727ee94309a9f0d3dbaecb569", + "reference": "04a6d1a00ea5da0727ee94309a9f0d3dbaecb569", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "request", + "response", + "stream", + "uri", + "url" + ], + "time": "2017-02-21T01:20:32+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "96fbdc07635989c35c5a1912379f4c4b2ab15fd5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/96fbdc07635989c35c5a1912379f4c4b2ab15fd5", + "reference": "96fbdc07635989c35c5a1912379f4c4b2ab15fd5", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "doctrine/collections": "1.*", + "phpunit/phpunit": "~4.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "homepage": "https://github.com/myclabs/DeepCopy", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "time": "2015-03-21T22:40:23+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "66ae84e9d7c8ea85c979cb65977bd8e608baf0c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/66ae84e9d7c8ea85c979cb65977bd8e608baf0c5", + "reference": "66ae84e9d7c8ea85c979cb65977bd8e608baf0c5", + "shasum": "" + }, + "require": { + "dflydev/markdown": "1.0.*", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*@stable" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "phpDocumentor": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "mike.vanriel@naenius.com" + } + ], + "time": "2013-08-07T11:04:22+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "v1.3.1", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "9ca52329bcdd1500de24427542577ebf3fc2f1c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/9ca52329bcdd1500de24427542577ebf3fc2f1c9", + "reference": "9ca52329bcdd1500de24427542577ebf3fc2f1c9", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "~1.0,>=1.0.2", + "phpdocumentor/reflection-docblock": "~2.0" + }, + "require-dev": { + "phpspec/phpspec": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "http://phpspec.org", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2014-11-17T16:23:49+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "4.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "903fd6318d0a90b4770a009ff73e4a4e9c437929" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/903fd6318d0a90b4770a009ff73e4a4e9c437929", + "reference": "903fd6318d0a90b4770a009ff73e4a4e9c437929", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0", + "phpunit/php-file-iterator": "~1.3", + "phpunit/php-text-template": "~1.2", + "phpunit/php-token-stream": "^1.4.2", + "sebastian/code-unit-reverse-lookup": "~1.0", + "sebastian/environment": "^1.3.2 || ^2.0", + "sebastian/version": "~1.0|~2.0" + }, + "require-dev": { + "ext-xdebug": ">=2.1.4", + "phpunit/phpunit": "^5.4" + }, + "suggest": { + "ext-dom": "*", + "ext-xdebug": ">=2.4.0", + "ext-xmlwriter": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2016-11-28T16:00:31+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "a923bb15680d0089e2316f7a4af8f437046e96bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a923bb15680d0089e2316f7a4af8f437046e96bb", + "reference": "a923bb15680d0089e2316f7a4af8f437046e96bb", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2015-04-02T05:19:05+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/206dfefc0ffe9cebf65c413e3d0e809c82fbf00a", + "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "Text/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2014-01-30T17:20:04+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "83fe1bdc5d47658b727595c14da140da92b3d66d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/83fe1bdc5d47658b727595c14da140da92b3d66d", + "reference": "83fe1bdc5d47658b727595c14da140da92b3d66d", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2015-06-13T07:35:30+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "db63be1159c81df649cd0260e30249a586d4129e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/db63be1159c81df649cd0260e30249a586d4129e", + "reference": "db63be1159c81df649cd0260e30249a586d4129e", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2015-06-12T07:34:24+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "5.7.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "00d72b8dbd2bb7d6f02a820e6db5cb70df6ac55c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/00d72b8dbd2bb7d6f02a820e6db5cb70df6ac55c", + "reference": "00d72b8dbd2bb7d6f02a820e6db5cb70df6ac55c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "myclabs/deep-copy": "~1.3", + "php": "^5.6 || ^7.0", + "phpspec/prophecy": "^1.3.1", + "phpunit/php-code-coverage": "^4.0.3", + "phpunit/php-file-iterator": "~1.4", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": "^1.0.6", + "phpunit/phpunit-mock-objects": "^3.2", + "sebastian/comparator": "~1.2.2", + "sebastian/diff": "~1.2", + "sebastian/environment": "^1.3.4 || ^2.0", + "sebastian/exporter": "~2.0", + "sebastian/global-state": "~1.0", + "sebastian/object-enumerator": "~2.0", + "sebastian/resource-operations": "~1.0", + "sebastian/version": "~1.0|~2.0", + "symfony/yaml": "~2.1|~3.0" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "3.0.2" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-xdebug": "*", + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.7.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2016-12-01T17:04:00+00:00" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "3.4.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "45026c8383187ad1dcb14fbfec77dced265b9cfc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/45026c8383187ad1dcb14fbfec77dced265b9cfc", + "reference": "45026c8383187ad1dcb14fbfec77dced265b9cfc", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.6 || ^7.0", + "phpunit/php-text-template": "^1.2", + "sebastian/exporter": "^1.2 || ^2.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.4" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "time": "2016-11-19T09:07:46+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "85d63699f0dbedb190bbd4b0d2b9dc707ea4c298" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/85d63699f0dbedb190bbd4b0d2b9dc707ea4c298", + "reference": "85d63699f0dbedb190bbd4b0d2b9dc707ea4c298", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "time": "2015-05-04T20:22:00+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "c36f5e7cfce482fde5bf8d10d41a53591e0198fe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/c36f5e7cfce482fde5bf8d10d41a53591e0198fe", + "reference": "c36f5e7cfce482fde5bf8d10d41a53591e0198fe", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "phpunit/phpunit": "~5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2016-02-13T06:45:14+00:00" + }, + { + "name": "sebastian/comparator", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "6a1ed12e8b2409076ab22e3897126211ff8b1f7f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/6a1ed12e8b2409076ab22e3897126211ff8b1f7f", + "reference": "6a1ed12e8b2409076ab22e3897126211ff8b1f7f", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2 || ~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2016-11-19T09:18:40+00:00" + }, + { + "name": "sebastian/diff", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "5843509fed39dee4b356a306401e9dd1a931fec7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/5843509fed39dee4b356a306401e9dd1a931fec7", + "reference": "5843509fed39dee4b356a306401e9dd1a931fec7", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "http://www.github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2014-08-15T10:29:00+00:00" + }, + { + "name": "sebastian/environment", + "version": "1.3.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "22aa49baa48886f40b060e061a7967436f44a249" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/22aa49baa48886f40b060e061a7967436f44a249", + "reference": "22aa49baa48886f40b060e061a7967436f44a249", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2016-02-26T11:40:57+00:00" + }, + { + "name": "sebastian/exporter", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~2.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2016-11-19T08:54:04+00:00" + }, + { + "name": "sebastian/global-state", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "c7428acdb62ece0a45e6306f1ae85e1c05b09c01" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/c7428acdb62ece0a45e6306f1ae85e1c05b09c01", + "reference": "c7428acdb62ece0a45e6306f1ae85e1c05b09c01", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2014-10-06T09:23:50+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "96f8a3f257b69e8128ad74d3a7fd464bcbaa3b35" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/96f8a3f257b69e8128ad74d3a7fd464bcbaa3b35", + "reference": "96f8a3f257b69e8128ad74d3a7fd464bcbaa3b35", + "shasum": "" + }, + "require": { + "php": ">=5.6", + "sebastian/recursion-context": "~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2016-11-19T07:35:10+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2016-11-19T07:33:16+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2015-07-28T20:34:47+00:00" + }, + { + "name": "sebastian/version", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "16b021aed448b654ae05846e394e057e9a6f04cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/16b021aed448b654ae05846e394e057e9a6f04cb", + "reference": "16b021aed448b654ae05846e394e057e9a6f04cb", + "shasum": "" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2013-01-05T14:27:32+00:00" + }, + { + "name": "symfony/yaml", + "version": "v2.1.0", + "target-dir": "Symfony/Component/Yaml", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "f18e004fc975707bb4695df1dbbe9b0d8c8b7715" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/f18e004fc975707bb4695df1dbbe9b0d8c8b7715", + "reference": "f18e004fc975707bb4695df1dbbe9b0d8c8b7715", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\Yaml": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Symfony Yaml Component", + "homepage": "http://symfony.com", + "time": "2012-08-22T13:48:41+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^5.6 || ^7.0" + }, + "platform-dev": [] +} diff --git a/common/cron.php b/common/cron.php index 77d9d8f..9cc56a6 100644 --- a/common/cron.php +++ b/common/cron.php @@ -1,9 +1,13 @@ <?php -include_once(dirname(__FILE__).'/app/app.php'); +include_once(__DIR__.'/app/app.php'); //Load OPML -if (0 < $Planet->loadOpml(dirname(__FILE__).'/custom/people.opml')) { +if (0 < $Planet->loadOpml(__DIR__.'/custom/people.opml')) { $Planet->download(1.0); } -var_dump($Planet->errors); +if ($conf['debug'] === true) { + foreach ($Planet->errors as $error) { + echo $error->toString() . "\n"; + } +}
\ No newline at end of file diff --git a/common/custom/img/moonmoon.png b/common/custom/img/moonmoon.png Binary files differnew file mode 100644 index 0000000..10f9736 --- /dev/null +++ b/common/custom/img/moonmoon.png diff --git a/common/custom/img/moonmoon@128w.png b/common/custom/img/moonmoon@128w.png Binary files differnew file mode 100644 index 0000000..2a164aa --- /dev/null +++ b/common/custom/img/moonmoon@128w.png diff --git a/common/custom/img/moonmoon@256w.png b/common/custom/img/moonmoon@256w.png Binary files differnew file mode 100644 index 0000000..b52c4bb --- /dev/null +++ b/common/custom/img/moonmoon@256w.png diff --git a/common/custom/style/default.css b/common/custom/style/default.css index de813ac..12268ef 100644 --- a/common/custom/style/default.css +++ b/common/custom/style/default.css @@ -1,128 +1,158 @@ -html{ +@viewport { + width: device-width; + initial-scale: 1.0; +} + +html { font-family: "Lucida Grande", "Segoe UI", sans-serif; color: #111; background: #EEE; font-size: 62.5%; } -body{ +body { margin: 0; padding: 0; font-size: 1.3em; line-height: 1.5em; } -a{ +a { color: #669; } -a:visited{ +a:visited { color: #444; } -a:hover{ +a:hover { color: #000; } -a img{ +a img { border: none; } -pre, code{ +pre, code { max-height: 100%; width: 100%; font-size: 1.2em; overflow: auto; } -h1, h2{ +h1, h2 { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; } -h2{ +h2 { margin: 0; } #page { - width: 890px; - margin: 0 auto; + margin: 0 5em; } /* Header *******************************************************************************/ + #top { color: #FFF; padding: 10px 30px; } - h1 a, - h1 a:visited { - color: #665; - text-decoration: none; - } - h1 a:hover { - color: #444; - } + +h1 a, h1 a:visited { + color: #665; + text-decoration: none; +} + +h1 a:hover { + color: #444; +} /* Content *******************************************************************************/ + #content { - width: 590px; + width: 75%; float: left; } +#content img { + max-width: 100%; + height: auto; +} + /* Sidebar *******************************************************************************/ + #sidebar { - width: 300px; + width: 18%; float: left; } - #sidebar div { - padding: 1em 30px; - } - #sidebar h2 { - color: #665; - } - #sidebar ul { - list-style: none; - padding: 0; - margin: 1em 0; - } - + +#sidebar div { + padding: 1em 30px; +} + +#sidebar h2 { + color: #665; +} + +#sidebar ul { + list-style: none; + padding: 0; + margin: 1em 0; +} + /* Article *******************************************************************************/ -.article{ + +.article { margin-bottom: 2em; padding: 30px; background: #FFF; border: 1px solid #DDD; + overflow: hidden; } - .article-title, - .article-title a{ - color: #111; - text-decoration: none; - } +.article-title, .article-title a { + color: #111; + text-decoration: none; +} - .article-info{ - margin: 0 0 1em 0; - color: #999; - } - - .article-content { - font-family: Georgia, serif; - } +.article-info { + margin: 0 0 1em 0; + color: #999; +} - .article img{ - max-width: 440px; - } +.article-content { + font-family: Georgia, serif; +} - .article .collapsed{ - display: none; - } +.article img { + max-width: 440px; +} + +.article .collapsed { + display: none; +} /* Footer *******************************************************************************/ + #footer { clear: both; padding: 0 30px; +} + +@media screen and (max-width: 1023px) { + #page { + margin: 0px; + display: flex; + flex-direction: column; + } + #content, #sidebar { + float: none; + width: 100%; + } }
\ No newline at end of file diff --git a/common/custom/views/archive/footer.tpl.php b/common/custom/views/archive/footer.tpl.php index bfc0210..c9c84b1 100755 --- a/common/custom/views/archive/footer.tpl.php +++ b/common/custom/views/archive/footer.tpl.php @@ -1,3 +1,3 @@ <div id="footer"> - <p><? echo str_replace('%s', 'href="http://moonmoon.org"', _g('Powered by <a %s>moonmoon</a>'))?> | <a href="./admin/"><?=_g('Administration')?></a></p> + <p><?php echo str_replace('%s', 'href="http://moonmoon.org"', _g('Powered by <a %s>moonmoon</a>'))?> | <a href="./admin/"><?=_g('Administration')?></a></p> </div> diff --git a/common/custom/views/archive/index.tpl.php b/common/custom/views/archive/index.tpl.php index 2bb8f36..16bc6ab 100755 --- a/common/custom/views/archive/index.tpl.php +++ b/common/custom/views/archive/index.tpl.php @@ -29,12 +29,12 @@ header('Content-type: text/html; charset=UTF-8'); <meta http-equiv="Content-Style-Type" content="text/css" /> <title><?php echo $PlanetConfig->getName(); ?></title> - <?php include(dirname(__FILE__).'/head.tpl.php'); ?> + <?php include(__DIR__.'/head.tpl.php'); ?> </head> <body> <div id="page"> - <?php include(dirname(__FILE__).'/top.tpl.php'); ?> + <?php include(__DIR__.'/top.tpl.php'); ?> <div id="content"> <?php if (0 == count($items)) :?> @@ -106,9 +106,9 @@ header('Content-type: text/html; charset=UTF-8'); <?php endif; ?> </div> - <?php include_once(dirname(__FILE__).'/sidebar.tpl.php'); ?> + <?php include_once(__DIR__.'/sidebar.tpl.php'); ?> - <?php include(dirname(__FILE__).'/footer.tpl.php'); ?> + <?php include(__DIR__.'/footer.tpl.php'); ?> </div> </body> </html> diff --git a/common/custom/views/archive/sidebar.tpl.php b/common/custom/views/archive/sidebar.tpl.php index e21f377..7a5d080 100755 --- a/common/custom/views/archive/sidebar.tpl.php +++ b/common/custom/views/archive/sidebar.tpl.php @@ -1,5 +1,5 @@ <?php -$all_people = &$Planet->getPeople(); +$all_people = $Planet->getPeople(); usort($all_people, array('PlanetFeed', 'compare')); ?> <div id="sidebar"> diff --git a/common/custom/views/default/footer.tpl.php b/common/custom/views/default/footer.tpl.php index bfc0210..c9c84b1 100755 --- a/common/custom/views/default/footer.tpl.php +++ b/common/custom/views/default/footer.tpl.php @@ -1,3 +1,3 @@ <div id="footer"> - <p><? echo str_replace('%s', 'href="http://moonmoon.org"', _g('Powered by <a %s>moonmoon</a>'))?> | <a href="./admin/"><?=_g('Administration')?></a></p> + <p><?php echo str_replace('%s', 'href="http://moonmoon.org"', _g('Powered by <a %s>moonmoon</a>'))?> | <a href="./admin/"><?=_g('Administration')?></a></p> </div> diff --git a/common/custom/views/default/index.tpl.php b/common/custom/views/default/index.tpl.php index 1bf037a..c33ffa5 100755 --- a/common/custom/views/default/index.tpl.php +++ b/common/custom/views/default/index.tpl.php @@ -10,9 +10,10 @@ header('Content-type: text/html; charset=UTF-8'); <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Script-Type" content="text/javascript" /> <meta http-equiv="Content-Style-Type" content="text/css" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title><?php echo $PlanetConfig->getName(); ?></title> - <?php include(dirname(__FILE__).'/head.tpl.php'); ?> + <?php include(__DIR__.'/head.tpl.php'); ?> </head> <body> @@ -20,7 +21,7 @@ header('Content-type: text/html; charset=UTF-8'); document.body.className += 'js'; </script> <div id="page"> - <?php include(dirname(__FILE__).'/top.tpl.php'); ?> + <?php include(__DIR__.'/top.tpl.php'); ?> <div id="content"> <?php if (0 == count($items)) : ?> @@ -41,8 +42,7 @@ header('Content-type: text/html; charset=UTF-8'); <a href="<?php echo $item->get_permalink(); ?>" title="Go to original place"><?php echo $item->get_title(); ?></a> </h2> <p class="article-info"> - - <?php echo ($item->get_author()? $item->get_author()->get_name() : 'Anonymous'); ?>, + <?php echo strip_tags(($item->get_author()? $item->get_author()->get_name() : 'Anonymous')); ?>, <?php $ago = time() - $item->get_date('U'); //echo '<span title="'.Duration::toString($ago).' ago" class="date">'.date('d/m/Y', $item->get_date('U')).'</span>'; @@ -65,9 +65,9 @@ header('Content-type: text/html; charset=UTF-8'); <?php endif; ?> </div> - <?php include_once(dirname(__FILE__).'/sidebar.tpl.php'); ?> + <?php include_once(__DIR__.'/sidebar.tpl.php'); ?> - <?php include(dirname(__FILE__).'/footer.tpl.php'); ?> + <?php include(__DIR__.'/footer.tpl.php'); ?> </div> </body> </html> diff --git a/common/custom/views/default/sidebar.tpl.php b/common/custom/views/default/sidebar.tpl.php index 830f3c6..13ae626 100755 --- a/common/custom/views/default/sidebar.tpl.php +++ b/common/custom/views/default/sidebar.tpl.php @@ -1,5 +1,5 @@ <?php -$all_people = &$Planet->getPeople(); +$all_people = $Planet->getPeople(); usort($all_people, array('PlanetFeed', 'compare')); ?> <div id="sidebar" class="aside"> diff --git a/common/custom/views/default/top.tpl.php b/common/custom/views/default/top.tpl.php index dd6206f..6db425c 100644 --- a/common/custom/views/default/top.tpl.php +++ b/common/custom/views/default/top.tpl.php @@ -1,3 +1,3 @@ <div id="header"> - <h1 id="top"><a href="<?php echo $PlanetConfig->getUrl() ?>"><?php echo $PlanetConfig->getName(); ?></a></h1> + <h1 id="top"><a href="<?php echo $PlanetConfig->getUrl(); ?>"><?php echo $PlanetConfig->getName(); ?></a></h1> </div>
\ No newline at end of file diff --git a/common/custom/views/install.tpl.php b/common/custom/views/install.tpl.php new file mode 100644 index 0000000..9eaff65 --- /dev/null +++ b/common/custom/views/install.tpl.php @@ -0,0 +1,119 @@ +<!DOCTYPE html> +<html lang="en"> +<meta charset="utf-8"/> +<head> + <title><?=_g('moonmoon installation')?></title> + <style> + body { + font: normal 1em sans-serif; + width: 500px; + margin: 0 auto; + } + + /* Error */ + td.ok { + color: #090; + } + + td.fail { + color: #900; + font-weight: bold; + } + th { + text-align: left; + } + + /* Install */ + .field label { + display: block; + } + + .submit { + font-size: 2em; + } + + </style> +</head> + +<body> +<h1><?=_g('moonmoon installation')?></h1> + +<?php if ($status == 'error') : ?> + <div id="compatibility"> + <h2>Sorry, your server is not compatible with moonmoon.</h2> + + <h3>Your server does not fulfill the requirements</h3> + <table> + <thead> + <tr> + <th>Test</th> + <th>Result</th> + </tr> + </thead> + <tbody> + <?php echo $strInstall ?> + </tbody> + </table> + + <h3>Troubleshooting</h3> + <p>To install moonmoon, try the following changes:</p> + <ul> + <?php echo $strRecommendation; ?> + </ul> + </div> + +<?php elseif ($status == 'install') : ?> + <div> + <form method="post" action=""> + <fieldset> + <input type="hidden" id="url" name="url" value="" readonly="readonly"/> + <script> + <!-- + document.forms[0].elements[1].value = document.URL.replace('install.php',''); + --> + </script> + + <p class="field"> + <label for="title">Title:</label> + <input type="text" id="title" name="title" value="My website"/> + </p> + <!-- + <p class="field"> + <label>Administrator login:</label> <code>admin</code> + </p> + --> + <p class="field"> + <label for="password">Administrator password:</label> + <input type="text" id="password" name="password" class="text password" value="admin" /> + </p> + <p class="field"> + <label for="locale">Language:</label> + <select name="locale" id="locale"> + <option selected="selected" value="en">English</option> + <option value="es">Español</option> + <option value="fr">Français</option> + <option value="en">Deutsch</option> + </select> + </p> + <p> + <input type="submit" class="submit" value="Install"/> + </p> + </fieldset> + </form> + </div> + +<?php elseif ($status =='installed'): ?> + + <p><?=_g('Congratulations! Your moonmoon is ready.')?></p> + <h3><?=_g("What's next?")?></h3> + <ol> + <li> + <?=_g('<strong>Delete</strong> <code>install.php</code> with your FTP software.')?> + </li> + <li> + <?=_g('Use your password to go to the <a href="./admin/">administration panel</a>')?> + </li> + </ol> +<?php endif; ?> +</body> +</html> diff --git a/common/index.php b/common/index.php index 60d5e77..3a2c1e4 100755 --- a/common/index.php +++ b/common/index.php @@ -1,6 +1,6 @@ <?php -include_once(dirname(__FILE__).'/app/app.php'); -include_once(dirname(__FILE__).'/app/lib/Cache.php'); +include_once(__DIR__.'/app/app.php'); +include_once(__DIR__.'/app/lib/Cache.php'); //Installed ? if (!isset($Planet)) { @@ -10,7 +10,7 @@ if (!isset($Planet)) { //Load from cache $items = Array(); -if (0 < $Planet->loadOpml(dirname(__FILE__).'/custom/people.opml')) { +if (0 < $Planet->loadOpml(__DIR__.'/custom/people.opml')) { $Planet->loadFeeds(); $items = $Planet->getItems(); } @@ -21,7 +21,7 @@ $cache_key = (count($items)) ? $items[0]->get_id() : ''; $last_modified = (count($items)) ? $items[0]->get_date() : ''; $cache_duration = $PlanetConfig->getOutputTimeout()*60; -Cache::setStore(dirname(__FILE__) . '/' . $conf['cachedir'] . '/'); +Cache::setStore(__DIR__ . '/' . $conf['cachedir'] . '/'); if (isset($_GET['type']) && $_GET['type'] == 'atom10') { /* XXX: Redirect old ATOM feeds to new url to make sure our users don't @@ -35,16 +35,18 @@ if (isset($_GET['type']) && $_GET['type'] == 'atom10') { //Go display if (!isset($_GET['type']) || - !is_file(dirname(__FILE__).'/custom/views/'.$_GET['type'].'/index.tpl.php') || - strpos($_GET['type'], DIRECTORY_SEPARATOR)){ + !is_file(__DIR__.'/custom/views/'.$_GET['type'].'/index.tpl.php') || + strpos($_GET['type'], DIRECTORY_SEPARATOR) || strpos($GET['type'], '..')){ $_GET['type'] = 'default'; } if (!OutputCache::Start($_GET['type'], $cache_key, $cache_duration)) { - include_once(dirname(__FILE__).'/custom/views/'.$_GET['type'].'/index.tpl.php'); + include_once(__DIR__.'/custom/views/'.$_GET['type'].'/index.tpl.php'); OutputCache::End(); } -echo "<!--"; -var_dump($Planet->errors); -echo "-->"; +if ($conf['debug'] === true) { + echo "<!-- \$Planet->errors:\n"; + var_dump($Planet->errors); + echo "-->"; +} diff --git a/common/install.php b/common/install.php index 3a3eae7..86e1123 100755 --- a/common/install.php +++ b/common/install.php @@ -1,6 +1,6 @@ <?php -require_once dirname(__FILE__) . '/app/app.php'; +require_once __DIR__ . '/app/app.php'; // This is an helper function returning an html table row to avoid code duplication function installStatus($str, $msg, $result) { @@ -8,31 +8,32 @@ function installStatus($str, $msg, $result) { return '<tr><td>' . $str . '</td><td class="' . $class . '">' . $msg . '</td></tr>'; } -// If the password and config files exist, moonmoon is already installed -if (file_exists(dirname(__FILE__) . '/custom/config.yml') - && file_exists(dirname(__FILE__) . '/admin/inc/pwd.inc.php')) { +// If the config file exists and the auth variables are set, moonmoon is already installed +if (is_installed()) { $status = 'installed'; -} elseif (isset($_REQUEST['url'])) { +} elseif (isset($_POST['url'])) { + + // Do no try to use the file of an invalid locale + if (strstr($_POST['locale'], '..') !== false + || !file_exists(__DIR__ . "/app/l10n/${_REQUEST['locale']}.lang")) { + $_POST['locale'] = 'en'; + } + $save = array(); //Save config file - $config = array( - 'url' => filter_var($_REQUEST['url'], FILTER_SANITIZE_ENCODED), - 'name' => filter_var($_REQUEST['title'], FILTER_SANITIZE_SPECIAL_CHARS), - 'locale' => filter_var($_REQUEST['locale'], FILTER_SANITIZE_SPECIAL_CHARS), - 'items' => 10, - 'shuffle' => 0, - 'refresh' => 240, - 'cache' => 10, - 'nohtml' => 0, - 'postmaxlength' => 0, - 'cachedir' => './cache' - ); + $config = array_merge(PlanetConfig::$defaultConfig, [ + 'url' => $_POST['url'], + 'name' => filter_var($_POST['title'], FILTER_SANITIZE_SPECIAL_CHARS), + 'locale' => $_POST['locale'], + ]); $CreatePlanetConfig = new PlanetConfig($config); - $save['config'] = file_put_contents(dirname(__FILE__).'/custom/config.yml', $CreatePlanetConfig->toYaml()); + $save['config'] = file_put_contents(custom_path('config.yml'), $CreatePlanetConfig->toYaml()); + + OpmlManager::save(new Opml(), custom_path('people.opml')); //Save password - $save['password'] = file_put_contents(dirname(__FILE__).'/admin/inc/pwd.inc.php', '<?php $login="admin"; $password="'.md5($_REQUEST['password']).'"; ?>'); + $save['password'] = file_put_contents(admin_path('inc/pwd.inc.php'), '<?php $login="admin"; $password="'.md5($_POST['password']).'"; ?>'); if (0 != ($save['config'] + $save['password'])) { $status = 'installed'; @@ -40,12 +41,12 @@ if (file_exists(dirname(__FILE__) . '/custom/config.yml') } else { // We start by malking sure we have PHP5 as a base requirement - if(phpversion() >= 5) { - $strInstall = installStatus('Server is running PHP5', 'OK',true); + if(version_compare(PHP_VERSION, '5.6.0') >= 0) { + $strInstall = installStatus('Server is running at least PHP 5.6', 'OK',true); $strRecommendation = ''; } else { - $strInstall = installStatus('Server is running PHP5', 'FAIL',false); - $strRecommendation = '<li>Check your server documentation to activate PHP5</li>'; + $strInstall = installStatus('Server is running at least PHP 5.6', 'FAIL',false); + $strRecommendation = '<li>Check your server documentation to activate at least PHP 5.6</li>'; } // Writable file requirements @@ -56,10 +57,11 @@ if (file_exists(dirname(__FILE__) . '/custom/config.yml') '/cache', ); - // We now test that all required files are writable + // We now test that all required files and directories are writable. foreach ($tests as $v) { - if(is_writable(dirname(__FILE__) . $v)) { + if(touch(__DIR__ . $v)) { $strInstall .= installStatus("<code>$v</code> is writable", 'OK', true); + unlink(__DIR__.$v); } else { $strInstall .= installStatus("<code>$v</code> is writable", 'FAIL',false); $strRecommendation .= "<li>Make <code>$v</code> writable with CHMOD</li>"; @@ -70,121 +72,5 @@ if (file_exists(dirname(__FILE__) . '/custom/config.yml') $status = ($strRecommendation != '') ? 'error' : 'install'; } -?> -<!DOCTYPE html> -<html lang="en"> -<meta charset="utf-8"> -<head> - <title><?=_g('moonmoon installation')?></title> - <style> - body { - font: normal 1em sans-serif; - width: 500px; - margin: 0 auto; - } - - /* Error */ - td.ok { - color: #090; - } - - td.fail { - color: #900; - font-weight: bold; - } - th { - text-align: left; - } - - /* Install */ - .field label { - display: block; - } - - .submit { - font-size: 2em; - } - - </style> -</head> - -<body> - <h1><?=_g('moonmoon installation')?></h1> - - <?php if ($status == 'error') : ?> - <div id="compatibility"> - <h2>Sorry, your server is not compatible with moonmoon.</h2> - - <h3>Your server does not fulfill the requirements</h3> - <table> - <thead> - <tr> - <th>Test</th> - <th>Result</th> - </tr> - </thead> - <tbody> - <?php echo $strInstall ?> - </tbody> - </table> - - <h3>Troubleshooting</h3> - <p>To install moonmoon, try the following changes:</p> - <ul> - <?php echo $strRecommendation; ?> - </ul> - </div> - - <?php elseif ($status == 'install') : ?> - <div> - <form method="post" action=""> - <fieldset> - <input type="hidden" id="url" name="url" value="" readonly="readonly"/> - <script> - <!-- - document.forms[0].elements[1].value = document.URL.replace('install.php',''); - --> - </script> - - <p class="field"> - <label for="title">Title:</label> - <input type="text" id="title" name="title" value="My website"/> - </p> - <!-- - <p class="field"> - <label>Administrator login:</label> <code>admin</code> - </p> - --> - <p class="field"> - <label for="password">Administrator password:</label> - <input type="text" id="password" name="password" class="text password" value="admin" /> - </p> - <p class="field"> - <label for="locale">Language:</label> - <select name="locale" id="locale"> - <option selected="selected" value="en">English</option> - <option value="fr">Français</option> - </select> - </p> - <p> - <input type="submit" class="submit" value="Install"/> - </p> - </fieldset> - </form> - </div> - - <?php elseif ($status =='installed'): ?> - <p><?=_g('Congratulations! Your moonmoon is ready.')?></p> - <h3><?=_g("What's next?")?></h3> - <ol> - <li> - <?=_g('<strong>Delete</strong> <code>install.php</code> with your FTP software.')?> - </li> - <li> - <?=_g('Use your password to go to the <a href="./admin/">administration panel</a>')?> - </li> - </ol> - <?php endif; ?> -</body> -</html> +require_once views_path('install.tpl.php');
\ No newline at end of file diff --git a/common/phpunit.xml b/common/phpunit.xml new file mode 100644 index 0000000..835272f --- /dev/null +++ b/common/phpunit.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/5.7/phpunit.xsd" + bootstrap="vendor/autoload.php" + backupGlobals="false" + beStrictAboutCoversAnnotation="true" + beStrictAboutOutputDuringTests="true" + beStrictAboutTestsThatDoNotTestAnything="true" + beStrictAboutTodoAnnotatedTests="true" + verbose="true"> + <testsuite> + <directory suffix="Test.php">tests</directory> + </testsuite> + + <filter> + <whitelist processUncoveredFilesFromWhitelist="true"> + <directory suffix=".php">.</directory> + </whitelist> + </filter> +</phpunit> diff --git a/common/postload.php b/common/postload.php index 8f18678..7341b82 100644 --- a/common/postload.php +++ b/common/postload.php @@ -1,16 +1,30 @@ <?php -include_once(dirname(__FILE__).'/app/app.php'); -$Planet->addPerson( - new PlanetFeed( - '', - htmlspecialchars_decode($_GET['url'], ENT_QUOTES), - '' - ) -); +require_once __DIR__.'/app/app.php'; -//Load feeds -$Planet->download(1); -header("Content-type: image/png"); -readfile(dirname(__FILE__)."/custom/img/feed.png"); -die(); +if (!is_installed()) { + die(); +} + +$xml = new SimpleXMLElement(file_get_contents(custom_path('people.opml'))); + +foreach ($xml->xpath('/opml/body/outline[@xmlUrl]') as $element) +{ + if ($element->attributes()->xmlUrl == $_GET['url']) + { + $person = new PlanetFeed( + '', + $_GET['url'], + '', + false + ); + $Planet->addPerson($person); + + $Planet->download(1); + header('Content-type: image/png'); + readfile(custom_path('img/feed.png')); + die(); + } +} + +echo 'Updating this URL is not allowed.';
\ No newline at end of file diff --git a/common/tests/GuzzleHarness.php b/common/tests/GuzzleHarness.php new file mode 100644 index 0000000..a3f2ac4 --- /dev/null +++ b/common/tests/GuzzleHarness.php @@ -0,0 +1,20 @@ +<?php + +use \PHPUnit\Framework\TestCase; +use \GuzzleHttp\Client; + +class GuzzleHarness extends TestCase +{ + + /** @var GuzzleHttp\Client */ + protected $client = null; + + public function setUp() + { + $this->client = new Client([ + 'base_uri' => 'http://127.0.0.1:8081', + 'timeout' => 1, + ]); + } + +}
\ No newline at end of file diff --git a/common/tests/HelpersTest.php b/common/tests/HelpersTest.php new file mode 100644 index 0000000..141e604 --- /dev/null +++ b/common/tests/HelpersTest.php @@ -0,0 +1,15 @@ +<?php + +use PHPUnit\Framework\TestCase; + +class HelpersTest extends TestCase +{ + function test_constant_time_compare() + { + $this->assertTrue(_hash_equals('abc', 'abc')); + $this->assertFalse(_hash_equals('abc', 'ab')); + $this->assertFalse(_hash_equals('ab', 'abc')); + $this->assertFalse(_hash_equals('abcd', 'adbc')); + $this->assertFalse(_hash_equals(0, 0)); + } +} diff --git a/common/tests/InstallTest.php b/common/tests/InstallTest.php new file mode 100644 index 0000000..7615f18 --- /dev/null +++ b/common/tests/InstallTest.php @@ -0,0 +1,61 @@ +<?php + +require_once 'GuzzleHarness.php'; + +class InstallTest extends GuzzleHarness { + + public function setUp() + { + parent::setUp(); + removeCustomFiles(); + } + + public function tearDown() + { + parent::tearDown(); + removeCustomFiles(); + } + + public function test_index_page_tells_moonmoon_is_not_installed() + { + $res = $this->client->get('/index.php'); + $this->assertEquals(200, $res->getStatusCode()); + $this->assertContains('install moonmoon', (string) $res->getBody()); + } + + public function test_install_page_loads_without_error() + { + $res = $this->client->get('/install.php'); + $this->assertEquals(200, $res->getStatusCode()); + $this->assertContains('Administrator password', (string) $res->getBody()); + } + + /** + * Regression test, `people.opml` was created by requesting `/install.php` + * even if the site was not installed: `touch()` was called to see if + * the path was writable but the file was not removed. + */ + public function test_get_install_page_should_not_create_custom_files() + { + $this->client->get('/install.php'); + $this->assertFalse(file_exists(custom_path('people.opml'))); + $this->assertFalse(file_exists(custom_path('config.yml'))); + $this->assertFalse(file_exists(custom_path('inc/pwc.inc.php'))); + } + + public function test_install_button() + { + $data = [ + 'url' => 'http://127.0.0.1:8081/', + 'title' => 'My website', + 'password' => 'admin', + 'locale' => 'en', + ]; + + $res = $this->client->request('POST', '/install.php', [ + 'form_params' => $data + ]); + $this->assertEquals(200, $res->getStatusCode()); + $this->assertContains('Your moonmoon is ready.', (string) $res->getBody()); + } +}
\ No newline at end of file diff --git a/common/tests/PlanetConfigTest.php b/common/tests/PlanetConfigTest.php new file mode 100644 index 0000000..4db6e90 --- /dev/null +++ b/common/tests/PlanetConfigTest.php @@ -0,0 +1,73 @@ +<?php + +use PHPUnit\Framework\TestCase; + +class PlanetConfigTest extends TestCase +{ + public function test_default_configuration_values() + { + $conf = new PlanetConfig(); + $this->assertEquals('http://www.example.com/', $conf->getUrl()); + } + + public function test_merge_user_configuration_with_default_one() + { + $conf = new PlanetConfig(['url' => 'http://foobar.tld']); + $this->assertEquals('http://foobar.tld', $conf->getUrl()); + } + + public function test_generic_getter() + { + $conf = new PlanetConfig(['foo' => 'bar']); + $this->assertEquals('bar', $conf->foo); + } + + public function test_generic_setter() + { + $conf = new PlanetConfig(); + $conf->foo = 'bar'; + $this->assertEquals('bar', $conf->foo); + } + + public function test_normalize_key_name_on_merge() + { + $conf = new PlanetConfig(['FOO' => 'bar']); + $this->assertEquals('bar', $conf->foo); + } + + public function test_normalize_key_name_on_generic_getter() + { + $conf = new PlanetConfig(['foo' => 'bar']); + $this->assertEquals('bar', $conf->FOO); + } + + public function test_normalize_key_name_on_generic_setter() + { + $conf = new PlanetConfig(); + $conf->FOO = 'bar'; + $this->assertEquals('bar', $conf->foo); + } + + public function test_to_array() + { + $conf = new PlanetConfig(['foo' => 'bar']); + $this->assertEquals('bar', $conf->toArray()['foo']); + $this->assertEquals('http://www.example.com/', $conf->toArray()['url']); + } + + public function test_constructor_without_default_config() + { + $conf = new PlanetConfig(['foo' => 'bar'], false); + $this->assertEquals('bar', $conf->foo); + $this->assertEquals(1, sizeof($conf->toArray())); + } + + public function test_to_yaml() + { + $conf = new PlanetConfig([], false); + $this->assertEquals("---\n", $conf->toYaml()); + + $conf = new PlanetConfig(['foo' => 'bar'], false); + $this->assertEquals("---\nfoo: bar\n", $conf->toYaml()); + } +} diff --git a/common/tests/PlanetErrorTest.php b/common/tests/PlanetErrorTest.php new file mode 100644 index 0000000..d2f4599 --- /dev/null +++ b/common/tests/PlanetErrorTest.php @@ -0,0 +1,12 @@ +<?php + +use PHPUnit\Framework\TestCase; + +class PlanetErrorTest extends TestCase +{ + public function test_to_string() + { + $error = new PlanetError(1, 'foo'); + $this->assertEquals('notice: foo', $error->toString()); + } +}
\ No newline at end of file diff --git a/common/tests/PlanetTest.php b/common/tests/PlanetTest.php new file mode 100644 index 0000000..5cec1ce --- /dev/null +++ b/common/tests/PlanetTest.php @@ -0,0 +1,84 @@ +<?php + +use PHPUnit\Framework\TestCase; + +class FoolCategory { + + protected $name; + + function __construct($name) + { + $this->name = $name; + } + + function get_label() + { + return $this->name; + } +} + +class FoolItem +{ + protected $categories; + + function __construct($categories) + { + foreach ($categories as $c) + $this->categories[] = new FoolCategory($c); + } + + function get_categories() { + return $this->categories; + } +} + +class PlanetTest extends TestCase +{ + + protected $planet; + protected $items; + + public function setUp() + { + $this->planet = new Planet(); + + $this->items = array( + new FoolItem(array('catA', 'catB', 'catC')), + new FoolItem(array('catB')), + new FoolItem(array('catA')), + new FoolItem(array('catC')) + ); + } + + protected function _after() + { + unset($this->planet); + } + + public function testFilterItemsByCategoryWithInvalidCategory() + { + $this->assertEquals(count($this->planet->_filterItemsByCategory($this->items, null)), count($this->items)); + $this->assertEquals(count($this->planet->_filterItemsByCategory($this->items, ' ')), count($this->items)); + } + + public function testFilterItemsByCategoryWithNonUsedCategory() + { + $this->assertEquals(count($this->planet->_filterItemsByCategory($this->items, 'catD')), 0); + } + + public function testFilterItemsByCategoryWithValidCategory() + { + $this->assertEquals(count($this->planet->_filterItemsByCategory($this->items, 'catA')), 2); + $this->assertEquals(count($this->planet->_filterItemsByCategory($this->items, 'catB')), 2); + $this->assertEquals(count($this->planet->_filterItemsByCategory($this->items, 'catC')), 2); + $this->assertEquals(count($this->planet->_filterItemsByCategory($this->items, 'CATA')), 2); + } + + public function testFilterItemsByCategoryWithMultipleCategory() + { + $this->assertEquals(count($this->planet->_filterItemsByCategory($this->items, 'catA,catB')), 3); + $this->assertEquals(count($this->planet->_filterItemsByCategory($this->items, 'catA,catB,catC')), 4); + $this->assertEquals(count($this->planet->_filterItemsByCategory($this->items, 'catA, catB')), 3); + } + +} |