]> git.immae.eu Git - github/shaarli/Shaarli.git/commitdiff
Merge pull request #764 from ArthurHoaro/feature/history
authorArthurHoaro <arthur@hoa.ro>
Sat, 6 May 2017 15:12:06 +0000 (17:12 +0200)
committerGitHub <noreply@github.com>
Sat, 6 May 2017 15:12:06 +0000 (17:12 +0200)
History mechanism

45 files changed:
.gitignore
README.md
application/ApplicationUtils.php
application/LinkDB.php
application/PageBuilder.php
application/TimeZone.php
application/Updater.php
application/Utils.php
application/api/ApiUtils.php
application/api/controllers/ApiController.php
application/api/controllers/Links.php
composer.lock [new file with mode: 0644]
index.php
plugins/readityourself/book-open.png [deleted file]
plugins/readityourself/readityourself.html [deleted file]
plugins/readityourself/readityourself.meta [deleted file]
plugins/readityourself/readityourself.php [deleted file]
shaarli_version.php
tests/ApplicationUtilsTest.php
tests/TimeZoneTest.php
tests/UtilsTest.php
tests/api/controllers/PostLinkTest.php [new file with mode: 0644]
tests/languages/de/UtilsDeTest.php
tests/languages/en/UtilsEnTest.php
tests/languages/fr/UtilsFrTest.php
tests/plugins/PluginReadityourselfTest.php [deleted file]
tests/utils/ReferenceLinkDB.php
tpl/default/configure.html
tpl/default/css/shaarli.css
tpl/default/daily.html
tpl/default/fonts/Fira-Sans-regular.woff [deleted file]
tpl/default/fonts/Fira-Sans-regular.woff2 [deleted file]
tpl/default/fonts/Roboto-Bold.woff [new file with mode: 0644]
tpl/default/fonts/Roboto-Bold.woff2 [new file with mode: 0644]
tpl/default/fonts/Roboto-Regular.woff [new file with mode: 0644]
tpl/default/fonts/Roboto-Regular.woff2 [new file with mode: 0644]
tpl/default/import.html
tpl/default/install.html
tpl/default/js/shaarli.js
tpl/vintage/configure.html
tpl/vintage/css/shaarli.css
tpl/vintage/import.html
tpl/vintage/install.html
tpl/vintage/js/shaarli.js [new file with mode: 0644]
tpl/vintage/page.footer.html

index e64c8a4be448800066f3f99f754f56c5210c8580..984d9d1f410d688e5e61b1a626df6485879632ef 100644 (file)
@@ -13,7 +13,6 @@ pagecache
 *.rtpl.php
 
 # 3rd-party dependencies
-composer.lock
 vendor/
 
 # Release archives
index db1b901f27a3ae7709fef55bfc420f306fb39b95..cfdefc66da5763b050d61487900f35002a267d4b 100644 (file)
--- a/README.md
+++ b/README.md
@@ -6,13 +6,18 @@ _Do you want to share the links you discover?_
 _Shaarli is a minimalist delicious clone that you can install on your own server._
 _It is designed to be personal (single-user), fast and handy._
 
