summaryrefslogtreecommitdiffstats
path: root/common
diff options
context:
space:
mode:
Diffstat (limited to 'common')
-rw-r--r--common/CONTRIBUTING.md37
-rw-r--r--common/LICENSE1
-rw-r--r--common/README.md66
-rw-r--r--common/VERSION2
-rwxr-xr-xcommon/admin/administration.php11
-rw-r--r--common/admin/changepassword.php8
-rw-r--r--common/admin/inc/auth.inc.php16
-rwxr-xr-xcommon/admin/index.php15
-rwxr-xr-xcommon/admin/login.php7
-rw-r--r--common/admin/logout.php9
-rw-r--r--common/admin/purgecache.php14
-rwxr-xr-xcommon/admin/subscriptions.php30
-rwxr-xr-xcommon/app/app.php37
-rw-r--r--common/app/classes/CSRF.php55
-rw-r--r--common/app/classes/Cache.php254
-rw-r--r--common/app/classes/Opml.php70
-rw-r--r--common/app/classes/OpmlManager.php50
-rw-r--r--common/app/classes/Planet.php266
-rw-r--r--common/app/classes/PlanetConfig.php166
-rw-r--r--common/app/classes/PlanetError.php23
-rw-r--r--common/app/classes/PlanetFeed.php20
-rw-r--r--common/app/classes/PlanetItem.php11
-rwxr-xr-xcommon/app/classes/Simplel10n.php52
-rw-r--r--common/app/helpers.php130
-rw-r--r--common/app/l10n/de.lang219
-rw-r--r--common/app/l10n/en.lang4
-rw-r--r--common/app/l10n/es.lang244
-rwxr-xr-xcommon/app/l10n/extract.php13
-rwxr-xr-xcommon/app/l10n/fr.lang4
-rw-r--r--common/atom.php8
-rwxr-xr-xcommon/bin/build_release.sh20
-rw-r--r--common/composer.json35
-rw-r--r--common/composer.lock1599
-rw-r--r--common/cron.php10
-rw-r--r--common/custom/img/moonmoon.pngbin0 -> 73408 bytes
-rw-r--r--common/custom/img/moonmoon@128w.pngbin0 -> 8671 bytes
-rw-r--r--common/custom/img/moonmoon@256w.pngbin0 -> 24255 bytes
-rw-r--r--common/custom/style/default.css136
-rwxr-xr-xcommon/custom/views/archive/footer.tpl.php2
-rwxr-xr-xcommon/custom/views/archive/index.tpl.php8
-rwxr-xr-xcommon/custom/views/archive/sidebar.tpl.php2
-rwxr-xr-xcommon/custom/views/default/footer.tpl.php2
-rwxr-xr-xcommon/custom/views/default/index.tpl.php12
-rwxr-xr-xcommon/custom/views/default/sidebar.tpl.php2
-rw-r--r--common/custom/views/default/top.tpl.php2
-rw-r--r--common/custom/views/install.tpl.php119
-rwxr-xr-xcommon/index.php22
-rwxr-xr-xcommon/install.php170
-rw-r--r--common/phpunit.xml20
-rw-r--r--common/postload.php40
-rw-r--r--common/tests/GuzzleHarness.php20
-rw-r--r--common/tests/HelpersTest.php15
-rw-r--r--common/tests/InstallTest.php61
-rw-r--r--common/tests/PlanetConfigTest.php73
-rw-r--r--common/tests/PlanetErrorTest.php12
-rw-r--r--common/tests/PlanetTest.php84
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?
+&iquest;Est&aacute; seguro de que desea vaciar la cach&eacute;?
+
+
+# Translation note: Vaciar la caché
+;Clear cache
+Vaciar la cach&eacute;
+
+
+# Translation note: Vaciar la caché:
+;Clear cache:
+Vaciar la cach&eacute;:
+
+
+;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&eacute; har&aacute; que moonmoon recargue todas las fuentes web.
+
+
+# Translation note: Cambiar la contraseña de administración
+;Change administrator password
+Cambiar la contrase&ntilde;a de administraci&oacute;n
+
+
+# Translation note: Nueva contraseña:
+;New password:
+Nueva contrase&ntilde;a:
+
+
+# Translation note: Cambiar la contraseña
+;Change password
+Cambiar la contrase&ntilde;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&ntilde;adir una nueva fuente web
+
+
+# Translation note: Añadir fuente
+;Add Feed
+A&ntilde;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&aacute; de encontrar la fuente de forma autom&aacute;tica.
+
+
+;Manage existing feeds
+Administrar las fuentes existentes
+
+
+# Translation note: Número de fuentes
+;Number of feeds: %s
+N&uacute;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&oacute;n
+
+
+;Name
+Nombre
+
+# Translation note: Última entrada
+;Last entry
+&Uacute;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&aacute; en cach&eacute;
+
+
+# Translation note: Contraseña:
+;Password:
+Contrase&ntilde;a:
+
+
+;OK
+Entrar
+
+
+# Translation note: Administración de moonmoon
+;moonmoon administration
+Administraci&oacute;n de moonmoon
+
+
+# Translation note: Volver a la página principal
+;Back to main page
+Volver a la p&aacute;gina principal
+
+
+;Logout
+Salir
+
+
+;Feeds
+Fuentes
+
+
+# Translation note: Administración
+;Administration
+Administraci&oacute;n
+
+
+;Powered by <a %s>moonmoon</a>
+Desarrollado por <a %s>moonmoon</a>
+
+
+# Translation note: Sin artículos
+;No article
+Sin art&iacute;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&oacute;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&oacute;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&oacute;n de moonmoon
+
+
+# Translation note: ¡Felicidades! Su moonmoon está preparado.
+;Congratulations! Your moonmoon is ready.
+&iexcl;Felicidades! Su moonmoon est&aacute; preparado.
+
+
+# Translation note: ¿Y ahora?
+;What's next?
+&iquest;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&ntilde;a para acceder al <a href="./admin/">panel de administraci&oacute;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
new file mode 100644
index 0000000..10f9736
--- /dev/null
+++ b/common/custom/img/moonmoon.png
Binary files differ
diff --git a/common/custom/img/moonmoon@128w.png b/common/custom/img/moonmoon@128w.png
new file mode 100644
index 0000000..2a164aa
--- /dev/null
+++ b/common/custom/img/moonmoon@128w.png
Binary files differ
diff --git a/common/custom/img/moonmoon@256w.png b/common/custom/img/moonmoon@256w.png
new file mode 100644
index 0000000..b52c4bb
--- /dev/null
+++ b/common/custom/img/moonmoon@256w.png
Binary files differ
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);
+ }
+
+}