-[![](https://img.shields.io/travis/shaarli/Shaarli.svg?label=master)](https://travis-ci.org/shaarli/Shaarli)
+[![](https://img.shields.io/badge/stable-v0.7.1-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.7.1)
 [![](https://img.shields.io/travis/shaarli/Shaarli/stable.svg?label=stable)](https://travis-ci.org/shaarli/Shaarli)
-[![](https://img.shields.io/github/release/shaarli/shaarli.svg)](https://github.com/shaarli/Shaarli/releases/latest/)
-[![Docker repository](https://img.shields.io/docker/pulls/shaarli/shaarli.svg)](https://hub.docker.com/r/shaarli/shaarli/)
+&bull;
+[![](https://img.shields.io/badge/latest-v0.8.4-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.8.4)
+[![](https://img.shields.io/travis/shaarli/Shaarli/latest.svg?label=latest)](https://travis-ci.org/shaarli/Shaarli)
+&bull;
+[![](https://img.shields.io/badge/master-v0.9.x-blue.svg)](https://github.com/shaarli/Shaarli)
+[![](https://img.shields.io/travis/shaarli/Shaarli.svg?label=master)](https://travis-ci.org/shaarli/Shaarli)
 
 [![Join the chat at https://gitter.im/shaarli/Shaarli](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/shaarli/Shaarli)
 [![Bountysource](https://www.bountysource.com/badge/team?team_id=19583&style=bounties_received)](https://www.bountysource.com/teams/shaarli/issues)
+[![Docker repository](https://img.shields.io/docker/pulls/shaarli/shaarli.svg)](https://hub.docker.com/r/shaarli/shaarli/)
 
 ## Quickstart
 - [Wiki/documentation](https://github.com/shaarli/Shaarli/wiki)
index a0f482b0b9791e0f4c1e28b5406dc4895459a239..85dcbeebdb164858680ff68b9fbc1048340d05f1 100644 (file)
@@ -4,9 +4,13 @@
  */
 class ApplicationUtils
 {
+    /**
+     * @var string File containing the current version
+     */
+    public static $VERSION_FILE = 'shaarli_version.php';
+
     private static $GIT_URL = 'https://raw.githubusercontent.com/shaarli/Shaarli';
-    private static $GIT_BRANCHES = array('master', 'stable');
-    private static $VERSION_FILE = 'shaarli_version.php';
+    private static $GIT_BRANCHES = array('latest', 'stable');
     private static $VERSION_START_TAG = '<?php /* ';
     private static $VERSION_END_TAG = ' */ ?>';
 
@@ -29,6 +33,30 @@ class ApplicationUtils
             return false;
         }
 
+        return $data;
+    }
+
+    /**
+     * Retrieve the version from a remote URL or a file.
+     *
+     * @param string $remote  URL or file to fetch.
+     * @param int    $timeout For URLs fetching.
+     *
+     * @return bool|string The version or false if it couldn't be retrieved.
+     */
+    public static function getVersion($remote, $timeout = 2)
+    {
+        if (startsWith($remote, 'http')) {
+            if (($data = static::getLatestGitVersionCode($remote, $timeout)) === false) {
+                return false;
+            }
+        } else {
+            if (! is_file($remote)) {
+                return false;
+            }
+            $data = file_get_contents($remote);
+        }
+
         return str_replace(
             array(self::$VERSION_START_TAG, self::$VERSION_END_TAG, PHP_EOL),
             array('', '', ''),
@@ -65,13 +93,10 @@ class ApplicationUtils
                                        $isLoggedIn,
                                        $branch='stable')
     {
-        if (! $isLoggedIn) {
-            // Do not check versions for visitors
-            return false;
-        }
-
-        if (empty($enableCheck)) {
-            // Do not check if the user doesn't want to
+        // Do not check versions for visitors
+        // Do not check if the user doesn't want to
+        // Do not check with dev version
+        if (! $isLoggedIn || empty($enableCheck) || $currentVersion === 'dev') {
             return false;
         }
 
@@ -93,7 +118,7 @@ class ApplicationUtils
 
         // Late Static Binding allows overriding within tests
         // See http://php.net/manual/en/language.oop5.late-static-bindings.php
-        $latestVersion = static::getLatestGitVersionCode(
+        $latestVersion = static::getVersion(
             self::$GIT_URL . '/' . $branch . '/' . self::$VERSION_FILE
         );
 
index 2fb15040321339cd31aa127aad692416f33e5110..0d3c85bd9815e2c879d8b18160441dfd19fd80f5 100644 (file)
@@ -138,10 +138,10 @@ class LinkDB implements Iterator, Countable, ArrayAccess
         if (!isset($value['id']) || empty($value['url'])) {
             die('Internal Error: A link should always have an id and URL.');
         }
-        if ((! empty($offset) && ! is_int($offset)) || ! is_int($value['id'])) {
+        if (($offset !== null && ! is_int($offset)) || ! is_int($value['id'])) {
             die('You must specify an integer as a key.');
         }
-        if (! empty($offset) && $offset !== $value['id']) {
+        if ($offset !== null && $offset !== $value['id']) {
             die('Array offset and link ID must be equal.');
         }
 
index b133dee83644794f7617726a79e1d087f373f825..8e39455bffe443b975be7d376628e66382e7b8bc 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 
+use Shaarli\Config\ConfigManager;
+
 /**
  * This class is in charge of building the final page.
  * (This is basically a wrapper around RainTPL which pre-fills some fields.)
index 36a8fb122ada91a1832f25944895f8aeca599288..c1869ef87e1d0b96b105c792d8dc902c15ca5246 100644 (file)
@@ -1,23 +1,42 @@
 <?php
 /**
- * Generates the timezone selection form and JavaScript.
+ * Generates a list of available timezone continents and cities.
  *
- * Note: 'UTC/UTC' is mapped to 'UTC' to form a valid option
+ * Two distinct array based on available timezones
+ * and the one selected in the settings:
+ *   - (0) continents:
+ *     + list of available continents
+ *     + special key 'selected' containing the value of the selected timezone's continent
+ *   - (1) cities:
+ *     + list of available cities associated with their continent
+ *     + special key 'selected' containing the value of the selected timezone's city (without the continent)
  *
- * Example: preselect Europe/Paris
- *  list($htmlform, $js) = generateTimeZoneForm('Europe/Paris');
+ * Example:
+ *   [
+ *     [
+ *       'America',
+ *       'Europe',
+ *       'selected' => 'Europe',
+ *     ],
+ *     [
+ *       ['continent' => 'America', 'city' => 'Toronto'],
+ *       ['continent' => 'Europe', 'city' => 'Paris'],
+ *       'selected' => 'Paris',
+ *     ],
+ *   ];
  *
+ * Notes:
+ *   - 'UTC/UTC' is mapped to 'UTC' to form a valid option
+ *   - a few timezone cities includes the country/state, such as Argentina/Buenos_Aires
+ *   - these arrays are designed to build timezone selects in template files with any HTML structure
+ *
+ * @param array  $installedTimeZones  List of installed timezones as string
  * @param string $preselectedTimezone preselected timezone (optional)
  *
- * @return array containing the generated HTML form and Javascript code
+ * @return array[] continents and cities
  **/
-function generateTimeZoneForm($preselectedTimezone='')
+function generateTimeZoneData($installedTimeZones, $preselectedTimezone = '')
 {
-    // Select the server timezone
-    if ($preselectedTimezone == '') {
-        $preselectedTimezone = date_default_timezone_get();
-    }
-
     if ($preselectedTimezone == 'UTC') {
         $pcity = $pcontinent = 'UTC';
     } else {
@@ -27,62 +46,30 @@ function generateTimeZoneForm($preselectedTimezone='')
         $pcity = substr($preselectedTimezone, $spos+1);
     }
 
-    // The list is in the form 'Europe/Paris', 'America/Argentina/Buenos_Aires'
-    // We split the list in continents/cities.
-    $continents = array();
-    $cities = array();
-
-    // TODO: use a template to generate the HTML/Javascript form
-
-    foreach (timezone_identifiers_list() as $tz) {
+    $continents = [];
+    $cities = [];
+    foreach ($installedTimeZones as $tz) {
         if ($tz == 'UTC') {
             $tz = 'UTC/UTC';
         }
         $spos = strpos($tz, '/');
 
-        if ($spos !== false) {
-            $continent = substr($tz, 0, $spos);
-            $city = substr($tz, $spos+1);
-            $continents[$continent] = 1;
-
-            if (!isset($cities[$continent])) {
-                $cities[$continent] = '';
-            }
-            $cities[$continent] .= '<option value="'.$city.'"';
-            if ($pcity == $city) {
-                $cities[$continent] .= ' selected="selected"';
-            }
-            $cities[$continent] .= '>'.$city.'</option>';
+        // Ignore invalid timezones
+        if ($spos === false) {
+            continue;
         }
-    }
-
-    $continentsHtml = '';
-    $continents = array_keys($continents);
 
-    foreach ($continents as $continent) {
-        $continentsHtml .= '<option  value="'.$continent.'"';
-        if ($pcontinent == $continent) {
-            $continentsHtml .= ' selected="selected"';
-        }
-        $continentsHtml .= '>'.$continent.'</option>';
+        $continent = substr($tz, 0, $spos);
+        $city = substr($tz, $spos+1);
+        $cities[] = ['continent' => $continent, 'city' => $city];
+        $continents[$continent] = true;
     }
 
-    // Timezone selection form
-    $timezoneForm = 'Continent:';
-    $timezoneForm .= '<select name="continent" id="continent" onChange="onChangecontinent();">';
-    $timezoneForm .= $continentsHtml.'</select>';
-    $timezoneForm .= '&nbsp;&nbsp;&nbsp;&nbsp;City:';
-    $timezoneForm .= '<select name="city" id="city">'.$cities[$pcontinent].'</select><br />';
-
-    // Javascript handler - updates the city list when the user selects a continent
-    $timezoneJs = '<script>';
-    $timezoneJs .= 'function onChangecontinent() {';
-    $timezoneJs .= 'document.getElementById("city").innerHTML =';
-    $timezoneJs .= ' citiescontinent[document.getElementById("continent").value]; }';
-    $timezoneJs .= 'var citiescontinent = '.json_encode($cities).';';
-    $timezoneJs .= '</script>';
+    $continents = array_keys($continents);
+    $continents['selected'] = $pcontinent;
+    $cities['selected'] = $pcity;
 
-    return array($timezoneForm, $timezoneJs);
+    return [$continents, $cities];
 }
 
 /**
index efbfc832d352cb4872dd96a4f5a3e3162a88c4e3..0fb68c5aa151bedffe10e5c79e8207997ee812de 100644 (file)
@@ -396,6 +396,50 @@ class Updater
 
         return true;
     }
+
+    /**
+     * Update updates.check_updates_branch setting.
+     *
+     * If the current major version digit matches the latest branch
+     * major version digit, we set the branch to `latest`,
+     * otherwise we'll check updates on the `stable` branch.
+     *
+     * No update required for the dev version.
+     *
+     * Note: due to hardcoded URL and lack of dependency injection, this is not unit testable.
+     *
+     * FIXME! This needs to be removed when we switch to first digit major version
+     *        instead of the second one since the versionning process will change.
+     */
+    public function updateMethodCheckUpdateRemoteBranch()
+    {
+        if (shaarli_version === 'dev' || $this->conf->get('updates.check_updates_branch') === 'latest') {
+            return true;
+        }
+
+        // Get latest branch major version digit
+        $latestVersion = ApplicationUtils::getLatestGitVersionCode(
+            'https://raw.githubusercontent.com/shaarli/Shaarli/latest/shaarli_version.php',
+            5
+        );
+        if (preg_match('/(\d+)\.\d+$/', $latestVersion, $matches) === false) {
+            return false;
+        }
+        $latestMajor = $matches[1];
+
+        // Get current major version digit
+        preg_match('/(\d+)\.\d+$/', shaarli_version, $matches);
+        $currentMajor = $matches[1];
+
+        if ($currentMajor === $latestMajor) {
+            $branch = 'latest';
+        } else {
+            $branch = 'stable';
+        }
+        $this->conf->set('updates.check_updates_branch', $branch);
+        $this->conf->write($this->isLoggedIn);
+        return true;
+    }
 }
 
 /**
index 5c077450e3a65016f55b0ede559dad4552b47e2b..ab463af9749cf3a597305fa37e1e91dbcf26046f 100644 (file)
@@ -321,25 +321,117 @@ function normalize_spaces($string)
  * otherwise default format '%c' will be returned.
  *
  * @param DateTime $date to format.
+ * @param bool     $time Displays time if true.
  * @param bool     $intl Use international format if true.
  *
  * @return bool|string Formatted date, or false if the input is invalid.
  */
-function format_date($date, $intl = true)
+function format_date($date, $time = true, $intl = true)
 {
     if (! $date instanceof DateTime) {
         return false;
     }
 
     if (! $intl || ! class_exists('IntlDateFormatter')) {
-        return strftime('%c', $date->getTimestamp());
+        $format = $time ? '%c' : '%x';
+        return strftime($format, $date->getTimestamp());
     }
 
     $formatter = new IntlDateFormatter(
         setlocale(LC_TIME, 0),
         IntlDateFormatter::LONG,
-        IntlDateFormatter::LONG
+        $time ? IntlDateFormatter::LONG : IntlDateFormatter::NONE
     );
 
     return $formatter->format($date);
 }
+
+/**
+ * Check if the input is an integer, no matter its real type.
+ *
+ * PHP is a bit messy regarding this:
+ *   - is_int returns false if the input is a string
+ *   - ctype_digit returns false if the input is an integer or negative
+ *
+ * @param mixed $input value
+ *
+ * @return bool true if the input is an integer, false otherwise
+ */
+function is_integer_mixed($input)
+{
+    if (is_array($input) || is_bool($input) || is_object($input)) {
+        return false;
+    }
+    $input = strval($input);
+    return ctype_digit($input) || (startsWith($input, '-') && ctype_digit(substr($input, 1)));
+}
+
+/**
+ * Convert post_max_size/upload_max_filesize (e.g. '16M') parameters to bytes.
+ *
+ * @param string $val Size expressed in string.
+ *
+ * @return int Size expressed in bytes.
+ */
+function return_bytes($val)
+{
+    if (is_integer_mixed($val) || $val === '0' || empty($val)) {
+        return $val;
+    }
+    $val = trim($val);
+    $last = strtolower($val[strlen($val)-1]);
+    $val = intval(substr($val, 0, -1));
+    switch($last) {
+        case 'g': $val *= 1024;
+        case 'm': $val *= 1024;
+        case 'k': $val *= 1024;
+    }
+    return $val;
+}
+
+/**
+ * Return a human readable size from bytes.
+ *
+ * @param int $bytes value
+ *
+ * @return string Human readable size
+ */
+function human_bytes($bytes)
+{
+    if ($bytes === '') {
+        return t('Setting not set');
+    }
+    if (! is_integer_mixed($bytes)) {
+        return $bytes;
+    }
+    $bytes = intval($bytes);
+    if ($bytes === 0) {
+        return t('Unlimited');
+    }
+
+    $units = [t('B'), t('kiB'), t('MiB'), t('GiB')];
+    for ($i = 0; $i < count($units) && $bytes >= 1024; ++$i) {
+        $bytes /= 1024;
+    }
+
+    return round($bytes) . $units[$i];
+}
+
+/**
+ * Try to determine max file size for uploads (POST).
+ * Returns an integer (in bytes) or formatted depending on $format.
+ *
+ * @param mixed $limitPost   post_max_size PHP setting
+ * @param mixed $limitUpload upload_max_filesize PHP setting
+ * @param bool  $format      Format max upload size to human readable size
+ *
+ * @return int|string max upload file size
+ */
+function get_max_upload_size($limitPost, $limitUpload, $format = true)
+{
+    $size1 = return_bytes($limitPost);
+    $size2 = return_bytes($limitUpload);
+    // Return the smaller of two:
+    $maxsize = min($size1, $size2);
+    return $format ? human_bytes($maxsize) : $maxsize;
+}
index d40158652d1f66680cfd5ceea17623b9a8eef5a8..b8155a3442669e452234e8b41a2bfc780befb130 100644 (file)
@@ -12,7 +12,7 @@ class ApiUtils
     /**
      * Validates a JWT token authenticity.
      *
-     * @param string $token  JWT token extracted from the headers.
+     * @param string $token JWT token extracted from the headers.
      * @param string $secret API secret set in the settings.
      *
      * @throws ApiAuthorizationException the token is not valid.
@@ -50,7 +50,7 @@ class ApiUtils
     /**
      * Format a Link for the REST API.
      *
-     * @param array  $link     Link data read from the datastore.
+     * @param array $link Link data read from the datastore.
      * @param string $indexUrl Shaarli's index URL (used for relative URL).
      *
      * @return array Link data formatted for the REST API.
@@ -77,4 +77,35 @@ class ApiUtils
         }
         return $out;
     }
+
+    /**
+     * Convert a link given through a request, to a valid link for LinkDB.
+     *
+     * If no URL is provided, it will generate a local note URL.
+     * If no title is provided, it will use the URL as title.
+     *
+     * @param array  $input          Request Link.
+     * @param bool   $defaultPrivate Request Link.
+     *
+     * @return array Formatted link.
+     */
+    public static function buildLinkFromRequest($input, $defaultPrivate)
+    {
+        $input['url'] = ! empty($input['url']) ? cleanup_url($input['url']) : '';
+        if (isset($input['private'])) {
+            $private = filter_var($input['private'], FILTER_VALIDATE_BOOLEAN);
+        } else {
+            $private = $defaultPrivate;
+        }
+
+        $link = [
+            'title'         => ! empty($input['title']) ? $input['title'] : $input['url'],
+            'url'           => $input['url'],
+            'description'   => ! empty($input['description']) ? $input['description'] : '',
+            'tags'          => ! empty($input['tags']) ? implode(' ', $input['tags']) : '',
+            'private'       => $private,
+            'created'       => new \DateTime(),
+        ];
+        return $link;
+    }
 }
index 1dd47f17da5709cb5bdf605c35e28c61604675a0..f35b923a0c52acf8826a61008b8f91f22c28b35f 100644 (file)
@@ -51,4 +51,14 @@ abstract class ApiController
             $this->jsonStyle = null;
         }
     }
+
+    /**
+     * Get the container.
+     *
+     * @return Container
+     */
+    public function getCi()
+    {
+        return $this->ci;
+    }
 }
index d4f1a09c8529cd53a019ef759f73e850fa8250aa..0db10fd054daff621826d58affafdc2eaa04aa04 100644 (file)
@@ -97,11 +97,53 @@ class Links extends ApiController
      */
     public function getLink($request, $response, $args)
     {
-        if (! isset($this->linkDb[$args['id']])) {
+        if (!isset($this->linkDb[$args['id']])) {
             throw new ApiLinkNotFoundException();
         }
         $index = index_url($this->ci['environment']);
         $out = ApiUtils::formatLink($this->linkDb[$args['id']], $index);
+
         return $response->withJson($out, 200, $this->jsonStyle);
     }
+
+    /**
+     * Creates a new link from posted request body.
+     *
+     * @param Request  $request  Slim request.
+     * @param Response $response Slim response.
+     *
+     * @return Response response.
+     */
+    public function postLink($request, $response)
+    {
+        $data = $request->getParsedBody();
+        $link = ApiUtils::buildLinkFromRequest($data, $this->conf->get('privacy.default_private_links'));
+        // duplicate by URL, return 409 Conflict
+        if (! empty($link['url']) && ! empty($dup = $this->linkDb->getLinkFromUrl($link['url']))) {
+            return $response->withJson(
+                ApiUtils::formatLink($dup, index_url($this->ci['environment'])),
+                409,
+                $this->jsonStyle
+            );
+        }
+
+        $link['id'] = $this->linkDb->getNextId();
+        $link['shorturl'] = link_small_hash($link['created'], $link['id']);
+
+        // note: general relative URL
+        if (empty($link['url'])) {
+            $link['url'] = '?' . $link['shorturl'];
+        }
+
+        if (empty($link['title'])) {
+            $link['title'] = $link['url'];
+        }
+
+        $this->linkDb[$link['id']] = $link;
+        $this->linkDb->save($this->conf->get('resource.page_cache'));
+        $out = ApiUtils::formatLink($link, index_url($this->ci['environment']));
+        $redirect = $this->ci->router->relativePathFor('getLink', ['id' => $link['id']]);
+        return $response->withAddedHeader('Location', $redirect)
+                        ->withJson($out, 201, $this->jsonStyle);
+    }
 }
diff --git a/composer.lock b/composer.lock
new file mode 100644 (file)
index 0000000..b285fcc
--- /dev/null
@@ -0,0 +1,2424 @@
+{
+    "_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": "ffcfc8e42f14183de6ef90874429e77a",
+    "packages": [
+        {
+            "name": "container-interop/container-interop",
+            "version": "1.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/container-interop/container-interop.git",
+                "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/container-interop/container-interop/zipball/79cbf1341c22ec75643d841642dd5d6acd83bdb8",
+                "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8",
+                "shasum": ""
+            },
+            "require": {
+                "psr/container": "^1.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Interop\\Container\\": "src/Interop/Container/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "Promoting the interoperability of container objects (DIC, SL, etc.)",
+            "homepage": "https://github.com/container-interop/container-interop",
+            "time": "2017-02-14T19:40:03+00:00"
+        },
+        {
+            "name": "erusev/parsedown",
+            "version": "1.6.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/erusev/parsedown.git",
+                "reference": "3ebbd730b5c2cf5ce78bc1bf64071407fc6674b7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/erusev/parsedown/zipball/3ebbd730b5c2cf5ce78bc1bf64071407fc6674b7",
+                "reference": "3ebbd730b5c2cf5ce78bc1bf64071407fc6674b7",
+                "shasum": ""
+            },
+            "type": "library",
+            "autoload": {
+                "psr-0": {
+                    "Parsedown": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Emanuil Rusev",
+                    "email": "hello@erusev.com",
+                    "homepage": "http://erusev.com"
+                }
+            ],
+            "description": "Parser for Markdown.",
+            "homepage": "http://parsedown.org",
+            "keywords": [
+                "markdown",
+                "parser"
+            ],
+            "time": "2015-10-04T16:44:32+00:00"
+        },
+        {
+            "name": "katzgrau/klogger",
+            "version": "1.2.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/katzgrau/KLogger.git",
+                "reference": "a4ed373fa8a214aa4ae7aa4f221fe2c6ce862ef1"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/katzgrau/KLogger/zipball/a4ed373fa8a214aa4ae7aa4f221fe2c6ce862ef1",
+                "reference": "a4ed373fa8a214aa4ae7aa4f221fe2c6ce862ef1",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3",
+                "psr/log": "^1.0.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "4.0.*"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Katzgrau\\KLogger\\": "src/"
+                },
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Kenny Katzgrau",
+                    "email": "katzgrau@gmail.com"
+                },
+                {
+                    "name": "Dan Horrigan",
+                    "email": "dan@dhorrigan.com"
+                }
+            ],
+            "description": "A Simple Logging Class",
+            "keywords": [
+                "logging"
+            ],
+            "time": "2016-11-07T19:29:14+00:00"
+        },
+        {
+            "name": "nikic/fast-route",
+            "version": "v1.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/nikic/FastRoute.git",
+                "reference": "b5f95749071c82a8e0f58586987627054400cdf6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/nikic/FastRoute/zipball/b5f95749071c82a8e0f58586987627054400cdf6",
+                "reference": "b5f95749071c82a8e0f58586987627054400cdf6",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.4.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "FastRoute\\": "src/"
+                },
+                "files": [
+                    "src/functions.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Nikita Popov",
+                    "email": "nikic@php.net"
+                }
+            ],
+            "description": "Fast request router for PHP",
+            "keywords": [
+                "router",
+                "routing"
+            ],
+            "time": "2017-01-19T11:35:12+00:00"
+        },
+        {
+            "name": "pimple/pimple",
+            "version": "v3.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/silexphp/Pimple.git",
+                "reference": "a30f7d6e57565a2e1a316e1baf2a483f788b258a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/silexphp/Pimple/zipball/a30f7d6e57565a2e1a316e1baf2a483f788b258a",
+                "reference": "a30f7d6e57565a2e1a316e1baf2a483f788b258a",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "Pimple": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                }
+            ],
+            "description": "Pimple, a simple Dependency Injection Container",
+            "homepage": "http://pimple.sensiolabs.org",
+            "keywords": [
+                "container",
+                "dependency injection"
+            ],
+            "time": "2015-09-11T15:10:35+00:00"
+        },
+        {
+            "name": "psr/container",
+            "version": "1.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/container.git",
+                "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
+                "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Container\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common Container Interface (PHP FIG PSR-11)",
+            "homepage": "https://github.com/php-fig/container",
+            "keywords": [
+                "PSR-11",
+                "container",
+                "container-interface",
+                "container-interop",
+                "psr"
+            ],
+            "time": "2017-02-14T16:28:37+00:00"
+        },
+        {
+            "name": "psr/http-message",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/http-message.git",
+                "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
+                "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
+                "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",
+            "homepage": "https://github.com/php-fig/http-message",
+            "keywords": [
+                "http",
+                "http-message",
+                "psr",
+                "psr-7",
+                "request",
+                "response"
+            ],
+            "time": "2016-08-06T14:39:51+00:00"
+        },
+        {
+            "name": "psr/log",
+            "version": "1.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/log.git",
+                "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
+                "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Log\\": "Psr/Log/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for logging libraries",
+            "homepage": "https://github.com/php-fig/log",
+            "keywords": [
+                "log",
+                "psr",
+                "psr-3"
+            ],
+            "time": "2016-10-10T12:19:37+00:00"
+        },
+        {
+            "name": "pubsubhubbub/publisher",
+            "version": "dev-master",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/pubsubhubbub/php-publisher.git",
+                "reference": "a5d6a0e1cc9d49101c3904480e5b06cbb8addba7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/pubsubhubbub/php-publisher/zipball/a5d6a0e1cc9d49101c3904480e5b06cbb8addba7",
+                "reference": "a5d6a0e1cc9d49101c3904480e5b06cbb8addba7",
+                "shasum": ""
+            },
+            "require": {
+                "php": "~5.4 || ~7.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "pubsubhubbub\\publisher\\": "library/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "pubsubhubbub publisher Contributors",
+                    "homepage": "https://github.com/pubsubhubbub/php-publisher/contributors"
+                }
+            ],
+            "description": "pubsubhubbub implementation of publisher.",
+            "homepage": "https://github.com/pubsubhubbub/php-publisher",
+            "keywords": [
+                "data",
+                "feeds",
+                "publishers",
+                "pubsubhubbub"
+            ],
+            "time": "2016-11-15 06:24:01"
+        },
+        {
+            "name": "shaarli/netscape-bookmark-parser",
+            "version": "v2.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/shaarli/netscape-bookmark-parser.git",
+                "reference": "b65c7235d490bd933cdd5f71520dae656253adb9"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/shaarli/netscape-bookmark-parser/zipball/b65c7235d490bd933cdd5f71520dae656253adb9",
+                "reference": "b65c7235d490bd933cdd5f71520dae656253adb9",
+                "shasum": ""
+            },
+            "require": {
+                "katzgrau/klogger": "~1.0",
+                "php": ">=5.3.4"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "4.8.*"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "NetscapeBookmarkParser.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Kafene",
+                    "email": "io@kafene.org",
+                    "homepage": "https://github.com/kafene",
+                    "role": "Developer"
+                },
+                {
+                    "name": "VirtualTam",
+                    "email": "virtualtam@flibidi.net",
+                    "homepage": "https://github.com/virtualtam",
+                    "role": "Developer"
+                }
+            ],
+            "description": "Generic Netscape bookmark parser",
+            "homepage": "https://github.com/shaarli/netscape-bookmark-parser",
+            "keywords": [
+                "bookmark",
+                "link",
+                "netscape",
+                "parse"
+            ],
+            "time": "2017-03-08T20:11:40+00:00"
+        },
+        {
+            "name": "slim/slim",
+            "version": "3.8.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/slimphp/Slim.git",
+                "reference": "5385302707530b2bccee1769613ad769859b826d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/slimphp/Slim/zipball/5385302707530b2bccee1769613ad769859b826d",
+                "reference": "5385302707530b2bccee1769613ad769859b826d",
+                "shasum": ""
+            },
+            "require": {
+                "container-interop/container-interop": "^1.2",
+                "nikic/fast-route": "^1.0",
+                "php": ">=5.5.0",
+                "pimple/pimple": "^3.0",
+                "psr/container": "^1.0",
+                "psr/http-message": "^1.0"
+            },
+            "provide": {
+                "psr/http-message-implementation": "1.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.0",
+                "squizlabs/php_codesniffer": "^2.5"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Slim\\": "Slim"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Rob Allen",
+                    "email": "rob@akrabat.com",
+                    "homepage": "http://akrabat.com"
+                },
+                {
+                    "name": "Josh Lockhart",
+                    "email": "hello@joshlockhart.com",
+                    "homepage": "https://joshlockhart.com"
+                },
+                {
+                    "name": "Gabriel Manricks",
+                    "email": "gmanricks@me.com",
+                    "homepage": "http://gabrielmanricks.com"
+                },
+                {
+                    "name": "Andrew Smith",
+                    "email": "a.smith@silentworks.co.uk",
+                    "homepage": "http://silentworks.co.uk"
+                }
+            ],
+            "description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs",
+            "homepage": "https://slimframework.com",
+            "keywords": [
+                "api",
+                "framework",
+                "micro",
+                "router"
+            ],
+            "time": "2017-03-19T17:55:20+00:00"
+        }
+    ],
+    "packages-dev": [
+        {
+            "name": "doctrine/instantiator",
+            "version": "1.0.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/doctrine/instantiator.git",
+                "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d",
+                "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d",
+                "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"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
+                }
+            },
+            "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": "2015-06-14T21:17:01+00:00"
+        },
+        {
+            "name": "pdepend/pdepend",
+            "version": "2.5.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/pdepend/pdepend.git",
+                "reference": "0c50874333149c0dad5a2877801aed148f2767ff"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/pdepend/pdepend/zipball/0c50874333149c0dad5a2877801aed148f2767ff",
+                "reference": "0c50874333149c0dad5a2877801aed148f2767ff",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.7",
+                "symfony/config": "^2.3.0|^3",
+                "symfony/dependency-injection": "^2.3.0|^3",
+                "symfony/filesystem": "^2.3.0|^3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.4.0,<4.8",
+                "squizlabs/php_codesniffer": "^2.0.0"
+            },
+            "bin": [
+                "src/bin/pdepend"
+            ],
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "PDepend\\": "src/main/php/PDepend"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "description": "Official version of pdepend to be handled with Composer",
+            "time": "2017-01-19T14:23:36+00:00"
+        },
+        {
+            "name": "phpdocumentor/reflection-common",
+            "version": "1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
+                "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c",
+                "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.5"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.6"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "phpDocumentor\\Reflection\\": [
+                        "src"
+                    ]
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jaap van Otterdijk",
+                    "email": "opensource@ijaap.nl"
+                }
+            ],
+            "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
+            "homepage": "http://www.phpdoc.org",
+            "keywords": [
+                "FQSEN",
+                "phpDocumentor",
+                "phpdoc",
+                "reflection",
+                "static analysis"
+            ],
+            "time": "2015-12-27T11:43:31+00:00"
+        },
+        {
+            "name": "phpdocumentor/reflection-docblock",
+            "version": "3.1.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
+                "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/8331b5efe816ae05461b7ca1e721c01b46bafb3e",
+                "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.5",
+                "phpdocumentor/reflection-common": "^1.0@dev",
+                "phpdocumentor/type-resolver": "^0.2.0",
+                "webmozart/assert": "^1.0"
+            },
+            "require-dev": {
+                "mockery/mockery": "^0.9.4",
+                "phpunit/phpunit": "^4.4"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "phpDocumentor\\Reflection\\": [
+                        "src/"
+                    ]
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Mike van Riel",
+                    "email": "me@mikevanriel.com"
+                }
+            ],
+            "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
+            "time": "2016-09-30T07:12:33+00:00"
+        },
+        {
+            "name": "phpdocumentor/type-resolver",
+            "version": "0.2.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phpDocumentor/TypeResolver.git",
+                "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb",
+                "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.5",
+                "phpdocumentor/reflection-common": "^1.0"
+            },
+            "require-dev": {
+                "mockery/mockery": "^0.9.4",
+                "phpunit/phpunit": "^5.2||^4.8.24"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "phpDocumentor\\Reflection\\": [
+                        "src/"
+                    ]
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Mike van Riel",
+                    "email": "me@mikevanriel.com"
+                }
+            ],
+            "time": "2016-11-25T06:54:22+00:00"
+        },
+        {
+            "name": "phpmd/phpmd",
+            "version": "2.6.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phpmd/phpmd.git",
+                "reference": "4e9924b2c157a3eb64395460fcf56b31badc8374"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phpmd/phpmd/zipball/4e9924b2c157a3eb64395460fcf56b31badc8374",
+                "reference": "4e9924b2c157a3eb64395460fcf56b31badc8374",
+                "shasum": ""
+            },
+            "require": {
+                "ext-xml": "*",
+                "pdepend/pdepend": "^2.5",
+                "php": ">=5.3.9"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.0",
+                "squizlabs/php_codesniffer": "^2.0"
+            },
+            "bin": [
+                "src/bin/phpmd"
+            ],
+            "type": "project",
+            "autoload": {
+                "psr-0": {
+                    "PHPMD\\": "src/main/php"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Manuel Pichler",
+                    "email": "github@manuel-pichler.de",
+                    "homepage": "https://github.com/manuelpichler",
+                    "role": "Project Founder"
+                },
+                {
+                    "name": "Other contributors",
+                    "homepage": "https://github.com/phpmd/phpmd/graphs/contributors",
+                    "role": "Contributors"
+                },
+                {
+                    "name": "Marc Würth",
+                    "email": "ravage@bluewin.ch",
+                    "homepage": "https://github.com/ravage84",
+                    "role": "Project Maintainer"
+                }
+            ],
+            "description": "PHPMD is a spin-off project of PHP Depend and aims to be a PHP equivalent of the well known Java tool PMD.",
+            "homepage": "http://phpmd.org/",
+            "keywords": [
+                "mess detection",
+                "mess detector",
+                "pdepend",
+                "phpmd",
+                "pmd"
+            ],
+            "time": "2017-01-20T14:41:10+00:00"
+        },
+        {
+            "name": "phpspec/prophecy",
+            "version": "v1.7.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phpspec/prophecy.git",
+                "reference": "93d39f1f7f9326d746203c7c056f300f7f126073"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/93d39f1f7f9326d746203c7c056f300f7f126073",
+                "reference": "93d39f1f7f9326d746203c7c056f300f7f126073",
+                "shasum": ""
+            },
+            "require": {
+                "doctrine/instantiator": "^1.0.2",
+                "php": "^5.3|^7.0",
+                "phpdocumentor/reflection-docblock": "^2.0|^3.0.2",
+                "sebastian/comparator": "^1.1|^2.0",
+                "sebastian/recursion-context": "^1.0|^2.0|^3.0"
+            },
+            "require-dev": {
+                "phpspec/phpspec": "^2.5|^3.2",
+                "phpunit/phpunit": "^4.8 || ^5.6.5"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.6.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": "https://github.com/phpspec/prophecy",
+            "keywords": [
+                "Double",
+                "Dummy",
+                "fake",
+                "mock",
+                "spy",
+                "stub"
+            ],
+            "time": "2017-03-02T20:05:34+00:00"
+        },
+        {
+            "name": "phpunit/php-code-coverage",
+            "version": "2.2.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+                "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979",
+                "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3",
+                "phpunit/php-file-iterator": "~1.3",
+                "phpunit/php-text-template": "~1.2",
+                "phpunit/php-token-stream": "~1.3",
+                "sebastian/environment": "^1.3.2",
+                "sebastian/version": "~1.0"
+            },
+            "require-dev": {
+                "ext-xdebug": ">=2.1.4",
+                "phpunit/phpunit": "~4"
+            },
+            "suggest": {
+                "ext-dom": "*",
+                "ext-xdebug": ">=2.2.1",
+                "ext-xmlwriter": "*"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.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": "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": "2015-10-06T15:47:00+00:00"
+        },
+        {
+            "name": "phpunit/php-file-iterator",
+            "version": "1.4.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+                "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3cc8f69b3028d0f96a9078e6295d86e9bf019be5",
+                "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5",
+                "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": "2016-10-03T07:40:28+00:00"
+        },
+        {
+            "name": "phpunit/php-text-template",
+            "version": "1.2.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-text-template.git",
+                "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
+                "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
+                "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": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Simple template engine.",
+            "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+            "keywords": [
+                "template"
+            ],
+            "time": "2015-06-21T13:50:34+00:00"
+        },
+        {
+            "name": "phpunit/php-timer",
+            "version": "1.0.9",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-timer.git",
+                "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
+                "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.3.3 || ^7.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
+            },
+            "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": "sb@sebastian-bergmann.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Utility class for timing",
+            "homepage": "https://github.com/sebastianbergmann/php-timer/",
+            "keywords": [
+                "timer"
+            ],
+            "time": "2017-02-26T11:10:40+00:00"
+        },
+        {
+            "name": "phpunit/php-token-stream",
+            "version": "1.4.11",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-token-stream.git",
+                "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e03f8f67534427a787e21a385a67ec3ca6978ea7",
+                "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7",
+                "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": "2017-02-27T10:12:30+00:00"
+        },
+        {
+            "name": "phpunit/phpcov",
+            "version": "2.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/phpcov.git",
+                "reference": "9ef291483ff65eefd8639584d61bbfb044d747f3"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpcov/zipball/9ef291483ff65eefd8639584d61bbfb044d747f3",
+                "reference": "9ef291483ff65eefd8639584d61bbfb044d747f3",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3",
+                "phpunit/php-code-coverage": "~2.0",
+                "phpunit/phpunit": ">=4.1",
+                "sebastian/diff": "~1.1",
+                "sebastian/finder-facade": "~1.1",
+                "sebastian/version": "~1.0",
+                "symfony/console": "~2.2"
+            },
+            "bin": [
+                "phpcov"
+            ],
+            "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",
+                    "role": "lead"
+                }
+            ],
+            "description": "CLI frontend for PHP_CodeCoverage",
+            "homepage": "https://github.com/sebastianbergmann/phpcov",
+            "time": "2015-10-05T09:24:23+00:00"
+        },
+        {
+            "name": "phpunit/phpunit",
+            "version": "4.8.35",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/phpunit.git",
+                "reference": "791b1a67c25af50e230f841ee7a9c6eba507dc87"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/791b1a67c25af50e230f841ee7a9c6eba507dc87",
+                "reference": "791b1a67c25af50e230f841ee7a9c6eba507dc87",
+                "shasum": ""
+            },
+            "require": {
+                "ext-dom": "*",
+                "ext-json": "*",
+                "ext-pcre": "*",
+                "ext-reflection": "*",
+                "ext-spl": "*",
+                "php": ">=5.3.3",
+                "phpspec/prophecy": "^1.3.1",
+                "phpunit/php-code-coverage": "~2.1",
+                "phpunit/php-file-iterator": "~1.4",
+                "phpunit/php-text-template": "~1.2",
+                "phpunit/php-timer": "^1.0.6",
+                "phpunit/phpunit-mock-objects": "~2.3",
+                "sebastian/comparator": "~1.2.2",
+                "sebastian/diff": "~1.2",
+                "sebastian/environment": "~1.3",
+                "sebastian/exporter": "~1.2",
+                "sebastian/global-state": "~1.0",
+                "sebastian/version": "~1.0",
+                "symfony/yaml": "~2.1|~3.0"
+            },
+            "suggest": {
+                "phpunit/php-invoker": "~1.1"
+            },
+            "bin": [
+                "phpunit"
+            ],
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.8.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": "2017-02-06T05:18:07+00:00"
+        },
+        {
+            "name": "phpunit/phpunit-mock-objects",
+            "version": "2.3.8",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
+                "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983",
+                "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983",
+                "shasum": ""
+            },
+            "require": {
+                "doctrine/instantiator": "^1.0.2",
+                "php": ">=5.3.3",
+                "phpunit/php-text-template": "~1.2",
+                "sebastian/exporter": "~1.2"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.4"
+            },
+            "suggest": {
+                "ext-soap": "*"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.3.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": "2015-10-02T06:51:40+00:00"
+        },
+        {
+            "name": "sebastian/comparator",
+            "version": "1.2.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/comparator.git",
+                "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be",
+                "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be",
+                "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": "2017-01-29T09:50:25+00:00"
+        },
+        {
+            "name": "sebastian/diff",
+            "version": "1.4.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/diff.git",
+                "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e",
+                "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.8"
+            },
+            "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": "Kore Nordmann",
+                    "email": "mail@kore-nordmann.de"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Diff implementation",
+            "homepage": "https://github.com/sebastianbergmann/diff",
+            "keywords": [
+                "diff"
+            ],
+            "time": "2015-12-08T07:14:41+00:00"
+        },
+        {
+            "name": "sebastian/environment",
+            "version": "1.3.8",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/environment.git",
+                "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/be2c607e43ce4c89ecd60e75c6a85c126e754aea",
+                "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.3.3 || ^7.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.8 || ^5.0"
+            },
+            "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-08-18T05:49:44+00:00"
+        },
+        {
+            "name": "sebastian/exporter",
+            "version": "1.2.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/exporter.git",
+                "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4",
+                "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3",
+                "sebastian/recursion-context": "~1.0"
+            },
+            "require-dev": {
+                "ext-mbstring": "*",
+                "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": "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-06-17T09:04:28+00:00"
+        },
+        {
+            "name": "sebastian/finder-facade",
+            "version": "1.2.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/finder-facade.git",
+                "reference": "2a6f7f57efc0aa2d23297d9fd9e2a03111a8c0b9"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/finder-facade/zipball/2a6f7f57efc0aa2d23297d9fd9e2a03111a8c0b9",
+                "reference": "2a6f7f57efc0aa2d23297d9fd9e2a03111a8c0b9",
+                "shasum": ""
+            },
+            "require": {
+                "symfony/finder": "~2.3|~3.0",
+                "theseer/fdomdocument": "~1.3"
+            },
+            "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": "FinderFacade is a convenience wrapper for Symfony's Finder component.",
+            "homepage": "https://github.com/sebastianbergmann/finder-facade",
+            "time": "2016-02-17T07:02:23+00:00"
+        },
+        {
+            "name": "sebastian/global-state",
+            "version": "1.1.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/global-state.git",
+                "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4",
+                "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4",
+                "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": "2015-10-12T03:26:01+00:00"
+        },
+        {
+            "name": "sebastian/phpcpd",
+            "version": "2.0.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/phpcpd.git",
+                "reference": "24d9a880deadb0b8c9680e9cfe78e30b704225db"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpcpd/zipball/24d9a880deadb0b8c9680e9cfe78e30b704225db",
+                "reference": "24d9a880deadb0b8c9680e9cfe78e30b704225db",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3",
+                "phpunit/php-timer": ">=1.0.6",
+                "sebastian/finder-facade": "~1.1",
+                "sebastian/version": "~1.0|~2.0",
+                "symfony/console": "~2.7|^3.0",
+                "theseer/fdomdocument": "~1.4"
+            },
+            "bin": [
+                "phpcpd"
+            ],
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0-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": "Copy/Paste Detector (CPD) for PHP code.",
+            "homepage": "https://github.com/sebastianbergmann/phpcpd",
+            "time": "2016-04-17T19:32:49+00:00"
+        },
+        {
+            "name": "sebastian/recursion-context",
+            "version": "1.0.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/recursion-context.git",
+                "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/b19cc3298482a335a95f3016d2f8a6950f0fbcd7",
+                "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.4"
+            },
+            "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": "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-10-03T07:41:43+00:00"
+        },
+        {
+            "name": "sebastian/version",
+            "version": "1.0.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/version.git",
+                "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
+                "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
+                "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": "2015-06-21T13:59:46+00:00"
+        },
+        {
+            "name": "squizlabs/php_codesniffer",
+            "version": "2.8.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
+                "reference": "d7cf0d894e8aa4c73712ee4a331cc1eaa37cdc7d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/d7cf0d894e8aa4c73712ee4a331cc1eaa37cdc7d",
+                "reference": "d7cf0d894e8aa4c73712ee4a331cc1eaa37cdc7d",
+                "shasum": ""
+            },
+            "require": {
+                "ext-simplexml": "*",
+                "ext-tokenizer": "*",
+                "ext-xmlwriter": "*",
+                "php": ">=5.1.2"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.0"
+            },
+            "bin": [
+                "scripts/phpcs",
+                "scripts/phpcbf"
+            ],
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "CodeSniffer.php",
+                    "CodeSniffer/CLI.php",
+                    "CodeSniffer/Exception.php",
+                    "CodeSniffer/File.php",
+                    "CodeSniffer/Fixer.php",
+                    "CodeSniffer/Report.php",
+                    "CodeSniffer/Reporting.php",
+                    "CodeSniffer/Sniff.php",
+                    "CodeSniffer/Tokens.php",
+                    "CodeSniffer/Reports/",
+                    "CodeSniffer/Tokenizers/",
+                    "CodeSniffer/DocGenerators/",
+                    "CodeSniffer/Standards/AbstractPatternSniff.php",
+                    "CodeSniffer/Standards/AbstractScopeSniff.php",
+                    "CodeSniffer/Standards/AbstractVariableSniff.php",
+                    "CodeSniffer/Standards/IncorrectPatternException.php",
+                    "CodeSniffer/Standards/Generic/Sniffs/",
+                    "CodeSniffer/Standards/MySource/Sniffs/",
+                    "CodeSniffer/Standards/PEAR/Sniffs/",
+                    "CodeSniffer/Standards/PSR1/Sniffs/",
+                    "CodeSniffer/Standards/PSR2/Sniffs/",
+                    "CodeSniffer/Standards/Squiz/Sniffs/",
+                    "CodeSniffer/Standards/Zend/Sniffs/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Greg Sherwood",
+                    "role": "lead"
+                }
+            ],
+            "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
+            "homepage": "http://www.squizlabs.com/php-codesniffer",
+            "keywords": [
+                "phpcs",
+                "standards"
+            ],
+            "time": "2017-03-01T22:17:45+00:00"
+        },
+        {
+            "name": "symfony/config",
+            "version": "v3.2.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/config.git",
+                "reference": "741d6d4cd1414d67d48eb71aba6072b46ba740c2"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/config/zipball/741d6d4cd1414d67d48eb71aba6072b46ba740c2",
+                "reference": "741d6d4cd1414d67d48eb71aba6072b46ba740c2",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.5.9",
+                "symfony/filesystem": "~2.8|~3.0"
+            },
+            "require-dev": {
+                "symfony/yaml": "~3.0"
+            },
+            "suggest": {
+                "symfony/yaml": "To use the yaml reference dumper"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.2-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Config\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony Config Component",
+            "homepage": "https://symfony.com",
+            "time": "2017-03-01T18:18:25+00:00"
+        },
+        {
+            "name": "symfony/console",
+            "version": "v2.8.18",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/console.git",
+                "reference": "81508e6fac4476771275a3f4f53c3fee9b956bfa"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/console/zipball/81508e6fac4476771275a3f4f53c3fee9b956bfa",
+                "reference": "81508e6fac4476771275a3f4f53c3fee9b956bfa",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.9",
+                "symfony/debug": "^2.7.2|~3.0.0",
+                "symfony/polyfill-mbstring": "~1.0"
+            },
+            "require-dev": {
+                "psr/log": "~1.0",
+                "symfony/event-dispatcher": "~2.1|~3.0.0",
+                "symfony/process": "~2.1|~3.0.0"
+            },
+            "suggest": {
+                "psr/log": "For using the console logger",
+                "symfony/event-dispatcher": "",
+                "symfony/process": ""
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.8-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Console\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony Console Component",
+            "homepage": "https://symfony.com",
+            "time": "2017-03-04T11:00:12+00:00"
+        },
+        {
+            "name": "symfony/debug",
+            "version": "v3.0.9",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/debug.git",
+                "reference": "697c527acd9ea1b2d3efac34d9806bf255278b0a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/debug/zipball/697c527acd9ea1b2d3efac34d9806bf255278b0a",
+                "reference": "697c527acd9ea1b2d3efac34d9806bf255278b0a",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.5.9",
+                "psr/log": "~1.0"
+            },
+            "conflict": {
+                "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2"
+            },
+            "require-dev": {
+                "symfony/class-loader": "~2.8|~3.0",
+                "symfony/http-kernel": "~2.8|~3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Debug\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony Debug Component",
+            "homepage": "https://symfony.com",
+            "time": "2016-07-30T07:22:48+00:00"
+        },
+        {
+            "name": "symfony/dependency-injection",
+            "version": "v3.2.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/dependency-injection.git",
+                "reference": "74e0935e414ad33d5e82074212c0eedb4681a691"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/74e0935e414ad33d5e82074212c0eedb4681a691",
+                "reference": "74e0935e414ad33d5e82074212c0eedb4681a691",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.5.9"
+            },
+            "conflict": {
+                "symfony/yaml": "<3.2"
+            },
+            "require-dev": {
+                "symfony/config": "~2.8|~3.0",
+                "symfony/expression-language": "~2.8|~3.0",
+                "symfony/yaml": "~3.2"
+            },
+            "suggest": {
+                "symfony/config": "",
+                "symfony/expression-language": "For using expressions in service container configuration",
+                "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them",
+                "symfony/yaml": ""
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.2-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\DependencyInjection\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony DependencyInjection Component",
+            "homepage": "https://symfony.com",
+            "time": "2017-03-05T00:06:55+00:00"
+        },
+        {
+            "name": "symfony/filesystem",
+            "version": "v3.2.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/filesystem.git",
+                "reference": "bc0f17bed914df2cceb989972c3b996043c4da4a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/filesystem/zipball/bc0f17bed914df2cceb989972c3b996043c4da4a",
+                "reference": "bc0f17bed914df2cceb989972c3b996043c4da4a",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.5.9"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.2-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Filesystem\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony Filesystem Component",
+            "homepage": "https://symfony.com",
+            "time": "2017-03-06T19:30:27+00:00"
+        },
+        {
+            "name": "symfony/finder",
+            "version": "v3.2.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/finder.git",
+                "reference": "92d7476d2df60cd851a3e13e078664b1deb8ce10"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/finder/zipball/92d7476d2df60cd851a3e13e078664b1deb8ce10",
+                "reference": "92d7476d2df60cd851a3e13e078664b1deb8ce10",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.5.9"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.2-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Finder\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony Finder Component",
+            "homepage": "https://symfony.com",
+            "time": "2017-02-21T09:12:04+00:00"
+        },
+        {
+            "name": "symfony/polyfill-mbstring",
+            "version": "v1.3.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-mbstring.git",
+                "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/e79d363049d1c2128f133a2667e4f4190904f7f4",
+                "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "suggest": {
+                "ext-mbstring": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.3-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Mbstring\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for the Mbstring extension",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "mbstring",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "time": "2016-11-14T01:06:16+00:00"
+        },
+        {
+            "name": "symfony/yaml",
+            "version": "v3.2.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/yaml.git",
+                "reference": "093e416ad096355149e265ea2e4cc1f9ee40ab1a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/yaml/zipball/093e416ad096355149e265ea2e4cc1f9ee40ab1a",
+                "reference": "093e416ad096355149e265ea2e4cc1f9ee40ab1a",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.5.9"
+            },
+            "require-dev": {
+                "symfony/console": "~2.8|~3.0"
+            },
+            "suggest": {
+                "symfony/console": "For validating YAML files using the lint command"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.2-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Yaml\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony Yaml Component",
+            "homepage": "https://symfony.com",
+            "time": "2017-03-07T16:47:02+00:00"
+        },
+        {
+            "name": "theseer/fdomdocument",
+            "version": "1.6.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/theseer/fDOMDocument.git",
+                "reference": "d9ad139d6c2e8edf5e313ffbe37ff13344cf0684"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/theseer/fDOMDocument/zipball/d9ad139d6c2e8edf5e313ffbe37ff13344cf0684",
+                "reference": "d9ad139d6c2e8edf5e313ffbe37ff13344cf0684",
+                "shasum": ""
+            },
+            "require": {
+                "ext-dom": "*",
+                "lib-libxml": "*",
+                "php": ">=5.3.3"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Arne Blankerts",
+                    "email": "arne@blankerts.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "The classes contained within this repository extend the standard DOM to use exceptions at all occasions of errors instead of PHP warnings or notices. They also add various custom methods and shortcuts for convenience and to simplify the usage of DOM.",
+            "homepage": "https://github.com/theseer/fDOMDocument",
+            "time": "2015-05-27T22:58:02+00:00"
+        },
+        {
+            "name": "webmozart/assert",
+            "version": "1.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/webmozart/assert.git",
+                "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/webmozart/assert/zipball/2db61e59ff05fe5126d152bd0655c9ea113e550f",
+                "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.3.3 || ^7.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.6",
+                "sebastian/version": "^1.0.1"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.3-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Webmozart\\Assert\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Bernhard Schussek",
+                    "email": "bschussek@gmail.com"
+                }
+            ],
+            "description": "Assertions to validate method input/output with nice error messages.",
+            "keywords": [
+                "assert",
+                "check",
+                "validate"
+            ],
+            "time": "2016-11-23T20:04:58+00:00"
+        }
+    ],
+    "aliases": [],
+    "minimum-stability": "stable",
+    "stability-flags": {
+        "pubsubhubbub/publisher": 20,
+        "phpmd/phpmd": 0
+    },
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": {
+        "php": ">=5.5"
+    },
+    "platform-dev": []
+}
index 7f357c69ed942a826c155b6fdfa19c80b87872ec..76aa1ae0efbc1c9d5945b34b29c23ae576b06e34 100644 (file)
--- a/index.php
+++ b/index.php
@@ -1,8 +1,6 @@
 <?php
 /**
- * Shaarli v0.8.3 - Shaare your links...
- *
- * The personal, minimalist, super-fast, database free, bookmarking service.
+ * Shaarli - The personal, minimalist, super-fast, database free, bookmarking service.
  *
  * Friendly fork by the Shaarli community:
  *  - https://github.com/shaarli/Shaarli
@@ -25,7 +23,6 @@ if (date_default_timezone_get() == '') {
 /*
  * PHP configuration
  */
-define('shaarli_version', '0.8.2');
 
 // http://server.com/x/shaarli --> /shaarli/
 define('WEB_PATH', substr($_SERVER['REQUEST_URI'], 0, 1+strrpos($_SERVER['REQUEST_URI'], '/', 0)));
@@ -91,6 +88,8 @@ try {
     exit;
 }
 
+define('shaarli_version', ApplicationUtils::getVersion(__DIR__ .'/'. ApplicationUtils::$VERSION_FILE));
+
 // Force cookie path (but do not change lifetime)
 $cookie = session_get_cookie_params();
 $cookiedir = '';
@@ -434,7 +433,7 @@ if (isset($_POST['login']))
         // Optional redirect after login:
         if (isset($_GET['post'])) {
             $uri = '?post='. urlencode($_GET['post']);
-            foreach (array('description', 'source', 'title') as $param) {
+            foreach (array('description', 'source', 'title', 'tags') as $param) {
                 if (!empty($_GET[$param])) {
                     $uri .= '&'.$param.'='.urlencode($_GET[$param]);
                 }
@@ -463,7 +462,7 @@ if (isset($_POST['login']))
         $redir = '&username='. $_POST['login'];
         if (isset($_GET['post'])) {
             $redir .= '&post=' . urlencode($_GET['post']);
-            foreach (array('description', 'source', 'title') as $param) {
+            foreach (array('description', 'source', 'title', 'tags') as $param) {
                 if (!empty($_GET[$param])) {
                     $redir .= '&' . $param . '=' . urlencode($_GET[$param]);
                 }
@@ -474,34 +473,6 @@ if (isset($_POST['login']))
     }
 }
 
-// ------------------------------------------------------------------------------------------
-// Misc utility functions:
-
-// Convert post_max_size/upload_max_filesize (e.g. '16M') parameters to bytes.
-function return_bytes($val)
-{
-    $val = trim($val); $last=strtolower($val[strlen($val)-1]);
-    switch($last)
-    {
-        case 'g': $val *= 1024;
-        case 'm': $val *= 1024;
-        case 'k': $val *= 1024;
-    }
-    return $val;
-}
-
-// Try to determine max file size for uploads (POST).
-// Returns an integer (in bytes)
-function getMaxFileSize()
-{
-    $size1 = return_bytes(ini_get('post_max_size'));
-    $size2 = return_bytes(ini_get('upload_max_filesize'));
-    // Return the smaller of two:
-    $maxsize = min($size1,$size2);
-    // FIXME: Then convert back to readable notations ? (e.g. 2M instead of 2000000)
-    return $maxsize;
-}
-
 // ------------------------------------------------------------------------------------------
 // Token management for XSRF protection
 // Token should be used in any form which acts on data (create,update,delete,import...).
@@ -697,9 +668,11 @@ function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager)
 
     $dayDate = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $day.'_000000');
     $data = array(
+        'pagetitle' => $conf->get('general.title') .' - '. format_date($dayDate, false),
         'linksToDisplay' => $linksToDisplay,
         'cols' => $columns,
         'day' => $dayDate->getTimestamp(),
+        'dayDate' => $dayDate,
         'previousday' => $previousday,
         'nextday' => $nextday,
     );
@@ -1052,7 +1025,13 @@ function renderPage($conf, $pluginManager, $LINKSDB)
         // Show login screen, then redirect to ?post=...
         if (isset($_GET['post']))
         {
-            header('Location: ?do=login&post='.urlencode($_GET['post']).(!empty($_GET['title'])?'&title='.urlencode($_GET['title']):'').(!empty($_GET['description'])?'&description='.urlencode($_GET['description']):'').(!empty($_GET['source'])?'&source='.urlencode($_GET['source']):'')); // Redirect to login page, then back to post link.
+            header( // Redirect to login page, then back to post link.
+                'Location: ?do=login&post='.urlencode($_GET['post']).
+                (!empty($_GET['title'])?'&title='.urlencode($_GET['title']):'').
+                (!empty($_GET['description'])?'&description='.urlencode($_GET['description']):'').
+                (!empty($_GET['tags'])?'&tags='.urlencode($_GET['tags']):'').
+                (!empty($_GET['source'])?'&source='.urlencode($_GET['source']):'')
+            );
             exit;
         }
 
@@ -1149,7 +1128,7 @@ function renderPage($conf, $pluginManager, $LINKSDB)
             $conf->set('feed.rss_permalinks', !empty($_POST['enableRssPermalinks']));
             $conf->set('updates.check_updates', !empty($_POST['updateCheck']));
             $conf->set('privacy.hide_public_links', !empty($_POST['hidePublicLinks']));
-            $conf->set('api.enabled', !empty($_POST['apiEnabled']));
+            $conf->set('api.enabled', !empty($_POST['enableApi']));
             $conf->set('api.secret', escape($_POST['apiSecret']));
             try {
                 $conf->write(isLoggedIn());
@@ -1175,9 +1154,12 @@ function renderPage($conf, $pluginManager, $LINKSDB)
             $PAGE->assign('theme', $conf->get('resource.theme'));
             $PAGE->assign('theme_available', ThemeUtils::getThemes($conf->get('resource.raintpl_tpl')));
             $PAGE->assign('redirector', $conf->get('redirector.url'));
-            list($timezone_form, $timezone_js) = generateTimeZoneForm($conf->get('general.timezone'));
-            $PAGE->assign('timezone_form', $timezone_form);
-            $PAGE->assign('timezone_js',$timezone_js);
+            list($continents, $cities) = generateTimeZoneData(
+                timezone_identifiers_list(),
+                $conf->get('general.timezone')
+            );
+            $PAGE->assign('continents', $continents);
+            $PAGE->assign('cities', $cities);
             $PAGE->assign('private_links_default', $conf->get('privacy.default_private_links', false));
             $PAGE->assign('session_protection_disabled', $conf->get('security.session_protection_disabled', false));
             $PAGE->assign('enable_rss_permalinks', $conf->get('feed.rss_permalinks', false));
@@ -1257,7 +1239,7 @@ function renderPage($conf, $pluginManager, $LINKSDB)
         }
 
         // lf_id should only be present if the link exists.
-        $id = !empty($_POST['lf_id']) ? intval(escape($_POST['lf_id'])) : $LINKSDB->getNextId();
+        $id = isset($_POST['lf_id']) ? intval(escape($_POST['lf_id'])) : $LINKSDB->getNextId();
         // Linkdate is kept here to:
         //   - use the same permalink for notes as they're displayed when creating them
         //   - let users hack creation date of their posts
@@ -1337,9 +1319,13 @@ function renderPage($conf, $pluginManager, $LINKSDB)
     // -------- User clicked the "Cancel" button when editing a link.
     if (isset($_POST['cancel_edit']))
     {
+        $id = isset($_POST['lf_id']) ? (int) escape($_POST['lf_id']) : false;
+        if (! isset($LINKSDB[$id])) {
+            header('Location: ?');
+        }
         // If we are called from the bookmarklet, we must close the popup:
         if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { echo '<script>self.close();</script>'; exit; }
-        $link = $LINKSDB[(int) escape($_POST['lf_id'])];
+        $link = $LINKSDB[$id];
         $returnurl = ( isset($_POST['returnurl']) ? $_POST['returnurl'] : '?' );
         // Scroll to the link which has been edited.
         $returnurl .= '#'. $link['shorturl'];
@@ -1525,7 +1511,22 @@ function renderPage($conf, $pluginManager, $LINKSDB)
 
         if (! isset($_POST['token']) || ! isset($_FILES['filetoupload'])) {
             // Show import dialog
-            $PAGE->assign('maxfilesize', getMaxFileSize());
+            $PAGE->assign(
+                'maxfilesize',
+                get_max_upload_size(
+                    ini_get('post_max_size'),
+                    ini_get('upload_max_filesize'),
+                    false
+                )
+            );
+            $PAGE->assign(
+                'maxfilesizeHuman',
+                get_max_upload_size(
+                    ini_get('post_max_size'),
+                    ini_get('upload_max_filesize'),
+                    true
+                )
+            );
             $PAGE->renderPage('import');
             exit;
         }
@@ -1535,7 +1536,7 @@ function renderPage($conf, $pluginManager, $LINKSDB)
             // The file is too big or some form field may be missing.
             echo '<script>alert("The file you are trying to upload is probably'
                 .' bigger than what this webserver can accept ('
-                .getMaxFileSize().' bytes).'
+                .get_max_upload_size(ini_get('post_max_size'), ini_get('upload_max_filesize')).').'
                 .' Please upload in smaller chunks.");document.location=\'?do='
                 .Router::$PAGE_IMPORT .'\';</script>';
             exit;
@@ -1992,16 +1993,10 @@ function install($conf)
         exit;
     }
 
-    // Display config form:
-    list($timezone_form, $timezone_js) = generateTimeZoneForm();
-    $timezone_html = '';
-    if ($timezone_form != '') {
-        $timezone_html = '<tr><td><b>Timezone:</b></td><td>'.$timezone_form.'</td></tr>';
-    }
-
     $PAGE = new PageBuilder($conf);
-    $PAGE->assign('timezone_html',$timezone_html);
-    $PAGE->assign('timezone_js',$timezone_js);
+    list($continents, $cities) = generateTimeZoneData(timezone_identifiers_list(), date_default_timezone_get());
+    $PAGE->assign('continents', $continents);
+    $PAGE->assign('cities', $cities);
     $PAGE->renderPage('install');
     exit;
 }
@@ -2252,9 +2247,10 @@ $app = new \Slim\App($container);
 
 // REST API routes
 $app->group('/api/v1', function() {
-    $this->get('/info', '\Shaarli\Api\Controllers\Info:getInfo');
-    $this->get('/links', '\Shaarli\Api\Controllers\Links:getLinks');
-    $this->get('/links/{id:[\d]+}', '\Shaarli\Api\Controllers\Links:getLink');
+    $this->get('/info', '\Shaarli\Api\Controllers\Info:getInfo')->setName('getInfo');
+    $this->get('/links', '\Shaarli\Api\Controllers\Links:getLinks')->setName('getLinks');
+    $this->get('/links/{id:[\d]+}', '\Shaarli\Api\Controllers\Links:getLink')->setName('getLink');
+    $this->post('/links', '\Shaarli\Api\Controllers\Links:postLink')->setName('postLink');
 })->add('\Shaarli\Api\ApiMiddleware');
 
 $response = $app->run(true);
diff --git a/plugins/readityourself/book-open.png b/plugins/readityourself/book-open.png
deleted file mode 100644 (file)
index 36513d7..0000000
Binary files a/plugins/readityourself/book-open.png and /dev/null differ
diff --git a/plugins/readityourself/readityourself.html b/plugins/readityourself/readityourself.html
deleted file mode 100644 (file)
index 5e20071..0000000
+++ /dev/null
@@ -1 +0,0 @@
-<span><a href="%s?url=%s"><img class="linklist-plugin-icon" src="%s/readityourself/book-open.png" title="Read with Readityourself" alt="readityourself" /></a></span>
diff --git a/plugins/readityourself/readityourself.meta b/plugins/readityourself/readityourself.meta
deleted file mode 100644 (file)
index bd611dd..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-description="For each link, add a ReadItYourself icon to save the shaared URL."
-parameters=READITYOUSELF_URL;
\ No newline at end of file
diff --git a/plugins/readityourself/readityourself.php b/plugins/readityourself/readityourself.php
deleted file mode 100644 (file)
index 961c5bd..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-<?php
-
-/**
- * Plugin readityourself
- */
-
-// If we're talking about https://github.com/memiks/readityourself
-// it seems kinda dead.
-// Not tested.
-
-/**
- * Init function, return an error if the server is not set.
- *
- * @param $conf ConfigManager instance.
- *
- * @return array Eventual error.
- */
-function readityourself_init($conf)
-{
-    $riyUrl = $conf->get('plugins.READITYOUSELF_URL');
-    if (empty($riyUrl)) {
-        $error = 'Readityourself plugin error: '.
-            'Please define the "READITYOUSELF_URL" setting in the plugin administration page.';
-        return array($error);
-    }
-}
-
-/**
- * Add readityourself icon to link_plugin when rendering linklist.
- *
- * @param mixed         $data Linklist data.
- * @param ConfigManager $conf Configuration Manager instance.
- *
- * @return mixed - linklist data with readityourself plugin.
- */
-function hook_readityourself_render_linklist($data, $conf)
-{
-    $riyUrl = $conf->get('plugins.READITYOUSELF_URL');
-    if (empty($riyUrl)) {
-        return $data;
-    }
-
-    $readityourself_html = file_get_contents(PluginManager::$PLUGINS_PATH . '/readityourself/readityourself.html');
-
-    foreach ($data['links'] as &$value) {
-        $readityourself = sprintf($readityourself_html, $riyUrl, $value['url'], PluginManager::$PLUGINS_PATH);
-        $value['link_plugin'][] = $readityourself;
-    }
-
-    return $data;
-}
index 3f79a4c608cf6ebdbb80baee552d86d423da0332..9167b43e1dd69981d28ebc514f89fe26cf981061 100644 (file)
@@ -1 +1 @@
-<?php /* 0.8.3 */ ?>
+<?php /* dev */ ?>
index ad86e21c78d8efe965ea02e940aaff8a7181b9db..ff4c9e17944be6de5fa9e53f9574eb1baac5c665 100644 (file)
@@ -17,7 +17,7 @@ class FakeApplicationUtils extends ApplicationUtils
     /**
      * Toggle HTTP requests, allow overriding the version code
      */
-    public static function getLatestGitVersionCode($url, $timeout=0)
+    public static function getVersion($url, $timeout=0)
     {
         return self::$VERSION_CODE;
     }
@@ -44,18 +44,28 @@ class ApplicationUtilsTest extends PHPUnit_Framework_TestCase
         }
     }
 
+    /**
+     * Remove test version file if it exists
+     */
+    public function tearDown()
+    {
+        if (is_file('sandbox/version.php')) {
+            unlink('sandbox/version.php');
+        }
+    }
+
     /**
      * Retrieve the latest version code available on Git
      *
      * Expected format: Semantic Versioning - major.minor.patch
      */
-    public function testGetLatestGitVersionCode()
+    public function testGetVersionCode()
     {
         $testTimeout = 10;
 
         $this->assertEquals(
             '0.5.4',
-            ApplicationUtils::getLatestGitVersionCode(
+            ApplicationUtils::getVersion(
                 'https://raw.githubusercontent.com/shaarli/Shaarli/'
                .'v0.5.4/shaarli_version.php',
                 $testTimeout
@@ -63,23 +73,35 @@ class ApplicationUtilsTest extends PHPUnit_Framework_TestCase
         );
         $this->assertRegExp(
             self::$versionPattern,
-            ApplicationUtils::getLatestGitVersionCode(
+            ApplicationUtils::getVersion(
                 'https://raw.githubusercontent.com/shaarli/Shaarli/'
-               .'master/shaarli_version.php',
+               .'latest/shaarli_version.php',
                 $testTimeout
             )
         );
     }
 
     /**
-     * Attempt to retrieve the latest version from an invalid URL
+     * Attempt to retrieve the latest version from an invalid File
+     */
+    public function testGetVersionCodeFromFile()
+    {
+        file_put_contents('sandbox/version.php', '<?php /* 1.2.3 */ ?>'. PHP_EOL);
+        $this->assertEquals(
+            '1.2.3',
+            ApplicationUtils::getVersion('sandbox/version.php', 1)
+        );
+    }
+
+    /**
+     * Attempt to retrieve the latest version from an invalid File
      */
-    public function testGetLatestGitVersionCodeInvalidUrl()
+    public function testGetVersionCodeInvalidFile()
     {
         $oldlog = ini_get('error_log');
         ini_set('error_log', '/dev/null');
         $this->assertFalse(
-            ApplicationUtils::getLatestGitVersionCode('htttp://null.io', 1)
+            ApplicationUtils::getVersion('idontexist', 1)
         );
         ini_set('error_log', $oldlog);
     }
@@ -332,4 +354,15 @@ class ApplicationUtilsTest extends PHPUnit_Framework_TestCase
             ApplicationUtils::checkResourcePermissions($conf)
         );
     }
+
+    /**
+     * Check update with 'dev' as curent version (master branch).
+     * It should always return false.
+     */
+    public function testCheckUpdateDev()
+    {
+        $this->assertFalse(
+            ApplicationUtils::checkUpdate('dev', self::$testUpdateFile, 100, true, true)
+        );
+    }
 }
index 2976d11612765dde427242073320c4d7f32bdb97..127fdc192ccab36ec57427ba270f444f9108b29f 100644 (file)
@@ -10,25 +10,46 @@ require_once 'application/TimeZone.php';
  */
 class TimeZoneTest extends PHPUnit_Framework_TestCase
 {
+    /**
+     * @var array of timezones
+     */
+    protected $installedTimezones;
+
+    public function setUp()
+    {
+        $this->installedTimezones = [
+            'Antarctica/Syowa',
+            'Europe/London',
+            'Europe/Paris',
+            'UTC'
+        ];
+    }
+
     /**
      * Generate a timezone selection form
      */
     public function testGenerateTimeZoneForm()
     {
-        $generated = generateTimeZoneForm();
+        $expected = [
+            'continents' => [
+                'Antarctica',
+                'Europe',
+                'UTC',
+                'selected' => '',
+            ],
+            'cities' => [
+                ['continent' => 'Antarctica', 'city' => 'Syowa'],
+                ['continent' => 'Europe',     'city' => 'London'],
+                ['continent' => 'Europe',     'city' => 'Paris'],
+                ['continent' => 'UTC',        'city' => 'UTC'],
+                'selected'    => '',
+            ]
+        ];
 
-        // HTML form
-        $this->assertStringStartsWith('Continent:<select', $generated[0]);
-        $this->assertContains('selected="selected"', $generated[0]);
-        $this->assertStringEndsWith('</select><br />', $generated[0]);
+        list($continents, $cities) = generateTimeZoneData($this->installedTimezones);
 
-        // Javascript handler
-        $this->assertStringStartsWith('<script>', $generated[1]);
-        $this->assertContains(
-            '<option value=\"Bermuda\">Bermuda<\/option>',
-            $generated[1]
-        );
-        $this->assertStringEndsWith('</script>', $generated[1]);
+        $this->assertEquals($expected['continents'], $continents);
+        $this->assertEquals($expected['cities'], $cities);
     }
 
     /**
@@ -36,28 +57,26 @@ class TimeZoneTest extends PHPUnit_Framework_TestCase
      */
     public function testGenerateTimeZoneFormPreselected()
     {
-        $generated = generateTimeZoneForm('Antarctica/Syowa');
-
-        // HTML form
-        $this->assertStringStartsWith('Continent:<select', $generated[0]);
-        $this->assertContains(
-            'value="Antarctica" selected="selected"',
-            $generated[0]
-        );
-        $this->assertContains(
-            'value="Syowa" selected="selected"',
-            $generated[0]
-        );
-        $this->assertStringEndsWith('</select><br />', $generated[0]);
+        $expected = [
+            'continents' => [
+                'Antarctica',
+                'Europe',
+                'UTC',
+                'selected' => 'Antarctica',
+            ],
+            'cities' => [
+                ['continent' => 'Antarctica', 'city' => 'Syowa'],
+                ['continent' => 'Europe',     'city' => 'London'],
+                ['continent' => 'Europe',     'city' => 'Paris'],
+                ['continent' => 'UTC',        'city' => 'UTC'],
+                'selected'   => 'Syowa',
+            ]
+        ];
 
+        list($continents, $cities) = generateTimeZoneData($this->installedTimezones, 'Antarctica/Syowa');
 
-        // Javascript handler
-        $this->assertStringStartsWith('<script>', $generated[1]);
-        $this->assertContains(
-            '<option value=\"Bermuda\">Bermuda<\/option>',
-            $generated[1]
-        );
-        $this->assertStringEndsWith('</script>', $generated[1]);
+        $this->assertEquals($expected['continents'], $continents);
+        $this->assertEquals($expected['cities'], $cities);
     }
 
     /**
index e70cc1aef10197d66718149a0ded03f85cbec072..d6a0aad5e8e0201da8441dcaa83eab6d7181575b 100644 (file)
@@ -4,6 +4,7 @@
  */
 
 require_once 'application/Utils.php';
+require_once 'application/Languages.php';
 require_once 'tests/utils/ReferenceSessionIdHashes.php';
 
 // Initialize reference data before PHPUnit starts a session
@@ -326,4 +327,94 @@ class UtilsTest extends PHPUnit_Framework_TestCase
         $this->assertFalse(format_date([]));
         $this->assertFalse(format_date(null));
     }
+
+    /**
+     * Test is_integer_mixed with valid values
+     */
+    public function testIsIntegerMixedValid()
+    {
+        $this->assertTrue(is_integer_mixed(12));
+        $this->assertTrue(is_integer_mixed('12'));
+        $this->assertTrue(is_integer_mixed(-12));
+        $this->assertTrue(is_integer_mixed('-12'));
+        $this->assertTrue(is_integer_mixed(0));
+        $this->assertTrue(is_integer_mixed('0'));
+        $this->assertTrue(is_integer_mixed(0x0a));
+    }
+
+    /**
+     * Test is_integer_mixed with invalid values
+     */
+    public function testIsIntegerMixedInvalid()
+    {
+        $this->assertFalse(is_integer_mixed(true));
+        $this->assertFalse(is_integer_mixed(false));
+        $this->assertFalse(is_integer_mixed([]));
+        $this->assertFalse(is_integer_mixed(['test']));
+        $this->assertFalse(is_integer_mixed([12]));
+        $this->assertFalse(is_integer_mixed(new DateTime()));
+        $this->assertFalse(is_integer_mixed('0x0a'));
+        $this->assertFalse(is_integer_mixed('12k'));
+        $this->assertFalse(is_integer_mixed('k12'));
+        $this->assertFalse(is_integer_mixed(''));
+    }
+
+    /**
+     * Test return_bytes
+     */
+    public function testReturnBytes()
+    {
+        $this->assertEquals(2 * 1024, return_bytes('2k'));
+        $this->assertEquals(2 * 1024, return_bytes('2K'));
+        $this->assertEquals(2 * (pow(1024, 2)), return_bytes('2m'));
+        $this->assertEquals(2 * (pow(1024, 2)), return_bytes('2M'));
+        $this->assertEquals(2 * (pow(1024, 3)), return_bytes('2g'));
+        $this->assertEquals(2 * (pow(1024, 3)), return_bytes('2G'));
+        $this->assertEquals(374, return_bytes('374'));
+        $this->assertEquals(374, return_bytes(374));
+        $this->assertEquals(0, return_bytes('0'));
+        $this->assertEquals(0, return_bytes(0));
+        $this->assertEquals(-1, return_bytes('-1'));
+        $this->assertEquals(-1, return_bytes(-1));
+        $this->assertEquals('', return_bytes(''));
+    }
+
+    /**
+     * Test human_bytes
+     */
+    public function testHumanBytes()
+    {
+        $this->assertEquals('2kiB', human_bytes(2 * 1024));
+        $this->assertEquals('2kiB', human_bytes(strval(2 * 1024)));
+        $this->assertEquals('2MiB', human_bytes(2 * (pow(1024, 2))));
+        $this->assertEquals('2MiB', human_bytes(strval(2 * (pow(1024, 2)))));
+        $this->assertEquals('2GiB', human_bytes(2 * (pow(1024, 3))));
+        $this->assertEquals('2GiB', human_bytes(strval(2 * (pow(1024, 3)))));
+        $this->assertEquals('374B', human_bytes(374));
+        $this->assertEquals('374B', human_bytes('374'));
+        $this->assertEquals('232kiB', human_bytes(237481));
+        $this->assertEquals('Unlimited', human_bytes('0'));
+        $this->assertEquals('Unlimited', human_bytes(0));
+        $this->assertEquals('Setting not set', human_bytes(''));
+    }
+
+    /**
+     * Test get_max_upload_size with formatting
+     */
+    public function testGetMaxUploadSize()
+    {
+        $this->assertEquals('1MiB', get_max_upload_size(2097152, '1024k'));
+        $this->assertEquals('1MiB', get_max_upload_size('1m', '2m'));
+        $this->assertEquals('100B', get_max_upload_size(100, 100));
+    }
+
+    /**
+     * Test get_max_upload_size without formatting
+     */
+    public function testGetMaxUploadSizeRaw()
+    {
+        $this->assertEquals('1048576', get_max_upload_size(2097152, '1024k', false));
+        $this->assertEquals('1048576', get_max_upload_size('1m', '2m', false));
+        $this->assertEquals('100', get_max_upload_size(100, 100, false));
+    }
 }
diff --git a/tests/api/controllers/PostLinkTest.php b/tests/api/controllers/PostLinkTest.php
new file mode 100644 (file)
index 0000000..3ed7bcb
--- /dev/null
@@ -0,0 +1,193 @@
+<?php
+
+namespace Shaarli\Api\Controllers;
+
+
+use Shaarli\Config\ConfigManager;
+use Slim\Container;
+use Slim\Http\Environment;
+use Slim\Http\Request;
+use Slim\Http\Response;
+
+/**
+ * Class PostLinkTest
+ *
+ * Test POST Link REST API service.
+ *
+ * @package Shaarli\Api\Controllers
+ */
+class PostLinkTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var string datastore to test write operations
+     */
+    protected static $testDatastore = 'sandbox/datastore.php';
+
+    /**
+     * @var ConfigManager instance
+     */
+    protected $conf;
+
+    /**
+     * @var \ReferenceLinkDB instance.
+     */
+    protected $refDB = null;
+
+    /**
+     * @var Container instance.
+     */
+    protected $container;
+
+    /**
+     * @var Links controller instance.
+     */
+    protected $controller;
+
+    /**
+     * Number of JSON field per link.
+     */
+    const NB_FIELDS_LINK = 9;
+
+    /**
+     * Before every test, instantiate a new Api with its config, plugins and links.
+     */
+    public function setUp()
+    {
+        $this->conf = new ConfigManager('tests/utils/config/configJson.json.php');
+        $this->refDB = new \ReferenceLinkDB();
+        $this->refDB->write(self::$testDatastore);
+
+        $this->container = new Container();
+        $this->container['conf'] = $this->conf;
+        $this->container['db'] = new \LinkDB(self::$testDatastore, true, false);
+
+        $this->controller = new Links($this->container);
+
+        $mock = $this->getMock('\Slim\Router', ['relativePathFor']);
+        $mock->expects($this->any())
+             ->method('relativePathFor')
+             ->willReturn('api/v1/links/1');
+
+        // affect @property-read... seems to work
+        $this->controller->getCi()->router = $mock;
+
+        // Used by index_url().
+        $this->controller->getCi()['environment'] = [
+            'SERVER_NAME' => 'domain.tld',
+            'SERVER_PORT' => 80,
+            'SCRIPT_NAME' => '/',
+        ];
+    }
+
+    /**
+     * After every test, remove the test datastore.
+     */
+    public function tearDown()
+    {
+        @unlink(self::$testDatastore);
+    }
+
+    /**
+     * Test link creation without any field: creates a blank note.
+     */
+    public function testPostLinkMinimal()
+    {
+        $env = Environment::mock([
+            'REQUEST_METHOD' => 'POST',
+        ]);
+
+        $request = Request::createFromEnvironment($env);
+
+        $response = $this->controller->postLink($request, new Response());
+        $this->assertEquals(201, $response->getStatusCode());
+        $this->assertEquals('api/v1/links/1', $response->getHeader('Location')[0]);
+        $data = json_decode((string) $response->getBody(), true);
+        $this->assertEquals(self::NB_FIELDS_LINK, count($data));
+        $this->assertEquals(43, $data['id']);
+        $this->assertRegExp('/[\w-_]{6}/', $data['shorturl']);
+        $this->assertEquals('http://domain.tld/?' . $data['shorturl'], $data['url']);
+        $this->assertEquals('?' . $data['shorturl'], $data['title']);
+        $this->assertEquals('', $data['description']);
+        $this->assertEquals([], $data['tags']);
+        $this->assertEquals(false, $data['private']);
+        $this->assertTrue(new \DateTime('5 seconds ago') < \DateTime::createFromFormat(\DateTime::ATOM, $data['created']));
+        $this->assertEquals('', $data['updated']);
+    }
+
+    /**
+     * Test link creation with all available fields.
+     */
+    public function testPostLinkFull()
+    {
+        $link = [
+            'url' => 'website.tld/test?foo=bar',
+            'title' => 'new entry',
+            'description' => 'shaare description',
+            'tags' => ['one', 'two'],
+            'private' => true,
+        ];
+        $env = Environment::mock([
+            'REQUEST_METHOD' => 'POST',
+            'CONTENT_TYPE' => 'application/json'
+        ]);
+
+        $request = Request::createFromEnvironment($env);
+        $request = $request->withParsedBody($link);
+        $response = $this->controller->postLink($request, new Response());
+
+        $this->assertEquals(201, $response->getStatusCode());
+        $this->assertEquals('api/v1/links/1', $response->getHeader('Location')[0]);
+        $data = json_decode((string) $response->getBody(), true);
+        $this->assertEquals(self::NB_FIELDS_LINK, count($data));
+        $this->assertEquals(43, $data['id']);
+        $this->assertRegExp('/[\w-_]{6}/', $data['shorturl']);
+        $this->assertEquals('http://' . $link['url'], $data['url']);
+        $this->assertEquals($link['title'], $data['title']);
+        $this->assertEquals($link['description'], $data['description']);
+        $this->assertEquals($link['tags'], $data['tags']);
+        $this->assertEquals(true, $data['private']);
+        $this->assertTrue(new \DateTime('2 seconds ago') < \DateTime::createFromFormat(\DateTime::ATOM, $data['created']));
+        $this->assertEquals('', $data['updated']);
+    }
+
+    /**
+     * Test link creation with an existing link (duplicate URL). Should return a 409 HTTP error and the existing link.
+     */
+    public function testPostLinkDuplicate()
+    {
+        $link = [
+            'url' => 'mediagoblin.org/',
+            'title' => 'new entry',
+            'description' => 'shaare description',
+            'tags' => ['one', 'two'],
+            'private' => true,
+        ];
+        $env = Environment::mock([
+            'REQUEST_METHOD' => 'POST',
+            'CONTENT_TYPE' => 'application/json'
+        ]);
+
+        $request = Request::createFromEnvironment($env);
+        $request = $request->withParsedBody($link);
+        $response = $this->controller->postLink($request, new Response());
+
+        $this->assertEquals(409, $response->getStatusCode());
+        $data = json_decode((string) $response->getBody(), true);
+        $this->assertEquals(self::NB_FIELDS_LINK, count($data));
+        $this->assertEquals(7, $data['id']);
+        $this->assertEquals('IuWvgA', $data['shorturl']);
+        $this->assertEquals('http://mediagoblin.org/', $data['url']);
+        $this->assertEquals('MediaGoblin', $data['title']);
+        $this->assertEquals('A free software media publishing platform #hashtagOther', $data['description']);
+        $this->assertEquals(['gnu', 'media', 'web', '.hidden', 'hashtag'], $data['tags']);
+        $this->assertEquals(false, $data['private']);
+        $this->assertEquals(
+            \DateTime::createFromFormat(\LinkDB::LINK_DATE_FORMAT, '20130614_184135'),
+            \DateTime::createFromFormat(\DateTime::ATOM, $data['created'])
+        );
+        $this->assertEquals(
+            \DateTime::createFromFormat(\LinkDB::LINK_DATE_FORMAT, '20130615_184230'),
+            \DateTime::createFromFormat(\DateTime::ATOM, $data['updated'])
+        );
+    }
+}
index 545fa572e46dbdeaf40db50a0da942a5f49169dd..6c9c9adce8cb6b02653bf32b4660f5f69c04edd5 100644 (file)
@@ -11,7 +11,16 @@ class UtilsDeTest extends UtilsTest
     public function testDateFormat()
     {
         $date = DateTime::createFromFormat('Ymd_His', '20170101_101112');
-        $this->assertRegExp('/1. Januar 2017 (um )?10:11:12 GMT\+0?3(:00)?/', format_date($date, true));
+        $this->assertRegExp('/1\. Januar 2017 (um )?10:11:12 GMT\+0?3(:00)?/', format_date($date, true, true));
+    }
+
+    /**
+     * Test date_format() without time.
+     */
+    public function testDateFormatNoTime()
+    {
+        $date = DateTime::createFromFormat('Ymd_His', '20170101_101112');
+        $this->assertRegExp('/1\. Januar 2017/', format_date($date, false,true));
     }
 
     /**
@@ -20,7 +29,16 @@ class UtilsDeTest extends UtilsTest
     public function testDateFormatDefault()
     {
         $date = DateTime::createFromFormat('Ymd_His', '20170101_101112');
-        $this->assertEquals('So 01 Jan 2017 10:11:12 EAT', format_date($date, false));
+        $this->assertEquals('So 01 Jan 2017 10:11:12 EAT', format_date($date, true, false));
+    }
+
+    /**
+     * Test date_format() using builtin PHP function strftime without time.
+     */
+    public function testDateFormatDefaultNoTime()
+    {
+        $date = DateTime::createFromFormat('Ymd_His', '20170201_101112');
+        $this->assertEquals('01.02.2017', format_date($date, false, false));
     }
 
     /**
index 7c829ac7e5856cd003e979949572d7c1551397da..d8680b2b429c1ae6ab4b1f0f888e61c5d3faf4a4 100644 (file)
@@ -11,7 +11,16 @@ class UtilsEnTest extends UtilsTest
     public function testDateFormat()
     {
         $date = DateTime::createFromFormat('Ymd_His', '20170101_101112');
-        $this->assertRegExp('/January 1, 2017 (at )?10:11:12 AM GMT\+0?3(:00)?/', format_date($date, true));
+        $this->assertRegExp('/January 1, 2017 (at )?10:11:12 AM GMT\+0?3(:00)?/', format_date($date, true, true));
+    }
+
+    /**
+     * Test date_format() without time.
+     */
+    public function testDateFormatNoTime()
+    {
+        $date = DateTime::createFromFormat('Ymd_His', '20170101_101112');
+        $this->assertRegExp('/January 1, 2017/', format_date($date, false, true));
     }
 
     /**
@@ -20,7 +29,16 @@ class UtilsEnTest extends UtilsTest
     public function testDateFormatDefault()
     {
         $date = DateTime::createFromFormat('Ymd_His', '20170101_101112');
-        $this->assertEquals('Sun 01 Jan 2017 10:11:12 AM EAT', format_date($date, false));
+        $this->assertEquals('Sun 01 Jan 2017 10:11:12 AM EAT', format_date($date, true, false));
+    }
+
+    /**
+     * Test date_format() using builtin PHP function strftime without time.
+     */
+    public function testDateFormatDefaultNoTime()
+    {
+        $date = DateTime::createFromFormat('Ymd_His', '20170201_101112');
+        $this->assertEquals('02/01/2017', format_date($date, false, false));
     }
 
     /**
index 45996ee27318c37c6e0e3db2ef76d58ca990a636..0d50a87829f241591b0868446831c2918dcd4618 100644 (file)
@@ -14,13 +14,31 @@ class UtilsFrTest extends UtilsTest
         $this->assertRegExp('/1 janvier 2017 (à )?10:11:12 UTC\+0?3(:00)?/', format_date($date));
     }
 
+    /**
+     * Test date_format() without time.
+     */
+    public function testDateFormatNoTime()
+    {
+        $date = DateTime::createFromFormat('Ymd_His', '20170101_101112');
+        $this->assertRegExp('/1 janvier 2017/', format_date($date, false, true));
+    }
+
     /**
      * Test date_format() using builtin PHP function strftime.
      */
     public function testDateFormatDefault()
     {
         $date = DateTime::createFromFormat('Ymd_His', '20170101_101112');
-        $this->assertEquals('dim. 01 janv. 2017 10:11:12 EAT', format_date($date, false));
+        $this->assertEquals('dim. 01 janv. 2017 10:11:12 EAT', format_date($date, true, false));
+    }
+
+    /**
+     * Test date_format() using builtin PHP function strftime without time.
+     */
+    public function testDateFormatDefaultNoTime()
+    {
+        $date = DateTime::createFromFormat('Ymd_His', '20170201_101112');
+        $this->assertEquals('01/02/2017', format_date($date, false, false));
     }
 
     /**
diff --git a/tests/plugins/PluginReadityourselfTest.php b/tests/plugins/PluginReadityourselfTest.php
deleted file mode 100644 (file)
index bbba967..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-<?php
-use Shaarli\Config\ConfigManager;
-
-/**
- * PluginReadityourselfTest.php.php
- */
-
-require_once 'plugins/readityourself/readityourself.php';
-
-/**
- * Class PluginWallabagTest
- * Unit test for the Wallabag plugin
- */
-class PluginReadityourselfTest extends PHPUnit_Framework_TestCase
-{
-    /**
-     * Reset plugin path
-     */
-    public function setUp()
-    {
-        PluginManager::$PLUGINS_PATH = 'plugins';
-    }
-
-    /**
-     * Test Readityourself init without errors.
-     */
-    public function testReadityourselfInitNoError()
-    {
-        $conf = new ConfigManager('');
-        $conf->set('plugins.READITYOUSELF_URL', 'value');
-        $errors = readityourself_init($conf);
-        $this->assertEmpty($errors);
-    }
-
-    /**
-     * Test Readityourself init with errors.
-     */
-    public function testReadityourselfInitError()
-    {
-        $conf = new ConfigManager('');
-        $errors = readityourself_init($conf);
-        $this->assertNotEmpty($errors);
-    }
-
-    /**
-     * Test render_linklist hook.
-     */
-    public function testReadityourselfLinklist()
-    {
-        $conf = new ConfigManager('');
-        $conf->set('plugins.READITYOUSELF_URL', 'value');
-        $str = 'http://randomstr.com/test';
-        $data = array(
-            'title' => $str,
-            'links' => array(
-                array(
-                    'url' => $str,
-                )
-            )
-        );
-
-        $data = hook_readityourself_render_linklist($data, $conf);
-        $link = $data['links'][0];
-        // data shouldn't be altered
-        $this->assertEquals($str, $data['title']);
-        $this->assertEquals($str, $link['url']);
-
-        // plugin data
-        $this->assertEquals(1, count($link['link_plugin']));
-        $this->assertNotFalse(strpos($link['link_plugin'][0], $str));
-    }
-
-    /**
-     * Test without config: nothing should happened.
-     */
-    public function testReadityourselfLinklistWithoutConfig()
-    {
-        $conf = new ConfigManager('');
-        $conf->set('plugins.READITYOUSELF_URL', null);
-        $str = 'http://randomstr.com/test';
-        $data = array(
-            'title' => $str,
-            'links' => array(
-                array(
-                    'url' => $str,
-                )
-            )
-        );
-
-        $data = hook_readityourself_render_linklist($data, $conf);
-        $link = $data['links'][0];
-        // data shouldn't be altered
-        $this->assertEquals($str, $data['title']);
-        $this->assertEquals($str, $link['url']);
-
-        // plugin data
-        $this->assertArrayNotHasKey('link_plugin', $link);
-    }
-}
index 36d58c683e449eb11ac2388c3c4fe43bda2bd1b6..1f4b306372e9a8dd4959d9e3636c39220ea01202 100644 (file)
@@ -56,7 +56,7 @@ class ReferenceLinkDB
             0,
             DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20130614_184135'),
             'gnu media web .hidden hashtag',
-            null,
+            DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20130615_184230'),
             'IuWvgA'
         );
 
index d6536d47509a5228bda5e1acbc70a32069735f35..7469ab597a69ab4cb468a43e0b8fb6e7d6a0cb98 100644 (file)
         <div class="pure-u-lg-{$ratioLabel} pure-u-1 ">
           <div class="form-label">
             <label>
-              <span class="label-name">{'Timezone'|t}</span>
+              <span class="label-name">{'Timezone'|t}</span><br>
+              <span class="label-desc">{'Continent'|t} &middot; {'City'|t}</span>
             </label>
           </div>
         </div>
         <div class="pure-u-lg-{$ratioInput} pure-u-1 ">
           <div class="form-input">
-            {ignore}FIXME! too hackish, needs to be fixed upstream{/ignore}
-            <div class="timezone" id="timezone-remove">{$timezone_form}</div>
-            <div class="timezone" id="timezone-add"></div>
+            <div class="timezone">
+              <select id="continent" name="continent">
+                {loop="$continents"}
+                  {if="$key !== 'selected'"}
+                    <option value="{$value}" {if="$continents.selected === $value"}selected{/if}>
+                      {$value}
+                    </option>
+                  {/if}
+                {/loop}
+              </select>
+              <select id="city" name="city">
+                {loop="$cities"}
+                  {if="$key !== 'selected'"}
+                    <option value="{$value.city}"
+                            {if="$cities.selected === $value.city"}selected{/if}
+                            data-continent="{$value.continent}">
+                      {$value.city}
+                    </option>
+                  {/if}
+                {/loop}
+              </select>
+            </div>
           </div>
         </div>
       </div>
       <div class="pure-g">
         <div class="pure-u-lg-{$ratioLabel} pure-u-{$ratioLabelMobile}">
           <div class="form-label">
-            <label for="apiEnabled">
+            <label for="enableApi">
               <span class="label-name">{'Enable REST API'|t}</span><br>
               <span class="label-desc">{'Allow third party software to use Shaarli such as mobile application'|t}</span>
             </label>
         </div>
         <div class="pure-u-lg-{$ratioInput} pure-u-{$ratioInputMobile}">
           <div class="form-input">
-            <input type="checkbox" name="apiEnabled" id="apiEnabled"
+            <input type="checkbox" name="enableApi" id="enableApi"
                  {if="$api_enabled"}checked{/if}/>
           </div>
         </div>
index 8fcd13afdad80956789f8842e329ef1d3ac1233e..73fade5ffa3a4a6570ecb296baa7e51391f0f3df 100644 (file)
@@ -35,14 +35,29 @@ pre {
 }
 
 @font-face {
-    font-family: 'Roboto Slab';
+    font-family: 'Roboto';
     font-weight: 400;
     font-style: normal;
     src:
-    local('Fira Sans'),
-    local('Fira-Sans-regular'),
-    url('../fonts/Fira-Sans-regular.woff2') format('woff2'),
-    url('../fonts/Fira-Sans-regular.woff') format('woff');
+    local('Roboto'),
+    local('Roboto-Regular'),
+    url('../fonts/Roboto-Regular.woff2') format('woff2'),
+    url('../fonts/Roboto-Regular.woff') format('woff');
+}
+
+@font-face {
+    font-family: 'Roboto';
+    font-weight: 700;
+    font-style: normal;
+    src:
+    local('Roboto'),
+    local('Roboto-Bold'),
+    url('../fonts/Roboto-Bold.woff2') format('woff2'),
+    url('../fonts/Roboto-Bold.woff') format('woff');
+}
+
+body, .pure-g [class*="pure-u"] {
+    font-family: Roboto, Arial, sans-serif;
 }
 
 /**
@@ -68,10 +83,6 @@ pre {
     .pure-u-xl-visible { display: inline-block !important; }
 }
 
-.pure-g [class*="pure-u"]{
-    font-family: Roboto Slab, Arial, sans-serif;
-}
-
 /**
  * Make pure-extras alert closable.
  */
@@ -504,7 +515,6 @@ pre {
     color: #252525;
     text-decoration: none;
     vertical-align: middle;
-    font-family: Roboto Slab, Arial, sans-serif;
 }
 
 .linklist-item-title .linklist-link {
@@ -560,7 +570,6 @@ pre {
 .linklist-item-description {
     position: relative;
     padding: 10px;
-    font-family: Roboto Slab, Arial, sans-serif;
     word-wrap: break-word;
     color: #252525;
     line-height: 1.3em;
index d8c91078ad533dbda81d769c1f543292ae1b68e5..29d845d5307231d1337e3964837d8931a89736e0 100644 (file)
@@ -44,7 +44,7 @@
         </div>
       </div>
       <div>
-        <h3 class="window-subtitle">{function="strftime('%A %d, %B %Y', $day)"}</h3>
+        <h3 class="window-subtitle">{function="format_date($dayDate, false)"}</h3>
 
         <div id="plugin_zone_about_daily" class="plugin_zone">
           {loop="$daily_about_plugin"}
diff --git a/tpl/default/fonts/Fira-Sans-regular.woff b/tpl/default/fonts/Fira-Sans-regular.woff
deleted file mode 100644 (file)
index 014ac31..0000000
Binary files a/tpl/default/fonts/Fira-Sans-regular.woff and /dev/null differ
diff --git a/tpl/default/fonts/Fira-Sans-regular.woff2 b/tpl/default/fonts/Fira-Sans-regular.woff2
deleted file mode 100644 (file)
index bf3ad9a..0000000
Binary files a/tpl/default/fonts/Fira-Sans-regular.woff2 and /dev/null differ
diff --git a/tpl/default/fonts/Roboto-Bold.woff b/tpl/default/fonts/Roboto-Bold.woff
new file mode 100644 (file)
index 0000000..3d86753
Binary files /dev/null and b/tpl/default/fonts/Roboto-Bold.woff differ
diff --git a/tpl/default/fonts/Roboto-Bold.woff2 b/tpl/default/fonts/Roboto-Bold.woff2
new file mode 100644 (file)
index 0000000..bd05e2e
Binary files /dev/null and b/tpl/default/fonts/Roboto-Bold.woff2 differ
diff --git a/tpl/default/fonts/Roboto-Regular.woff b/tpl/default/fonts/Roboto-Regular.woff
new file mode 100644 (file)
index 0000000..464d206
Binary files /dev/null and b/tpl/default/fonts/Roboto-Regular.woff differ
diff --git a/tpl/default/fonts/Roboto-Regular.woff2 b/tpl/default/fonts/Roboto-Regular.woff2
new file mode 100644 (file)
index 0000000..f966196
Binary files /dev/null and b/tpl/default/fonts/Roboto-Regular.woff2 differ
index e6e521e8fc3d0280e2ec04f622c88a5792271968..1f040685caa5195f0a2e9a41ec199bf720bee178 100644 (file)
@@ -18,6 +18,7 @@
       <div class="center" id="import-field">
         <input type="hidden" name="MAX_FILE_SIZE" value="{$maxfilesize}">
         <input type="file" name="filetoupload">
+        <p><br>Maximum size allowed: <strong>{$maxfilesizeHuman}</strong></p>
       </div>
 
       <div class="pure-g">
index 33f8a45336c4a448505001dbce93d55f88261f34..164d453b1d6c162bfa6b5a28b9203dbdfdb10d3e 100644 (file)
@@ -7,6 +7,8 @@
 
 {$ratioLabel='1-4'}
 {$ratioInput='3-4'}
+{$ratioLabelMobile='7-8'}
+{$ratioInputMobile='1-8'}
 
 <form method="POST" action="#" name="installform" id="installform">
 <div class="pure-g">
       </div>
       <div class="pure-u-lg-{$ratioInput} pure-u-1">
         <div class="form-input">
-          <input type="text" name="setpassword" id="password">
+          <input type="password" name="setpassword" id="password">
         </div>
       </div>
     </div>
 
     <div class="pure-g">
-      <div class="pure-u-lg-{$ratioLabel} pure-u-1 ">
+      <div class="pure-u-lg-{$ratioLabel} pure-u-1">
         <div class="form-label">
-          <label>
-            <span class="label-name">{'Timezone'|t}</span>
+          <label for="title">
+            <span class="label-name">{'Shaarli title'|t}</span>
           </label>
         </div>
       </div>
-      <div class="pure-u-lg-{$ratioInput} pure-u-1 ">
+      <div class="pure-u-lg-{$ratioInput} pure-u-1">
         <div class="form-input">
-          {ignore}FIXME! too hackish, needs to be fixed upstream{/ignore}
-          <div class="timezone" id="timezone-remove">{$timezone_html}</div>
-          <div class="timezone" id="timezone-add"></div>
+          <input type="text" name="title" id="title" placeholder="{'My links'|t}">
         </div>
       </div>
     </div>
     <div class="pure-g">
       <div class="pure-u-lg-{$ratioLabel} pure-u-1">
         <div class="form-label">
-          <label for="title">
-            <span class="label-name">{'Shaarli title'|t}</span>
+          <label>
+            <span class="label-name">{'Timezone'|t}</span><br>
+            <span class="label-desc">{'Continent'|t} &middot; {'City'|t}</span>
           </label>
         </div>
       </div>
       <div class="pure-u-lg-{$ratioInput} pure-u-1">
         <div class="form-input">
-          <input type="text" name="title" id="title" placeholder="{'My links'|t}">
+          <div class="timezone">
+            <select id="continent" name="continent">
+              {loop="$continents"}
+                {if="$key !== 'selected'"}
+                  <option value="{$value}" {if="$continents.selected === $value"}selected{/if}>
+                    {$value}
+                  </option>
+                {/if}
+              {/loop}
+            </select>
+            <select id="city" name="city">
+              {loop="$cities"}
+                {if="$key !== 'selected'"}
+                  <option value="{$value.city}"
+                          {if="$cities.selected === $value.city"}selected{/if}
+                          data-continent="{$value.continent}">
+                    {$value.city}
+                  </option>
+                {/if}
+              {/loop}
+            </select>
+          </div>
         </div>
       </div>
     </div>
       </div>
     </div>
 
+    <div class="pure-g">
+      <div class="pure-u-lg-{$ratioLabel} pure-u-{$ratioLabelMobile}">
+        <div class="form-label">
+          <label for="enableApi">
+            <span class="label-name">{'Enable REST API'|t}</span><br>
+            <span class="label-desc">{'Allow third party software to use Shaarli such as mobile application'|t}</span>
+          </label>
+        </div>
+      </div>
+      <div class="pure-u-lg-{$ratioInput} pure-u-{$ratioInputMobile}">
+        <div class="form-input">
+          <input type="checkbox" name="enableApi" id="enableApi" checked />
+        </div>
+      </div>
+    </div>
+
     <div class="center">
       <input type="submit" value="{'Install'|t}" name="Save">
     </div>
index 30d8ed6ff73482f9502e03268e321c3643aa0726..4d47fcd0c2cd4aaf3be8080e84c61782adabd247 100644 (file)
@@ -76,9 +76,12 @@ window.onload = function () {
             }
         }
 
-        document.getElementById('menu-toggle').addEventListener('click', function (e) {
-            toggleMenu();
-        });
+        var menuToggle = document.getElementById('menu-toggle');
+        if (menuToggle != null) {
+            menuToggle.addEventListener('click', function (e) {
+                toggleMenu();
+            });
+        }
 
         window.addEventListener(WINDOW_CHANGE_EVENT, closeMenu);
     })(this, this.document);
@@ -255,10 +258,9 @@ window.onload = function () {
      * Remove CSS target padding (for fixed bar)
      */
     if (location.hash != '') {
-        var anchor = document.querySelector(location.hash);
+        var anchor = document.getElementById(location.hash.substr(1));
         if (anchor != null) {
             var padsize = anchor.clientHeight;
-            console.log(document.querySelector(location.hash).clientHeight);
             this.window.scroll(0, this.window.scrollY - padsize);
             anchor.style.paddingTop = 0;
         }
@@ -299,21 +301,6 @@ window.onload = function () {
         });
     }
 
-    /**
-     * TimeZome select
-     * FIXME! way too hackish
-     */
-    var toRemove = document.getElementById('timezone-remove');
-    if (toRemove != null) {
-        var firstSelect = toRemove.getElementsByTagName('select')[0];
-        var secondSelect = toRemove.getElementsByTagName('select')[1];
-        toRemove.parentNode.removeChild(toRemove);
-        var toAdd = document.getElementById('timezone-add');
-        var newTimezone = '<span class="timezone-continent">Continent ' + firstSelect.outerHTML + '</span>';
-        newTimezone += ' <span class="timezone-country">Country ' + secondSelect.outerHTML + '</span>';
-        toAdd.innerHTML = newTimezone;
-    }
-
     /**
      * Awesomplete trigger.
      */
@@ -366,6 +353,15 @@ window.onload = function () {
             }
         });
     });
+
+    var continent = document.getElementById('continent');
+    var city = document.getElementById('city');
+    if (continent != null && city != null) {
+        continent.addEventListener('change', function(event) {
+            hideTimezoneCities(city, continent.options[continent.selectedIndex].value, true);
+        });
+        hideTimezoneCities(city, continent.options[continent.selectedIndex].value, false);
+    }
 };
 
 function activateFirefoxSocial(node) {
@@ -391,3 +387,25 @@ function activateFirefoxSocial(node) {
     var activate = new CustomEvent("ActivateSocialFeature");
     node.dispatchEvent(activate);
 }
+
+/**
+ * Add the class 'hidden' to city options not attached to the current selected continent.
+ *
+ * @param cities           List of <option> elements
+ * @param currentContinent Current selected continent
+ * @param reset            Set to true to reset the selected value
+ */
+function hideTimezoneCities(cities, currentContinent, reset = false) {
+    var first = true;
+    [].forEach.call(cities, function(option) {
+        if (option.getAttribute('data-continent') != currentContinent) {
+            option.className = 'hidden';
+        } else {
+            option.className = '';
+            if (reset === true && first === true) {
+                option.setAttribute('selected', 'selected');
+                first = false;
+            }
+        }
+    });
+}
index 5820e6e4022d902f7284205405ab1e0495687b1a..7adc7545532cc7d92a0494c25aea111f79292e7c 100644 (file)
@@ -4,7 +4,6 @@
 <body onload="document.configform.title.focus();">
 <div id="pageheader">
   {include="page.header"}
-  {$timezone_js}
   <form method="POST" action="#" name="configform" id="configform">
     <input type="hidden" name="token" value="{$token}">
     <table id="configuration_table">
 
       <tr>
         <td><b>Timezone:</b></td>
-        <td>{$timezone_form}</td>
+        <td>
+          <select id="continent" name="continent">
+            {loop="$continents"}
+              {if="$key !== 'selected'"}
+                <option value="{$value}" {if="$continents.selected === $value"}selected{/if}>
+                  {$value}
+                </option>
+              {/if}
+            {/loop}
+          </select>
+          <select id="city" name="city">
+            {loop="$cities"}
+              {if="$key !== 'selected'"}
+                <option value="{$value.city}"
+                        {if="$cities.selected === $value.city"}selected{/if}
+                        data-continent="{$value.continent}">
+                  {$value.city}
+                </option>
+              {/if}
+            {/loop}
+          </select>
+        </td>
       </tr>
 
       <tr>
       <tr>
         <td valign="top"><b>Enable REST API</b></td>
         <td>
-          <input type="checkbox" name="apiEnabled" id="apiEnabled"
+          <input type="checkbox" name="enableApi" id="enableApi"
                  {if="$api_enabled"}checked{/if}/>
-          <label for="apiEnabled">&nbsp;Allow third party software to use Shaarli such as mobile application.</label>
+          <label for="enableApi">&nbsp;Allow third party software to use Shaarli such as mobile application.</label>
         </td>
       </tr>
       <tr>
index 7ca567e7549f5b3c729673a38c8e8455bfd6ddf9..9c72d9938bca0dd6750daf950c778b5c0cb8ae6d 100644 (file)
@@ -41,6 +41,10 @@ strong {
     font-weight: bold;
 }
 
+.hidden {
+    display: none;
+}
+
 /* Buttons */
 .bigbutton, #pageheader a.bigbutton  {
     background-color: #c0c0c0;
index 071e1160e5bbae106b58206bdd5c152f6891b45f..bb9e4a562040b410b00e90bbc6a6a63b5520d9ab 100644 (file)
@@ -5,7 +5,7 @@
 <div id="pageheader">
   {include="page.header"}
   <div id="uploaddiv">
-    Import Netscape HTML bookmarks (as exported from Firefox/Chrome/Opera/Delicious/Diigo...) (Max: {$maxfilesize} bytes).
+    Import Netscape HTML bookmarks (as exported from Firefox/Chrome/Opera/Delicious/Diigo...) (Max: {$maxfilesize}).
     <form method="POST" action="?do=import" enctype="multipart/form-data"
           name="uploadform" id="uploadform">
       <input type="hidden" name="token" value="{$token}">
index 42874dcdb42c61963050599c6df922a079b615f9..aca890d6c3a549fca924fa02a58a5fa384a7dae6 100644 (file)
@@ -1,6 +1,6 @@
 <!DOCTYPE html>
 <html>
-<head>{include="includes"}{$timezone_js}</head>
+<head>{include="includes"}</head>
 <body onload="document.installform.setlogin.focus();">
 <div id="install">
     <h1>Shaarli</h1>
@@ -9,7 +9,31 @@
         <table>
             <tr><td><b>Login:</b></td><td><input type="text" name="setlogin" size="30"></td></tr>
             <tr><td><b>Password:</b></td><td><input type="password" name="setpassword" size="30"></td></tr>
-            {$timezone_html}
+            <tr>
+                <td><b>Timezone:</b></td>
+                <td>
+                    <select id="continent" name="continent">
+                        {loop="$continents"}
+                        {if="$key !== 'selected'"}
+                        <option value="{$value}" {if="$continents.selected === $value"}selected{/if}>
+                        {$value}
+                        </option>
+                        {/if}
+                        {/loop}
+                    </select>
+                    <select id="city" name="city">
+                        {loop="$cities"}
+                        {if="$key !== 'selected'"}
+                        <option value="{$value.city}"
+                                {if="$cities.selected === $value.city"}selected{/if}
+                        data-continent="{$value.continent}">
+                        {$value.city}
+                        </option>
+                        {/if}
+                        {/loop}
+                    </select>
+                </td>
+            </tr>
             <tr><td><b>Page title:</b></td><td><input type="text" name="title" size="30"></td></tr>
             <tr><td valign="top"><b>Update:</b></td><td>
                 <input type="checkbox" name="updateCheck" id="updateCheck" checked="checked"><label for="updateCheck">&nbsp;Notify me when a new release is ready</label></td>
diff --git a/tpl/vintage/js/shaarli.js b/tpl/vintage/js/shaarli.js
new file mode 100644 (file)
index 0000000..9bcc96f
--- /dev/null
@@ -0,0 +1,32 @@
+window.onload = function () {
+    var continent = document.getElementById('continent');
+    var city = document.getElementById('city');
+    if (continent != null && city != null) {
+        continent.addEventListener('change', function(event) {
+            hideTimezoneCities(city, continent.options[continent.selectedIndex].value, true);
+        });
+        hideTimezoneCities(city, continent.options[continent.selectedIndex].value, false);
+    }
+};
+
+/**
+ * Add the class 'hidden' to city options not attached to the current selected continent.
+ *
+ * @param cities           List of <option> elements
+ * @param currentContinent Current selected continent
+ * @param reset            Set to true to reset the selected value
+ */
+function hideTimezoneCities(cities, currentContinent, reset = false) {
+    var first = true;
+    [].forEach.call(cities, function(option) {
+        if (option.getAttribute('data-continent') != currentContinent) {
+            option.className = 'hidden';
+        } else {
+            option.className = '';
+            if (reset === true && first === true) {
+                option.setAttribute('selected', 'selected');
+                first = false;
+            }
+        }
+    });
+}
index 006d1d683b465d303ae56c4332d659bedab0533b..4ce0803a064dcbf3a78efed3043566b7986cf1ca 100644 (file)
@@ -26,6 +26,7 @@
 <script>function confirmDeleteLink() { var agree=confirm("Are you sure you want to delete this link ?"); if (agree) return true ; else return false ; }</script>
 {/if}
 
+<script src="js/shaarli.js"></script>
 {loop="$plugins_footer.js_files"}
        <script src="{$value}#"></script>
 {/loop}