aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--README.md11
-rw-r--r--application/ApplicationUtils.php45
-rw-r--r--application/LinkDB.php4
-rw-r--r--application/PageBuilder.php2
-rw-r--r--application/TimeZone.php101
-rw-r--r--application/Updater.php44
-rw-r--r--application/Utils.php98
-rw-r--r--application/api/ApiUtils.php35
-rw-r--r--application/api/controllers/ApiController.php10
-rw-r--r--application/api/controllers/Links.php44
-rw-r--r--composer.lock2424
-rw-r--r--index.php106
-rw-r--r--plugins/readityourself/book-open.pngbin568 -> 0 bytes
-rw-r--r--plugins/readityourself/readityourself.html1
-rw-r--r--plugins/readityourself/readityourself.meta2
-rw-r--r--plugins/readityourself/readityourself.php51
-rw-r--r--shaarli_version.php2
-rw-r--r--tests/ApplicationUtilsTest.php49
-rw-r--r--tests/TimeZoneTest.php83
-rw-r--r--tests/UtilsTest.php91
-rw-r--r--tests/api/controllers/PostLinkTest.php193
-rw-r--r--tests/languages/de/UtilsDeTest.php22
-rw-r--r--tests/languages/en/UtilsEnTest.php22
-rw-r--r--tests/languages/fr/UtilsFrTest.php20
-rw-r--r--tests/plugins/PluginReadityourselfTest.php99
-rw-r--r--tests/utils/ReferenceLinkDB.php2
-rw-r--r--tpl/default/configure.html32
-rw-r--r--tpl/default/css/shaarli.css31
-rw-r--r--tpl/default/daily.html2
-rw-r--r--tpl/default/fonts/Fira-Sans-regular.woffbin17100 -> 0 bytes
-rw-r--r--tpl/default/fonts/Fira-Sans-regular.woff2bin13836 -> 0 bytes
-rw-r--r--tpl/default/fonts/Roboto-Bold.woffbin0 -> 89584 bytes
-rw-r--r--tpl/default/fonts/Roboto-Bold.woff2bin0 -> 63320 bytes
-rw-r--r--tpl/default/fonts/Roboto-Regular.woffbin0 -> 89732 bytes
-rw-r--r--tpl/default/fonts/Roboto-Regular.woff2bin0 -> 63412 bytes
-rw-r--r--tpl/default/import.html1
-rw-r--r--tpl/default/install.html60
-rw-r--r--tpl/default/js/shaarli.js58
-rw-r--r--tpl/vintage/configure.html28
-rw-r--r--tpl/vintage/css/shaarli.css4
-rw-r--r--tpl/vintage/import.html2
-rw-r--r--tpl/vintage/install.html28
-rw-r--r--tpl/vintage/js/shaarli.js32
-rw-r--r--tpl/vintage/page.footer.html1
45 files changed, 3451 insertions, 390 deletions
diff --git a/.gitignore b/.gitignore
index e64c8a4b..984d9d1f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,7 +13,6 @@ pagecache
13*.rtpl.php 13*.rtpl.php
14 14
15# 3rd-party dependencies 15# 3rd-party dependencies
16composer.lock
17vendor/ 16vendor/
18 17
19# Release archives 18# Release archives
diff --git a/README.md b/README.md
index db1b901f..cfdefc66 100644
--- a/README.md
+++ b/README.md
@@ -6,13 +6,18 @@ _Do you want to share the links you discover?_
6_Shaarli is a minimalist delicious clone that you can install on your own server._ 6_Shaarli is a minimalist delicious clone that you can install on your own server._
7_It is designed to be personal (single-user), fast and handy._ 7_It is designed to be personal (single-user), fast and handy._
8 8
9[![](https://img.shields.io/travis/shaarli/Shaarli.svg?label=master)](https://travis-ci.org/shaarli/Shaarli) 9[![](https://img.shields.io/badge/stable-v0.7.1-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.7.1)
10[![](https://img.shields.io/travis/shaarli/Shaarli/stable.svg?label=stable)](https://travis-ci.org/shaarli/Shaarli) 10[![](https://img.shields.io/travis/shaarli/Shaarli/stable.svg?label=stable)](https://travis-ci.org/shaarli/Shaarli)
11[![](https://img.shields.io/github/release/shaarli/shaarli.svg)](https://github.com/shaarli/Shaarli/releases/latest/) 11•
12[![Docker repository](https://img.shields.io/docker/pulls/shaarli/shaarli.svg)](https://hub.docker.com/r/shaarli/shaarli/) 12[![](https://img.shields.io/badge/latest-v0.8.4-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.8.4)
13[![](https://img.shields.io/travis/shaarli/Shaarli/latest.svg?label=latest)](https://travis-ci.org/shaarli/Shaarli)
14•
15[![](https://img.shields.io/badge/master-v0.9.x-blue.svg)](https://github.com/shaarli/Shaarli)
16[![](https://img.shields.io/travis/shaarli/Shaarli.svg?label=master)](https://travis-ci.org/shaarli/Shaarli)
13 17
14[![Join the chat at https://gitter.im/shaarli/Shaarli](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/shaarli/Shaarli) 18[![Join the chat at https://gitter.im/shaarli/Shaarli](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/shaarli/Shaarli)
15[![Bountysource](https://www.bountysource.com/badge/team?team_id=19583&style=bounties_received)](https://www.bountysource.com/teams/shaarli/issues) 19[![Bountysource](https://www.bountysource.com/badge/team?team_id=19583&style=bounties_received)](https://www.bountysource.com/teams/shaarli/issues)
20[![Docker repository](https://img.shields.io/docker/pulls/shaarli/shaarli.svg)](https://hub.docker.com/r/shaarli/shaarli/)
16 21
17## Quickstart 22## Quickstart
18- [Wiki/documentation](https://github.com/shaarli/Shaarli/wiki) 23- [Wiki/documentation](https://github.com/shaarli/Shaarli/wiki)
diff --git a/application/ApplicationUtils.php b/application/ApplicationUtils.php
index a0f482b0..85dcbeeb 100644
--- a/application/ApplicationUtils.php
+++ b/application/ApplicationUtils.php
@@ -4,9 +4,13 @@
4 */ 4 */
5class ApplicationUtils 5class ApplicationUtils
6{ 6{
7 /**
8 * @var string File containing the current version
9 */
10 public static $VERSION_FILE = 'shaarli_version.php';
11
7 private static $GIT_URL = 'https://raw.githubusercontent.com/shaarli/Shaarli'; 12 private static $GIT_URL = 'https://raw.githubusercontent.com/shaarli/Shaarli';
8 private static $GIT_BRANCHES = array('master', 'stable'); 13 private static $GIT_BRANCHES = array('latest', 'stable');
9 private static $VERSION_FILE = 'shaarli_version.php';
10 private static $VERSION_START_TAG = '<?php /* '; 14 private static $VERSION_START_TAG = '<?php /* ';
11 private static $VERSION_END_TAG = ' */ ?>'; 15 private static $VERSION_END_TAG = ' */ ?>';
12 16
@@ -29,6 +33,30 @@ class ApplicationUtils
29 return false; 33 return false;
30 } 34 }
31 35
36 return $data;
37 }
38
39 /**
40 * Retrieve the version from a remote URL or a file.
41 *
42 * @param string $remote URL or file to fetch.
43 * @param int $timeout For URLs fetching.
44 *
45 * @return bool|string The version or false if it couldn't be retrieved.
46 */
47 public static function getVersion($remote, $timeout = 2)
48 {
49 if (startsWith($remote, 'http')) {
50 if (($data = static::getLatestGitVersionCode($remote, $timeout)) === false) {
51 return false;
52 }
53 } else {
54 if (! is_file($remote)) {
55 return false;
56 }
57 $data = file_get_contents($remote);
58 }
59
32 return str_replace( 60 return str_replace(
33 array(self::$VERSION_START_TAG, self::$VERSION_END_TAG, PHP_EOL), 61 array(self::$VERSION_START_TAG, self::$VERSION_END_TAG, PHP_EOL),
34 array('', '', ''), 62 array('', '', ''),
@@ -65,13 +93,10 @@ class ApplicationUtils
65 $isLoggedIn, 93 $isLoggedIn,
66 $branch='stable') 94 $branch='stable')
67 { 95 {
68 if (! $isLoggedIn) { 96 // Do not check versions for visitors
69 // Do not check versions for visitors 97 // Do not check if the user doesn't want to
70 return false; 98 // Do not check with dev version
71 } 99 if (! $isLoggedIn || empty($enableCheck) || $currentVersion === 'dev') {
72
73 if (empty($enableCheck)) {
74 // Do not check if the user doesn't want to
75 return false; 100 return false;
76 } 101 }
77 102
@@ -93,7 +118,7 @@ class ApplicationUtils
93 118
94 // Late Static Binding allows overriding within tests 119 // Late Static Binding allows overriding within tests
95 // See http://php.net/manual/en/language.oop5.late-static-bindings.php 120 // See http://php.net/manual/en/language.oop5.late-static-bindings.php
96 $latestVersion = static::getLatestGitVersionCode( 121 $latestVersion = static::getVersion(
97 self::$GIT_URL . '/' . $branch . '/' . self::$VERSION_FILE 122 self::$GIT_URL . '/' . $branch . '/' . self::$VERSION_FILE
98 ); 123 );
99 124
diff --git a/application/LinkDB.php b/application/LinkDB.php
index 2fb15040..0d3c85bd 100644
--- a/application/LinkDB.php
+++ b/application/LinkDB.php
@@ -138,10 +138,10 @@ class LinkDB implements Iterator, Countable, ArrayAccess
138 if (!isset($value['id']) || empty($value['url'])) { 138 if (!isset($value['id']) || empty($value['url'])) {
139 die('Internal Error: A link should always have an id and URL.'); 139 die('Internal Error: A link should always have an id and URL.');
140 } 140 }
141 if ((! empty($offset) && ! is_int($offset)) || ! is_int($value['id'])) { 141 if (($offset !== null && ! is_int($offset)) || ! is_int($value['id'])) {
142 die('You must specify an integer as a key.'); 142 die('You must specify an integer as a key.');
143 } 143 }
144 if (! empty($offset) && $offset !== $value['id']) { 144 if ($offset !== null && $offset !== $value['id']) {
145 die('Array offset and link ID must be equal.'); 145 die('Array offset and link ID must be equal.');
146 } 146 }
147 147
diff --git a/application/PageBuilder.php b/application/PageBuilder.php
index b133dee8..8e39455b 100644
--- a/application/PageBuilder.php
+++ b/application/PageBuilder.php
@@ -1,5 +1,7 @@
1<?php 1<?php
2 2
3use Shaarli\Config\ConfigManager;
4
3/** 5/**
4 * This class is in charge of building the final page. 6 * This class is in charge of building the final page.
5 * (This is basically a wrapper around RainTPL which pre-fills some fields.) 7 * (This is basically a wrapper around RainTPL which pre-fills some fields.)
diff --git a/application/TimeZone.php b/application/TimeZone.php
index 36a8fb12..c1869ef8 100644
--- a/application/TimeZone.php
+++ b/application/TimeZone.php
@@ -1,23 +1,42 @@
1<?php 1<?php
2/** 2/**
3 * Generates the timezone selection form and JavaScript. 3 * Generates a list of available timezone continents and cities.
4 * 4 *
5 * Note: 'UTC/UTC' is mapped to 'UTC' to form a valid option 5 * Two distinct array based on available timezones
6 * and the one selected in the settings:
7 * - (0) continents:
8 * + list of available continents
9 * + special key 'selected' containing the value of the selected timezone's continent
10 * - (1) cities:
11 * + list of available cities associated with their continent
12 * + special key 'selected' containing the value of the selected timezone's city (without the continent)
6 * 13 *
7 * Example: preselect Europe/Paris 14 * Example:
8 * list($htmlform, $js) = generateTimeZoneForm('Europe/Paris'); 15 * [
16 * [
17 * 'America',
18 * 'Europe',
19 * 'selected' => 'Europe',
20 * ],
21 * [
22 * ['continent' => 'America', 'city' => 'Toronto'],
23 * ['continent' => 'Europe', 'city' => 'Paris'],
24 * 'selected' => 'Paris',
25 * ],
26 * ];
9 * 27 *
28 * Notes:
29 * - 'UTC/UTC' is mapped to 'UTC' to form a valid option
30 * - a few timezone cities includes the country/state, such as Argentina/Buenos_Aires
31 * - these arrays are designed to build timezone selects in template files with any HTML structure
32 *
33 * @param array $installedTimeZones List of installed timezones as string
10 * @param string $preselectedTimezone preselected timezone (optional) 34 * @param string $preselectedTimezone preselected timezone (optional)
11 * 35 *
12 * @return array containing the generated HTML form and Javascript code 36 * @return array[] continents and cities
13 **/ 37 **/
14function generateTimeZoneForm($preselectedTimezone='') 38function generateTimeZoneData($installedTimeZones, $preselectedTimezone = '')
15{ 39{
16 // Select the server timezone
17 if ($preselectedTimezone == '') {
18 $preselectedTimezone = date_default_timezone_get();
19 }
20
21 if ($preselectedTimezone == 'UTC') { 40 if ($preselectedTimezone == 'UTC') {
22 $pcity = $pcontinent = 'UTC'; 41 $pcity = $pcontinent = 'UTC';
23 } else { 42 } else {
@@ -27,62 +46,30 @@ function generateTimeZoneForm($preselectedTimezone='')
27 $pcity = substr($preselectedTimezone, $spos+1); 46 $pcity = substr($preselectedTimezone, $spos+1);
28 } 47 }
29 48
30 // The list is in the form 'Europe/Paris', 'America/Argentina/Buenos_Aires' 49 $continents = [];
31 // We split the list in continents/cities. 50 $cities = [];
32 $continents = array(); 51 foreach ($installedTimeZones as $tz) {
33 $cities = array();
34
35 // TODO: use a template to generate the HTML/Javascript form
36
37 foreach (timezone_identifiers_list() as $tz) {
38 if ($tz == 'UTC') { 52 if ($tz == 'UTC') {
39 $tz = 'UTC/UTC'; 53 $tz = 'UTC/UTC';
40 } 54 }
41 $spos = strpos($tz, '/'); 55 $spos = strpos($tz, '/');
42 56
43 if ($spos !== false) { 57 // Ignore invalid timezones
44 $continent = substr($tz, 0, $spos); 58 if ($spos === false) {
45 $city = substr($tz, $spos+1); 59 continue;
46 $continents[$continent] = 1;
47
48 if (!isset($cities[$continent])) {
49 $cities[$continent] = '';
50 }
51 $cities[$continent] .= '<option value="'.$city.'"';
52 if ($pcity == $city) {
53 $cities[$continent] .= ' selected="selected"';
54 }
55 $cities[$continent] .= '>'.$city.'</option>';
56 } 60 }
57 }
58
59 $continentsHtml = '';
60 $continents = array_keys($continents);
61 61
62 foreach ($continents as $continent) { 62 $continent = substr($tz, 0, $spos);
63 $continentsHtml .= '<option value="'.$continent.'"'; 63 $city = substr($tz, $spos+1);
64 if ($pcontinent == $continent) { 64 $cities[] = ['continent' => $continent, 'city' => $city];
65 $continentsHtml .= ' selected="selected"'; 65 $continents[$continent] = true;
66 }
67 $continentsHtml .= '>'.$continent.'</option>';
68 } 66 }
69 67
70 // Timezone selection form 68 $continents = array_keys($continents);
71 $timezoneForm = 'Continent:'; 69 $continents['selected'] = $pcontinent;
72 $timezoneForm .= '<select name="continent" id="continent" onChange="onChangecontinent();">'; 70 $cities['selected'] = $pcity;
73 $timezoneForm .= $continentsHtml.'</select>';
74 $timezoneForm .= '&nbsp;&nbsp;&nbsp;&nbsp;City:';
75 $timezoneForm .= '<select name="city" id="city">'.$cities[$pcontinent].'</select><br />';
76
77 // Javascript handler - updates the city list when the user selects a continent
78 $timezoneJs = '<script>';
79 $timezoneJs .= 'function onChangecontinent() {';
80 $timezoneJs .= 'document.getElementById("city").innerHTML =';
81 $timezoneJs .= ' citiescontinent[document.getElementById("continent").value]; }';
82 $timezoneJs .= 'var citiescontinent = '.json_encode($cities).';';
83 $timezoneJs .= '</script>';
84 71
85 return array($timezoneForm, $timezoneJs); 72 return [$continents, $cities];
86} 73}
87 74
88/** 75/**
diff --git a/application/Updater.php b/application/Updater.php
index efbfc832..0fb68c5a 100644
--- a/application/Updater.php
+++ b/application/Updater.php
@@ -396,6 +396,50 @@ class Updater
396 396
397 return true; 397 return true;
398 } 398 }
399
400 /**
401 * Update updates.check_updates_branch setting.
402 *
403 * If the current major version digit matches the latest branch
404 * major version digit, we set the branch to `latest`,
405 * otherwise we'll check updates on the `stable` branch.
406 *
407 * No update required for the dev version.
408 *
409 * Note: due to hardcoded URL and lack of dependency injection, this is not unit testable.
410 *
411 * FIXME! This needs to be removed when we switch to first digit major version
412 * instead of the second one since the versionning process will change.
413 */
414 public function updateMethodCheckUpdateRemoteBranch()
415 {
416 if (shaarli_version === 'dev' || $this->conf->get('updates.check_updates_branch') === 'latest') {
417 return true;
418 }
419
420 // Get latest branch major version digit
421 $latestVersion = ApplicationUtils::getLatestGitVersionCode(
422 'https://raw.githubusercontent.com/shaarli/Shaarli/latest/shaarli_version.php',
423 5
424 );
425 if (preg_match('/(\d+)\.\d+$/', $latestVersion, $matches) === false) {
426 return false;
427 }
428 $latestMajor = $matches[1];
429
430 // Get current major version digit
431 preg_match('/(\d+)\.\d+$/', shaarli_version, $matches);
432 $currentMajor = $matches[1];
433
434 if ($currentMajor === $latestMajor) {
435 $branch = 'latest';
436 } else {
437 $branch = 'stable';
438 }
439 $this->conf->set('updates.check_updates_branch', $branch);
440 $this->conf->write($this->isLoggedIn);
441 return true;
442 }
399} 443}
400 444
401/** 445/**
diff --git a/application/Utils.php b/application/Utils.php
index 5c077450..ab463af9 100644
--- a/application/Utils.php
+++ b/application/Utils.php
@@ -321,25 +321,117 @@ function normalize_spaces($string)
321 * otherwise default format '%c' will be returned. 321 * otherwise default format '%c' will be returned.
322 * 322 *
323 * @param DateTime $date to format. 323 * @param DateTime $date to format.
324 * @param bool $time Displays time if true.
324 * @param bool $intl Use international format if true. 325 * @param bool $intl Use international format if true.
325 * 326 *
326 * @return bool|string Formatted date, or false if the input is invalid. 327 * @return bool|string Formatted date, or false if the input is invalid.
327 */ 328 */
328function format_date($date, $intl = true) 329function format_date($date, $time = true, $intl = true)
329{ 330{
330 if (! $date instanceof DateTime) { 331 if (! $date instanceof DateTime) {
331 return false; 332 return false;
332 } 333 }
333 334
334 if (! $intl || ! class_exists('IntlDateFormatter')) { 335 if (! $intl || ! class_exists('IntlDateFormatter')) {
335 return strftime('%c', $date->getTimestamp()); 336 $format = $time ? '%c' : '%x';
337 return strftime($format, $date->getTimestamp());
336 } 338 }
337 339
338 $formatter = new IntlDateFormatter( 340 $formatter = new IntlDateFormatter(
339 setlocale(LC_TIME, 0), 341 setlocale(LC_TIME, 0),
340 IntlDateFormatter::LONG, 342 IntlDateFormatter::LONG,
341 IntlDateFormatter::LONG 343 $time ? IntlDateFormatter::LONG : IntlDateFormatter::NONE
342 ); 344 );
343 345
344 return $formatter->format($date); 346 return $formatter->format($date);
345} 347}
348
349/**
350 * Check if the input is an integer, no matter its real type.
351 *
352 * PHP is a bit messy regarding this:
353 * - is_int returns false if the input is a string
354 * - ctype_digit returns false if the input is an integer or negative
355 *
356 * @param mixed $input value
357 *
358 * @return bool true if the input is an integer, false otherwise
359 */
360function is_integer_mixed($input)
361{
362 if (is_array($input) || is_bool($input) || is_object($input)) {
363 return false;
364 }
365 $input = strval($input);
366 return ctype_digit($input) || (startsWith($input, '-') && ctype_digit(substr($input, 1)));
367}
368
369/**
370 * Convert post_max_size/upload_max_filesize (e.g. '16M') parameters to bytes.
371 *
372 * @param string $val Size expressed in string.
373 *
374 * @return int Size expressed in bytes.
375 */
376function return_bytes($val)
377{
378 if (is_integer_mixed($val) || $val === '0' || empty($val)) {
379 return $val;
380 }
381 $val = trim($val);
382 $last = strtolower($val[strlen($val)-1]);
383 $val = intval(substr($val, 0, -1));
384 switch($last) {
385 case 'g': $val *= 1024;
386 case 'm': $val *= 1024;
387 case 'k': $val *= 1024;
388 }
389 return $val;
390}
391
392/**
393 * Return a human readable size from bytes.
394 *
395 * @param int $bytes value
396 *
397 * @return string Human readable size
398 */
399function human_bytes($bytes)
400{
401 if ($bytes === '') {
402 return t('Setting not set');
403 }
404 if (! is_integer_mixed($bytes)) {
405 return $bytes;
406 }
407 $bytes = intval($bytes);
408 if ($bytes === 0) {
409 return t('Unlimited');
410 }
411
412 $units = [t('B'), t('kiB'), t('MiB'), t('GiB')];
413 for ($i = 0; $i < count($units) && $bytes >= 1024; ++$i) {
414 $bytes /= 1024;
415 }
416
417 return round($bytes) . $units[$i];
418}
419
420/**
421 * Try to determine max file size for uploads (POST).
422 * Returns an integer (in bytes) or formatted depending on $format.
423 *
424 * @param mixed $limitPost post_max_size PHP setting
425 * @param mixed $limitUpload upload_max_filesize PHP setting
426 * @param bool $format Format max upload size to human readable size
427 *
428 * @return int|string max upload file size
429 */
430function get_max_upload_size($limitPost, $limitUpload, $format = true)
431{
432 $size1 = return_bytes($limitPost);
433 $size2 = return_bytes($limitUpload);
434 // Return the smaller of two:
435 $maxsize = min($size1, $size2);
436 return $format ? human_bytes($maxsize) : $maxsize;
437}
diff --git a/application/api/ApiUtils.php b/application/api/ApiUtils.php
index d4015865..b8155a34 100644
--- a/application/api/ApiUtils.php
+++ b/application/api/ApiUtils.php
@@ -12,7 +12,7 @@ class ApiUtils
12 /** 12 /**
13 * Validates a JWT token authenticity. 13 * Validates a JWT token authenticity.
14 * 14 *
15 * @param string $token JWT token extracted from the headers. 15 * @param string $token JWT token extracted from the headers.
16 * @param string $secret API secret set in the settings. 16 * @param string $secret API secret set in the settings.
17 * 17 *
18 * @throws ApiAuthorizationException the token is not valid. 18 * @throws ApiAuthorizationException the token is not valid.
@@ -50,7 +50,7 @@ class ApiUtils
50 /** 50 /**
51 * Format a Link for the REST API. 51 * Format a Link for the REST API.
52 * 52 *
53 * @param array $link Link data read from the datastore. 53 * @param array $link Link data read from the datastore.
54 * @param string $indexUrl Shaarli's index URL (used for relative URL). 54 * @param string $indexUrl Shaarli's index URL (used for relative URL).
55 * 55 *
56 * @return array Link data formatted for the REST API. 56 * @return array Link data formatted for the REST API.
@@ -77,4 +77,35 @@ class ApiUtils
77 } 77 }
78 return $out; 78 return $out;
79 } 79 }
80
81 /**
82 * Convert a link given through a request, to a valid link for LinkDB.
83 *
84 * If no URL is provided, it will generate a local note URL.
85 * If no title is provided, it will use the URL as title.
86 *
87 * @param array $input Request Link.
88 * @param bool $defaultPrivate Request Link.
89 *
90 * @return array Formatted link.
91 */
92 public static function buildLinkFromRequest($input, $defaultPrivate)
93 {
94 $input['url'] = ! empty($input['url']) ? cleanup_url($input['url']) : '';
95 if (isset($input['private'])) {
96 $private = filter_var($input['private'], FILTER_VALIDATE_BOOLEAN);
97 } else {
98 $private = $defaultPrivate;
99 }
100
101 $link = [
102 'title' => ! empty($input['title']) ? $input['title'] : $input['url'],
103 'url' => $input['url'],
104 'description' => ! empty($input['description']) ? $input['description'] : '',
105 'tags' => ! empty($input['tags']) ? implode(' ', $input['tags']) : '',
106 'private' => $private,
107 'created' => new \DateTime(),
108 ];
109 return $link;
110 }
80} 111}
diff --git a/application/api/controllers/ApiController.php b/application/api/controllers/ApiController.php
index 1dd47f17..f35b923a 100644
--- a/application/api/controllers/ApiController.php
+++ b/application/api/controllers/ApiController.php
@@ -51,4 +51,14 @@ abstract class ApiController
51 $this->jsonStyle = null; 51 $this->jsonStyle = null;
52 } 52 }
53 } 53 }
54
55 /**
56 * Get the container.
57 *
58 * @return Container
59 */
60 public function getCi()
61 {
62 return $this->ci;
63 }
54} 64}
diff --git a/application/api/controllers/Links.php b/application/api/controllers/Links.php
index d4f1a09c..0db10fd0 100644
--- a/application/api/controllers/Links.php
+++ b/application/api/controllers/Links.php
@@ -97,11 +97,53 @@ class Links extends ApiController
97 */ 97 */
98 public function getLink($request, $response, $args) 98 public function getLink($request, $response, $args)
99 { 99 {
100 if (! isset($this->linkDb[$args['id']])) { 100 if (!isset($this->linkDb[$args['id']])) {
101 throw new ApiLinkNotFoundException(); 101 throw new ApiLinkNotFoundException();
102 } 102 }
103 $index = index_url($this->ci['environment']); 103 $index = index_url($this->ci['environment']);
104 $out = ApiUtils::formatLink($this->linkDb[$args['id']], $index); 104 $out = ApiUtils::formatLink($this->linkDb[$args['id']], $index);
105
105 return $response->withJson($out, 200, $this->jsonStyle); 106 return $response->withJson($out, 200, $this->jsonStyle);
106 } 107 }
108
109 /**
110 * Creates a new link from posted request body.
111 *
112 * @param Request $request Slim request.
113 * @param Response $response Slim response.
114 *
115 * @return Response response.
116 */
117 public function postLink($request, $response)
118 {
119 $data = $request->getParsedBody();
120 $link = ApiUtils::buildLinkFromRequest($data, $this->conf->get('privacy.default_private_links'));
121 // duplicate by URL, return 409 Conflict
122 if (! empty($link['url']) && ! empty($dup = $this->linkDb->getLinkFromUrl($link['url']))) {
123 return $response->withJson(
124 ApiUtils::formatLink($dup, index_url($this->ci['environment'])),
125 409,
126 $this->jsonStyle
127 );
128 }
129
130 $link['id'] = $this->linkDb->getNextId();
131 $link['shorturl'] = link_small_hash($link['created'], $link['id']);
132
133 // note: general relative URL
134 if (empty($link['url'])) {
135 $link['url'] = '?' . $link['shorturl'];
136 }
137
138 if (empty($link['title'])) {
139 $link['title'] = $link['url'];
140 }
141
142 $this->linkDb[$link['id']] = $link;
143 $this->linkDb->save($this->conf->get('resource.page_cache'));
144 $out = ApiUtils::formatLink($link, index_url($this->ci['environment']));
145 $redirect = $this->ci->router->relativePathFor('getLink', ['id' => $link['id']]);
146 return $response->withAddedHeader('Location', $redirect)
147 ->withJson($out, 201, $this->jsonStyle);
148 }
107} 149}
diff --git a/composer.lock b/composer.lock
new file mode 100644
index 00000000..b285fcc9
--- /dev/null
+++ b/composer.lock
@@ -0,0 +1,2424 @@
1{
2 "_readme": [
3 "This file locks the dependencies of your project to a known state",
4 "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
5 "This file is @generated automatically"
6 ],
7 "content-hash": "ffcfc8e42f14183de6ef90874429e77a",
8 "packages": [
9 {
10 "name": "container-interop/container-interop",
11 "version": "1.2.0",
12 "source": {
13 "type": "git",
14 "url": "https://github.com/container-interop/container-interop.git",
15 "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8"
16 },
17 "dist": {
18 "type": "zip",
19 "url": "https://api.github.com/repos/container-interop/container-interop/zipball/79cbf1341c22ec75643d841642dd5d6acd83bdb8",
20 "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8",
21 "shasum": ""
22 },
23 "require": {
24 "psr/container": "^1.0"
25 },
26 "type": "library",
27 "autoload": {
28 "psr-4": {
29 "Interop\\Container\\": "src/Interop/Container/"
30 }
31 },
32 "notification-url": "https://packagist.org/downloads/",
33 "license": [
34 "MIT"
35 ],
36 "description": "Promoting the interoperability of container objects (DIC, SL, etc.)",
37 "homepage": "https://github.com/container-interop/container-interop",
38 "time": "2017-02-14T19:40:03+00:00"
39 },
40 {
41 "name": "erusev/parsedown",
42 "version": "1.6.0",
43 "source": {
44 "type": "git",
45 "url": "https://github.com/erusev/parsedown.git",
46 "reference": "3ebbd730b5c2cf5ce78bc1bf64071407fc6674b7"
47 },
48 "dist": {
49 "type": "zip",
50 "url": "https://api.github.com/repos/erusev/parsedown/zipball/3ebbd730b5c2cf5ce78bc1bf64071407fc6674b7",
51 "reference": "3ebbd730b5c2cf5ce78bc1bf64071407fc6674b7",
52 "shasum": ""
53 },
54 "type": "library",
55 "autoload": {
56 "psr-0": {
57 "Parsedown": ""
58 }
59 },
60 "notification-url": "https://packagist.org/downloads/",
61 "license": [
62 "MIT"
63 ],
64 "authors": [
65 {
66 "name": "Emanuil Rusev",
67 "email": "hello@erusev.com",
68 "homepage": "http://erusev.com"
69 }
70 ],
71 "description": "Parser for Markdown.",
72 "homepage": "http://parsedown.org",
73 "keywords": [
74 "markdown",
75 "parser"
76 ],
77 "time": "2015-10-04T16:44:32+00:00"
78 },
79 {
80 "name": "katzgrau/klogger",
81 "version": "1.2.1",
82 "source": {
83 "type": "git",
84 "url": "https://github.com/katzgrau/KLogger.git",
85 "reference": "a4ed373fa8a214aa4ae7aa4f221fe2c6ce862ef1"
86 },
87 "dist": {
88 "type": "zip",
89 "url": "https://api.github.com/repos/katzgrau/KLogger/zipball/a4ed373fa8a214aa4ae7aa4f221fe2c6ce862ef1",
90 "reference": "a4ed373fa8a214aa4ae7aa4f221fe2c6ce862ef1",
91 "shasum": ""
92 },
93 "require": {
94 "php": ">=5.3",
95 "psr/log": "^1.0.0"
96 },
97 "require-dev": {
98 "phpunit/phpunit": "4.0.*"
99 },
100 "type": "library",
101 "autoload": {
102 "psr-4": {
103 "Katzgrau\\KLogger\\": "src/"
104 },
105 "classmap": [
106 "src/"
107 ]
108 },
109 "notification-url": "https://packagist.org/downloads/",
110 "license": [
111 "MIT"
112 ],
113 "authors": [
114 {
115 "name": "Kenny Katzgrau",
116 "email": "katzgrau@gmail.com"
117 },
118 {
119 "name": "Dan Horrigan",
120 "email": "dan@dhorrigan.com"
121 }
122 ],
123 "description": "A Simple Logging Class",
124 "keywords": [
125 "logging"
126 ],
127 "time": "2016-11-07T19:29:14+00:00"
128 },
129 {
130 "name": "nikic/fast-route",
131 "version": "v1.2.0",
132 "source": {
133 "type": "git",
134 "url": "https://github.com/nikic/FastRoute.git",
135 "reference": "b5f95749071c82a8e0f58586987627054400cdf6"
136 },
137 "dist": {
138 "type": "zip",
139 "url": "https://api.github.com/repos/nikic/FastRoute/zipball/b5f95749071c82a8e0f58586987627054400cdf6",
140 "reference": "b5f95749071c82a8e0f58586987627054400cdf6",
141 "shasum": ""
142 },
143 "require": {
144 "php": ">=5.4.0"
145 },
146 "type": "library",
147 "autoload": {
148 "psr-4": {
149 "FastRoute\\": "src/"
150 },
151 "files": [
152 "src/functions.php"
153 ]
154 },
155 "notification-url": "https://packagist.org/downloads/",
156 "license": [
157 "BSD-3-Clause"
158 ],
159 "authors": [
160 {
161 "name": "Nikita Popov",
162 "email": "nikic@php.net"
163 }
164 ],
165 "description": "Fast request router for PHP",
166 "keywords": [
167 "router",
168 "routing"
169 ],
170 "time": "2017-01-19T11:35:12+00:00"
171 },
172 {
173 "name": "pimple/pimple",
174 "version": "v3.0.2",
175 "source": {
176 "type": "git",
177 "url": "https://github.com/silexphp/Pimple.git",
178 "reference": "a30f7d6e57565a2e1a316e1baf2a483f788b258a"
179 },
180 "dist": {
181 "type": "zip",
182 "url": "https://api.github.com/repos/silexphp/Pimple/zipball/a30f7d6e57565a2e1a316e1baf2a483f788b258a",
183 "reference": "a30f7d6e57565a2e1a316e1baf2a483f788b258a",
184 "shasum": ""
185 },
186 "require": {
187 "php": ">=5.3.0"
188 },
189 "type": "library",
190 "extra": {
191 "branch-alias": {
192 "dev-master": "3.0.x-dev"
193 }
194 },
195 "autoload": {
196 "psr-0": {
197 "Pimple": "src/"
198 }
199 },
200 "notification-url": "https://packagist.org/downloads/",
201 "license": [
202 "MIT"
203 ],
204 "authors": [
205 {
206 "name": "Fabien Potencier",
207 "email": "fabien@symfony.com"
208 }
209 ],
210 "description": "Pimple, a simple Dependency Injection Container",
211 "homepage": "http://pimple.sensiolabs.org",
212 "keywords": [
213 "container",
214 "dependency injection"
215 ],
216 "time": "2015-09-11T15:10:35+00:00"
217 },
218 {
219 "name": "psr/container",
220 "version": "1.0.0",
221 "source": {
222 "type": "git",
223 "url": "https://github.com/php-fig/container.git",
224 "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f"
225 },
226 "dist": {
227 "type": "zip",
228 "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
229 "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
230 "shasum": ""
231 },
232 "require": {
233 "php": ">=5.3.0"
234 },
235 "type": "library",
236 "extra": {
237 "branch-alias": {
238 "dev-master": "1.0.x-dev"
239 }
240 },
241 "autoload": {
242 "psr-4": {
243 "Psr\\Container\\": "src/"
244 }
245 },
246 "notification-url": "https://packagist.org/downloads/",
247 "license": [
248 "MIT"
249 ],
250 "authors": [
251 {
252 "name": "PHP-FIG",
253 "homepage": "http://www.php-fig.org/"
254 }
255 ],
256 "description": "Common Container Interface (PHP FIG PSR-11)",
257 "homepage": "https://github.com/php-fig/container",
258 "keywords": [
259 "PSR-11",
260 "container",
261 "container-interface",
262 "container-interop",
263 "psr"
264 ],
265 "time": "2017-02-14T16:28:37+00:00"
266 },
267 {
268 "name": "psr/http-message",
269 "version": "1.0.1",
270 "source": {
271 "type": "git",
272 "url": "https://github.com/php-fig/http-message.git",
273 "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
274 },
275 "dist": {
276 "type": "zip",
277 "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
278 "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
279 "shasum": ""
280 },
281 "require": {
282 "php": ">=5.3.0"
283 },
284 "type": "library",
285 "extra": {
286 "branch-alias": {
287 "dev-master": "1.0.x-dev"
288 }
289 },
290 "autoload": {
291 "psr-4": {
292 "Psr\\Http\\Message\\": "src/"
293 }
294 },
295 "notification-url": "https://packagist.org/downloads/",
296 "license": [
297 "MIT"
298 ],
299 "authors": [
300 {
301 "name": "PHP-FIG",
302 "homepage": "http://www.php-fig.org/"
303 }
304 ],
305 "description": "Common interface for HTTP messages",
306 "homepage": "https://github.com/php-fig/http-message",
307 "keywords": [
308 "http",
309 "http-message",
310 "psr",
311 "psr-7",
312 "request",
313 "response"
314 ],
315 "time": "2016-08-06T14:39:51+00:00"
316 },
317 {
318 "name": "psr/log",
319 "version": "1.0.2",
320 "source": {
321 "type": "git",
322 "url": "https://github.com/php-fig/log.git",
323 "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d"
324 },
325 "dist": {
326 "type": "zip",
327 "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
328 "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
329 "shasum": ""
330 },
331 "require": {
332 "php": ">=5.3.0"
333 },
334 "type": "library",
335 "extra": {
336 "branch-alias": {
337 "dev-master": "1.0.x-dev"
338 }
339 },
340 "autoload": {
341 "psr-4": {
342 "Psr\\Log\\": "Psr/Log/"
343 }
344 },
345 "notification-url": "https://packagist.org/downloads/",
346 "license": [
347 "MIT"
348 ],
349 "authors": [
350 {
351 "name": "PHP-FIG",
352 "homepage": "http://www.php-fig.org/"
353 }
354 ],
355 "description": "Common interface for logging libraries",
356 "homepage": "https://github.com/php-fig/log",
357 "keywords": [
358 "log",
359 "psr",
360 "psr-3"
361 ],
362 "time": "2016-10-10T12:19:37+00:00"
363 },
364 {
365 "name": "pubsubhubbub/publisher",
366 "version": "dev-master",
367 "source": {
368 "type": "git",
369 "url": "https://github.com/pubsubhubbub/php-publisher.git",
370 "reference": "a5d6a0e1cc9d49101c3904480e5b06cbb8addba7"
371 },
372 "dist": {
373 "type": "zip",
374 "url": "https://api.github.com/repos/pubsubhubbub/php-publisher/zipball/a5d6a0e1cc9d49101c3904480e5b06cbb8addba7",
375 "reference": "a5d6a0e1cc9d49101c3904480e5b06cbb8addba7",
376 "shasum": ""
377 },
378 "require": {
379 "php": "~5.4 || ~7.0"
380 },
381 "type": "library",
382 "autoload": {
383 "psr-4": {
384 "pubsubhubbub\\publisher\\": "library/"
385 }
386 },
387 "notification-url": "https://packagist.org/downloads/",
388 "license": [
389 "Apache-2.0"
390 ],
391 "authors": [
392 {
393 "name": "pubsubhubbub publisher Contributors",
394 "homepage": "https://github.com/pubsubhubbub/php-publisher/contributors"
395 }
396 ],
397 "description": "pubsubhubbub implementation of publisher.",
398 "homepage": "https://github.com/pubsubhubbub/php-publisher",
399 "keywords": [
400 "data",
401 "feeds",
402 "publishers",
403 "pubsubhubbub"
404 ],
405 "time": "2016-11-15 06:24:01"
406 },
407 {
408 "name": "shaarli/netscape-bookmark-parser",
409 "version": "v2.0.1",
410 "source": {
411 "type": "git",
412 "url": "https://github.com/shaarli/netscape-bookmark-parser.git",
413 "reference": "b65c7235d490bd933cdd5f71520dae656253adb9"
414 },
415 "dist": {
416 "type": "zip",
417 "url": "https://api.github.com/repos/shaarli/netscape-bookmark-parser/zipball/b65c7235d490bd933cdd5f71520dae656253adb9",
418 "reference": "b65c7235d490bd933cdd5f71520dae656253adb9",
419 "shasum": ""
420 },
421 "require": {
422 "katzgrau/klogger": "~1.0",
423 "php": ">=5.3.4"
424 },
425 "require-dev": {
426 "phpunit/phpunit": "4.8.*"
427 },
428 "type": "library",
429 "autoload": {
430 "files": [
431 "NetscapeBookmarkParser.php"
432 ]
433 },
434 "notification-url": "https://packagist.org/downloads/",
435 "license": [
436 "MIT"
437 ],
438 "authors": [
439 {
440 "name": "Kafene",
441 "email": "io@kafene.org",
442 "homepage": "https://github.com/kafene",
443 "role": "Developer"
444 },
445 {
446 "name": "VirtualTam",
447 "email": "virtualtam@flibidi.net",
448 "homepage": "https://github.com/virtualtam",
449 "role": "Developer"
450 }
451 ],
452 "description": "Generic Netscape bookmark parser",
453 "homepage": "https://github.com/shaarli/netscape-bookmark-parser",
454 "keywords": [
455 "bookmark",
456 "link",
457 "netscape",
458 "parse"
459 ],
460 "time": "2017-03-08T20:11:40+00:00"
461 },
462 {
463 "name": "slim/slim",
464 "version": "3.8.1",
465 "source": {
466 "type": "git",
467 "url": "https://github.com/slimphp/Slim.git",
468 "reference": "5385302707530b2bccee1769613ad769859b826d"
469 },
470 "dist": {
471 "type": "zip",
472 "url": "https://api.github.com/repos/slimphp/Slim/zipball/5385302707530b2bccee1769613ad769859b826d",
473 "reference": "5385302707530b2bccee1769613ad769859b826d",
474 "shasum": ""
475 },
476 "require": {
477 "container-interop/container-interop": "^1.2",
478 "nikic/fast-route": "^1.0",
479 "php": ">=5.5.0",
480 "pimple/pimple": "^3.0",
481 "psr/container": "^1.0",
482 "psr/http-message": "^1.0"
483 },
484 "provide": {
485 "psr/http-message-implementation": "1.0"
486 },
487 "require-dev": {
488 "phpunit/phpunit": "^4.0",
489 "squizlabs/php_codesniffer": "^2.5"
490 },
491 "type": "library",
492 "autoload": {
493 "psr-4": {
494 "Slim\\": "Slim"
495 }
496 },
497 "notification-url": "https://packagist.org/downloads/",
498 "license": [
499 "MIT"
500 ],
501 "authors": [
502 {
503 "name": "Rob Allen",
504 "email": "rob@akrabat.com",
505 "homepage": "http://akrabat.com"
506 },
507 {
508 "name": "Josh Lockhart",
509 "email": "hello@joshlockhart.com",
510 "homepage": "https://joshlockhart.com"
511 },
512 {
513 "name": "Gabriel Manricks",
514 "email": "gmanricks@me.com",
515 "homepage": "http://gabrielmanricks.com"
516 },
517 {
518 "name": "Andrew Smith",
519 "email": "a.smith@silentworks.co.uk",
520 "homepage": "http://silentworks.co.uk"
521 }
522 ],
523 "description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs",
524 "homepage": "https://slimframework.com",
525 "keywords": [
526 "api",
527 "framework",
528 "micro",
529 "router"
530 ],
531 "time": "2017-03-19T17:55:20+00:00"
532 }
533 ],
534 "packages-dev": [
535 {
536 "name": "doctrine/instantiator",
537 "version": "1.0.5",
538 "source": {
539 "type": "git",
540 "url": "https://github.com/doctrine/instantiator.git",
541 "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d"
542 },
543 "dist": {
544 "type": "zip",
545 "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d",
546 "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d",
547 "shasum": ""
548 },
549 "require": {
550 "php": ">=5.3,<8.0-DEV"
551 },
552 "require-dev": {
553 "athletic/athletic": "~0.1.8",
554 "ext-pdo": "*",
555 "ext-phar": "*",
556 "phpunit/phpunit": "~4.0",
557 "squizlabs/php_codesniffer": "~2.0"
558 },
559 "type": "library",
560 "extra": {
561 "branch-alias": {
562 "dev-master": "1.0.x-dev"
563 }
564 },
565 "autoload": {
566 "psr-4": {
567 "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
568 }
569 },
570 "notification-url": "https://packagist.org/downloads/",
571 "license": [
572 "MIT"
573 ],
574 "authors": [
575 {
576 "name": "Marco Pivetta",
577 "email": "ocramius@gmail.com",
578 "homepage": "http://ocramius.github.com/"
579 }
580 ],
581 "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
582 "homepage": "https://github.com/doctrine/instantiator",
583 "keywords": [
584 "constructor",
585 "instantiate"
586 ],
587 "time": "2015-06-14T21:17:01+00:00"
588 },
589 {
590 "name": "pdepend/pdepend",
591 "version": "2.5.0",
592 "source": {
593 "type": "git",
594 "url": "https://github.com/pdepend/pdepend.git",
595 "reference": "0c50874333149c0dad5a2877801aed148f2767ff"
596 },
597 "dist": {
598 "type": "zip",
599 "url": "https://api.github.com/repos/pdepend/pdepend/zipball/0c50874333149c0dad5a2877801aed148f2767ff",
600 "reference": "0c50874333149c0dad5a2877801aed148f2767ff",
601 "shasum": ""
602 },
603 "require": {
604 "php": ">=5.3.7",
605 "symfony/config": "^2.3.0|^3",
606 "symfony/dependency-injection": "^2.3.0|^3",
607 "symfony/filesystem": "^2.3.0|^3"
608 },
609 "require-dev": {
610 "phpunit/phpunit": "^4.4.0,<4.8",
611 "squizlabs/php_codesniffer": "^2.0.0"
612 },
613 "bin": [
614 "src/bin/pdepend"
615 ],
616 "type": "library",
617 "autoload": {
618 "psr-4": {
619 "PDepend\\": "src/main/php/PDepend"
620 }
621 },
622 "notification-url": "https://packagist.org/downloads/",
623 "license": [
624 "BSD-3-Clause"
625 ],
626 "description": "Official version of pdepend to be handled with Composer",
627 "time": "2017-01-19T14:23:36+00:00"
628 },
629 {
630 "name": "phpdocumentor/reflection-common",
631 "version": "1.0",
632 "source": {
633 "type": "git",
634 "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
635 "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c"
636 },
637 "dist": {
638 "type": "zip",
639 "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c",
640 "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c",
641 "shasum": ""
642 },
643 "require": {
644 "php": ">=5.5"
645 },
646 "require-dev": {
647 "phpunit/phpunit": "^4.6"
648 },
649 "type": "library",
650 "extra": {
651 "branch-alias": {
652 "dev-master": "1.0.x-dev"
653 }
654 },
655 "autoload": {
656 "psr-4": {
657 "phpDocumentor\\Reflection\\": [
658 "src"
659 ]
660 }
661 },
662 "notification-url": "https://packagist.org/downloads/",
663 "license": [
664 "MIT"
665 ],
666 "authors": [
667 {
668 "name": "Jaap van Otterdijk",
669 "email": "opensource@ijaap.nl"
670 }
671 ],
672 "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
673 "homepage": "http://www.phpdoc.org",
674 "keywords": [
675 "FQSEN",
676 "phpDocumentor",
677 "phpdoc",
678 "reflection",
679 "static analysis"
680 ],
681 "time": "2015-12-27T11:43:31+00:00"
682 },
683 {
684 "name": "phpdocumentor/reflection-docblock",
685 "version": "3.1.1",
686 "source": {
687 "type": "git",
688 "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
689 "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e"
690 },
691 "dist": {
692 "type": "zip",
693 "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/8331b5efe816ae05461b7ca1e721c01b46bafb3e",
694 "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e",
695 "shasum": ""
696 },
697 "require": {
698 "php": ">=5.5",
699 "phpdocumentor/reflection-common": "^1.0@dev",
700 "phpdocumentor/type-resolver": "^0.2.0",
701 "webmozart/assert": "^1.0"
702 },
703 "require-dev": {
704 "mockery/mockery": "^0.9.4",
705 "phpunit/phpunit": "^4.4"
706 },
707 "type": "library",
708 "autoload": {
709 "psr-4": {
710 "phpDocumentor\\Reflection\\": [
711 "src/"
712 ]
713 }
714 },
715 "notification-url": "https://packagist.org/downloads/",
716 "license": [
717 "MIT"
718 ],
719 "authors": [
720 {
721 "name": "Mike van Riel",
722 "email": "me@mikevanriel.com"
723 }
724 ],
725 "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
726 "time": "2016-09-30T07:12:33+00:00"
727 },
728 {
729 "name": "phpdocumentor/type-resolver",
730 "version": "0.2.1",
731 "source": {
732 "type": "git",
733 "url": "https://github.com/phpDocumentor/TypeResolver.git",
734 "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb"
735 },
736 "dist": {
737 "type": "zip",
738 "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb",
739 "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb",
740 "shasum": ""
741 },
742 "require": {
743 "php": ">=5.5",
744 "phpdocumentor/reflection-common": "^1.0"
745 },
746 "require-dev": {
747 "mockery/mockery": "^0.9.4",
748 "phpunit/phpunit": "^5.2||^4.8.24"
749 },
750 "type": "library",
751 "extra": {
752 "branch-alias": {
753 "dev-master": "1.0.x-dev"
754 }
755 },
756 "autoload": {
757 "psr-4": {
758 "phpDocumentor\\Reflection\\": [
759 "src/"
760 ]
761 }
762 },
763 "notification-url": "https://packagist.org/downloads/",
764 "license": [
765 "MIT"
766 ],
767 "authors": [
768 {
769 "name": "Mike van Riel",
770 "email": "me@mikevanriel.com"
771 }
772 ],
773 "time": "2016-11-25T06:54:22+00:00"
774 },
775 {
776 "name": "phpmd/phpmd",
777 "version": "2.6.0",
778 "source": {
779 "type": "git",
780 "url": "https://github.com/phpmd/phpmd.git",
781 "reference": "4e9924b2c157a3eb64395460fcf56b31badc8374"
782 },
783 "dist": {
784 "type": "zip",
785 "url": "https://api.github.com/repos/phpmd/phpmd/zipball/4e9924b2c157a3eb64395460fcf56b31badc8374",
786 "reference": "4e9924b2c157a3eb64395460fcf56b31badc8374",
787 "shasum": ""
788 },
789 "require": {
790 "ext-xml": "*",
791 "pdepend/pdepend": "^2.5",
792 "php": ">=5.3.9"
793 },
794 "require-dev": {
795 "phpunit/phpunit": "^4.0",
796 "squizlabs/php_codesniffer": "^2.0"
797 },
798 "bin": [
799 "src/bin/phpmd"
800 ],
801 "type": "project",
802 "autoload": {
803 "psr-0": {
804 "PHPMD\\": "src/main/php"
805 }
806 },
807 "notification-url": "https://packagist.org/downloads/",
808 "license": [
809 "BSD-3-Clause"
810 ],
811 "authors": [
812 {
813 "name": "Manuel Pichler",
814 "email": "github@manuel-pichler.de",
815 "homepage": "https://github.com/manuelpichler",
816 "role": "Project Founder"
817 },
818 {
819 "name": "Other contributors",
820 "homepage": "https://github.com/phpmd/phpmd/graphs/contributors",
821 "role": "Contributors"
822 },
823 {
824 "name": "Marc Würth",
825 "email": "ravage@bluewin.ch",
826 "homepage": "https://github.com/ravage84",
827 "role": "Project Maintainer"
828 }
829 ],
830 "description": "PHPMD is a spin-off project of PHP Depend and aims to be a PHP equivalent of the well known Java tool PMD.",
831 "homepage": "http://phpmd.org/",
832 "keywords": [
833 "mess detection",
834 "mess detector",
835 "pdepend",
836 "phpmd",
837 "pmd"
838 ],
839 "time": "2017-01-20T14:41:10+00:00"
840 },
841 {
842 "name": "phpspec/prophecy",
843 "version": "v1.7.0",
844 "source": {
845 "type": "git",
846 "url": "https://github.com/phpspec/prophecy.git",
847 "reference": "93d39f1f7f9326d746203c7c056f300f7f126073"
848 },
849 "dist": {
850 "type": "zip",
851 "url": "https://api.github.com/repos/phpspec/prophecy/zipball/93d39f1f7f9326d746203c7c056f300f7f126073",
852 "reference": "93d39f1f7f9326d746203c7c056f300f7f126073",
853 "shasum": ""
854 },
855 "require": {
856 "doctrine/instantiator": "^1.0.2",
857 "php": "^5.3|^7.0",
858 "phpdocumentor/reflection-docblock": "^2.0|^3.0.2",
859 "sebastian/comparator": "^1.1|^2.0",
860 "sebastian/recursion-context": "^1.0|^2.0|^3.0"
861 },
862 "require-dev": {
863 "phpspec/phpspec": "^2.5|^3.2",
864 "phpunit/phpunit": "^4.8 || ^5.6.5"
865 },
866 "type": "library",
867 "extra": {
868 "branch-alias": {
869 "dev-master": "1.6.x-dev"
870 }
871 },
872 "autoload": {
873 "psr-0": {
874 "Prophecy\\": "src/"
875 }
876 },
877 "notification-url": "https://packagist.org/downloads/",
878 "license": [
879 "MIT"
880 ],
881 "authors": [
882 {
883 "name": "Konstantin Kudryashov",
884 "email": "ever.zet@gmail.com",
885 "homepage": "http://everzet.com"
886 },
887 {
888 "name": "Marcello Duarte",
889 "email": "marcello.duarte@gmail.com"
890 }
891 ],
892 "description": "Highly opinionated mocking framework for PHP 5.3+",
893 "homepage": "https://github.com/phpspec/prophecy",
894 "keywords": [
895 "Double",
896 "Dummy",
897 "fake",
898 "mock",
899 "spy",
900 "stub"
901 ],
902 "time": "2017-03-02T20:05:34+00:00"
903 },
904 {
905 "name": "phpunit/php-code-coverage",
906 "version": "2.2.4",
907 "source": {
908 "type": "git",
909 "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
910 "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979"
911 },
912 "dist": {
913 "type": "zip",
914 "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979",
915 "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979",
916 "shasum": ""
917 },
918 "require": {
919 "php": ">=5.3.3",
920 "phpunit/php-file-iterator": "~1.3",
921 "phpunit/php-text-template": "~1.2",
922 "phpunit/php-token-stream": "~1.3",
923 "sebastian/environment": "^1.3.2",
924 "sebastian/version": "~1.0"
925 },
926 "require-dev": {
927 "ext-xdebug": ">=2.1.4",
928 "phpunit/phpunit": "~4"
929 },
930 "suggest": {
931 "ext-dom": "*",
932 "ext-xdebug": ">=2.2.1",
933 "ext-xmlwriter": "*"
934 },
935 "type": "library",
936 "extra": {
937 "branch-alias": {
938 "dev-master": "2.2.x-dev"
939 }
940 },
941 "autoload": {
942 "classmap": [
943 "src/"
944 ]
945 },
946 "notification-url": "https://packagist.org/downloads/",
947 "license": [
948 "BSD-3-Clause"
949 ],
950 "authors": [
951 {
952 "name": "Sebastian Bergmann",
953 "email": "sb@sebastian-bergmann.de",
954 "role": "lead"
955 }
956 ],
957 "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
958 "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
959 "keywords": [
960 "coverage",
961 "testing",
962 "xunit"
963 ],
964 "time": "2015-10-06T15:47:00+00:00"
965 },
966 {
967 "name": "phpunit/php-file-iterator",
968 "version": "1.4.2",
969 "source": {
970 "type": "git",
971 "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
972 "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5"
973 },
974 "dist": {
975 "type": "zip",
976 "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3cc8f69b3028d0f96a9078e6295d86e9bf019be5",
977 "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5",
978 "shasum": ""
979 },
980 "require": {
981 "php": ">=5.3.3"
982 },
983 "type": "library",
984 "extra": {
985 "branch-alias": {
986 "dev-master": "1.4.x-dev"
987 }
988 },
989 "autoload": {
990 "classmap": [
991 "src/"
992 ]
993 },
994 "notification-url": "https://packagist.org/downloads/",
995 "license": [
996 "BSD-3-Clause"
997 ],
998 "authors": [
999 {
1000 "name": "Sebastian Bergmann",
1001 "email": "sb@sebastian-bergmann.de",
1002 "role": "lead"
1003 }
1004 ],
1005 "description": "FilterIterator implementation that filters files based on a list of suffixes.",
1006 "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
1007 "keywords": [
1008 "filesystem",
1009 "iterator"
1010 ],
1011 "time": "2016-10-03T07:40:28+00:00"
1012 },
1013 {
1014 "name": "phpunit/php-text-template",
1015 "version": "1.2.1",
1016 "source": {
1017 "type": "git",
1018 "url": "https://github.com/sebastianbergmann/php-text-template.git",
1019 "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
1020 },
1021 "dist": {
1022 "type": "zip",
1023 "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
1024 "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
1025 "shasum": ""
1026 },
1027 "require": {
1028 "php": ">=5.3.3"
1029 },
1030 "type": "library",
1031 "autoload": {
1032 "classmap": [
1033 "src/"
1034 ]
1035 },
1036 "notification-url": "https://packagist.org/downloads/",
1037 "license": [
1038 "BSD-3-Clause"
1039 ],
1040 "authors": [
1041 {
1042 "name": "Sebastian Bergmann",
1043 "email": "sebastian@phpunit.de",
1044 "role": "lead"
1045 }
1046 ],
1047 "description": "Simple template engine.",
1048 "homepage": "https://github.com/sebastianbergmann/php-text-template/",
1049 "keywords": [
1050 "template"
1051 ],
1052 "time": "2015-06-21T13:50:34+00:00"
1053 },
1054 {
1055 "name": "phpunit/php-timer",
1056 "version": "1.0.9",
1057 "source": {
1058 "type": "git",
1059 "url": "https://github.com/sebastianbergmann/php-timer.git",
1060 "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f"
1061 },
1062 "dist": {
1063 "type": "zip",
1064 "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
1065 "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
1066 "shasum": ""
1067 },
1068 "require": {
1069 "php": "^5.3.3 || ^7.0"
1070 },
1071 "require-dev": {
1072 "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
1073 },
1074 "type": "library",
1075 "extra": {
1076 "branch-alias": {
1077 "dev-master": "1.0-dev"
1078 }
1079 },
1080 "autoload": {
1081 "classmap": [
1082 "src/"
1083 ]
1084 },
1085 "notification-url": "https://packagist.org/downloads/",
1086 "license": [
1087 "BSD-3-Clause"
1088 ],
1089 "authors": [
1090 {
1091 "name": "Sebastian Bergmann",
1092 "email": "sb@sebastian-bergmann.de",
1093 "role": "lead"
1094 }
1095 ],
1096 "description": "Utility class for timing",
1097 "homepage": "https://github.com/sebastianbergmann/php-timer/",
1098 "keywords": [
1099 "timer"
1100 ],
1101 "time": "2017-02-26T11:10:40+00:00"
1102 },
1103 {
1104 "name": "phpunit/php-token-stream",
1105 "version": "1.4.11",
1106 "source": {
1107 "type": "git",
1108 "url": "https://github.com/sebastianbergmann/php-token-stream.git",
1109 "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7"
1110 },
1111 "dist": {
1112 "type": "zip",
1113 "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e03f8f67534427a787e21a385a67ec3ca6978ea7",
1114 "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7",
1115 "shasum": ""
1116 },
1117 "require": {
1118 "ext-tokenizer": "*",
1119 "php": ">=5.3.3"
1120 },
1121 "require-dev": {
1122 "phpunit/phpunit": "~4.2"
1123 },
1124 "type": "library",
1125 "extra": {
1126 "branch-alias": {
1127 "dev-master": "1.4-dev"
1128 }
1129 },
1130 "autoload": {
1131 "classmap": [
1132 "src/"
1133 ]
1134 },
1135 "notification-url": "https://packagist.org/downloads/",
1136 "license": [
1137 "BSD-3-Clause"
1138 ],
1139 "authors": [
1140 {
1141 "name": "Sebastian Bergmann",
1142 "email": "sebastian@phpunit.de"
1143 }
1144 ],
1145 "description": "Wrapper around PHP's tokenizer extension.",
1146 "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
1147 "keywords": [
1148 "tokenizer"
1149 ],
1150 "time": "2017-02-27T10:12:30+00:00"
1151 },
1152 {
1153 "name": "phpunit/phpcov",
1154 "version": "2.0.2",
1155 "source": {
1156 "type": "git",
1157 "url": "https://github.com/sebastianbergmann/phpcov.git",
1158 "reference": "9ef291483ff65eefd8639584d61bbfb044d747f3"
1159 },
1160 "dist": {
1161 "type": "zip",
1162 "url": "https://api.github.com/repos/sebastianbergmann/phpcov/zipball/9ef291483ff65eefd8639584d61bbfb044d747f3",
1163 "reference": "9ef291483ff65eefd8639584d61bbfb044d747f3",
1164 "shasum": ""
1165 },
1166 "require": {
1167 "php": ">=5.3.3",
1168 "phpunit/php-code-coverage": "~2.0",
1169 "phpunit/phpunit": ">=4.1",
1170 "sebastian/diff": "~1.1",
1171 "sebastian/finder-facade": "~1.1",
1172 "sebastian/version": "~1.0",
1173 "symfony/console": "~2.2"
1174 },
1175 "bin": [
1176 "phpcov"
1177 ],
1178 "type": "library",
1179 "extra": {
1180 "branch-alias": {
1181 "dev-master": "2.0.x-dev"
1182 }
1183 },
1184 "autoload": {
1185 "classmap": [
1186 "src/"
1187 ]
1188 },
1189 "notification-url": "https://packagist.org/downloads/",
1190 "license": [
1191 "BSD-3-Clause"
1192 ],
1193 "authors": [
1194 {
1195 "name": "Sebastian Bergmann",
1196 "email": "sebastian@phpunit.de",
1197 "role": "lead"
1198 }
1199 ],
1200 "description": "CLI frontend for PHP_CodeCoverage",
1201 "homepage": "https://github.com/sebastianbergmann/phpcov",
1202 "time": "2015-10-05T09:24:23+00:00"
1203 },
1204 {
1205 "name": "phpunit/phpunit",
1206 "version": "4.8.35",
1207 "source": {
1208 "type": "git",
1209 "url": "https://github.com/sebastianbergmann/phpunit.git",
1210 "reference": "791b1a67c25af50e230f841ee7a9c6eba507dc87"
1211 },
1212 "dist": {
1213 "type": "zip",
1214 "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/791b1a67c25af50e230f841ee7a9c6eba507dc87",
1215 "reference": "791b1a67c25af50e230f841ee7a9c6eba507dc87",
1216 "shasum": ""
1217 },
1218 "require": {
1219 "ext-dom": "*",
1220 "ext-json": "*",
1221 "ext-pcre": "*",
1222 "ext-reflection": "*",
1223 "ext-spl": "*",
1224 "php": ">=5.3.3",
1225 "phpspec/prophecy": "^1.3.1",
1226 "phpunit/php-code-coverage": "~2.1",
1227 "phpunit/php-file-iterator": "~1.4",
1228 "phpunit/php-text-template": "~1.2",
1229 "phpunit/php-timer": "^1.0.6",
1230 "phpunit/phpunit-mock-objects": "~2.3",
1231 "sebastian/comparator": "~1.2.2",
1232 "sebastian/diff": "~1.2",
1233 "sebastian/environment": "~1.3",
1234 "sebastian/exporter": "~1.2",
1235 "sebastian/global-state": "~1.0",
1236 "sebastian/version": "~1.0",
1237 "symfony/yaml": "~2.1|~3.0"
1238 },
1239 "suggest": {
1240 "phpunit/php-invoker": "~1.1"
1241 },
1242 "bin": [
1243 "phpunit"
1244 ],
1245 "type": "library",
1246 "extra": {
1247 "branch-alias": {
1248 "dev-master": "4.8.x-dev"
1249 }
1250 },
1251 "autoload": {
1252 "classmap": [
1253 "src/"
1254 ]
1255 },
1256 "notification-url": "https://packagist.org/downloads/",
1257 "license": [
1258 "BSD-3-Clause"
1259 ],
1260 "authors": [
1261 {
1262 "name": "Sebastian Bergmann",
1263 "email": "sebastian@phpunit.de",
1264 "role": "lead"
1265 }
1266 ],
1267 "description": "The PHP Unit Testing framework.",
1268 "homepage": "https://phpunit.de/",
1269 "keywords": [
1270 "phpunit",
1271 "testing",
1272 "xunit"
1273 ],
1274 "time": "2017-02-06T05:18:07+00:00"
1275 },
1276 {
1277 "name": "phpunit/phpunit-mock-objects",
1278 "version": "2.3.8",
1279 "source": {
1280 "type": "git",
1281 "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
1282 "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983"
1283 },
1284 "dist": {
1285 "type": "zip",
1286 "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983",
1287 "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983",
1288 "shasum": ""
1289 },
1290 "require": {
1291 "doctrine/instantiator": "^1.0.2",
1292 "php": ">=5.3.3",
1293 "phpunit/php-text-template": "~1.2",
1294 "sebastian/exporter": "~1.2"
1295 },
1296 "require-dev": {
1297 "phpunit/phpunit": "~4.4"
1298 },
1299 "suggest": {
1300 "ext-soap": "*"
1301 },
1302 "type": "library",
1303 "extra": {
1304 "branch-alias": {
1305 "dev-master": "2.3.x-dev"
1306 }
1307 },
1308 "autoload": {
1309 "classmap": [
1310 "src/"
1311 ]
1312 },
1313 "notification-url": "https://packagist.org/downloads/",
1314 "license": [
1315 "BSD-3-Clause"
1316 ],
1317 "authors": [
1318 {
1319 "name": "Sebastian Bergmann",
1320 "email": "sb@sebastian-bergmann.de",
1321 "role": "lead"
1322 }
1323 ],
1324 "description": "Mock Object library for PHPUnit",
1325 "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
1326 "keywords": [
1327 "mock",
1328 "xunit"
1329 ],
1330 "time": "2015-10-02T06:51:40+00:00"
1331 },
1332 {
1333 "name": "sebastian/comparator",
1334 "version": "1.2.4",
1335 "source": {
1336 "type": "git",
1337 "url": "https://github.com/sebastianbergmann/comparator.git",
1338 "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be"
1339 },
1340 "dist": {
1341 "type": "zip",
1342 "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be",
1343 "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be",
1344 "shasum": ""
1345 },
1346 "require": {
1347 "php": ">=5.3.3",
1348 "sebastian/diff": "~1.2",
1349 "sebastian/exporter": "~1.2 || ~2.0"
1350 },
1351 "require-dev": {
1352 "phpunit/phpunit": "~4.4"
1353 },
1354 "type": "library",
1355 "extra": {
1356 "branch-alias": {
1357 "dev-master": "1.2.x-dev"
1358 }
1359 },
1360 "autoload": {
1361 "classmap": [
1362 "src/"
1363 ]
1364 },
1365 "notification-url": "https://packagist.org/downloads/",
1366 "license": [
1367 "BSD-3-Clause"
1368 ],
1369 "authors": [
1370 {
1371 "name": "Jeff Welch",
1372 "email": "whatthejeff@gmail.com"
1373 },
1374 {
1375 "name": "Volker Dusch",
1376 "email": "github@wallbash.com"
1377 },
1378 {
1379 "name": "Bernhard Schussek",
1380 "email": "bschussek@2bepublished.at"
1381 },
1382 {
1383 "name": "Sebastian Bergmann",
1384 "email": "sebastian@phpunit.de"
1385 }
1386 ],
1387 "description": "Provides the functionality to compare PHP values for equality",
1388 "homepage": "http://www.github.com/sebastianbergmann/comparator",
1389 "keywords": [
1390 "comparator",
1391 "compare",
1392 "equality"
1393 ],
1394 "time": "2017-01-29T09:50:25+00:00"
1395 },
1396 {
1397 "name": "sebastian/diff",
1398 "version": "1.4.1",
1399 "source": {
1400 "type": "git",
1401 "url": "https://github.com/sebastianbergmann/diff.git",
1402 "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e"
1403 },
1404 "dist": {
1405 "type": "zip",
1406 "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e",
1407 "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e",
1408 "shasum": ""
1409 },
1410 "require": {
1411 "php": ">=5.3.3"
1412 },
1413 "require-dev": {
1414 "phpunit/phpunit": "~4.8"
1415 },
1416 "type": "library",
1417 "extra": {
1418 "branch-alias": {
1419 "dev-master": "1.4-dev"
1420 }
1421 },
1422 "autoload": {
1423 "classmap": [
1424 "src/"
1425 ]
1426 },
1427 "notification-url": "https://packagist.org/downloads/",
1428 "license": [
1429 "BSD-3-Clause"
1430 ],
1431 "authors": [
1432 {
1433 "name": "Kore Nordmann",
1434 "email": "mail@kore-nordmann.de"
1435 },
1436 {
1437 "name": "Sebastian Bergmann",
1438 "email": "sebastian@phpunit.de"
1439 }
1440 ],
1441 "description": "Diff implementation",
1442 "homepage": "https://github.com/sebastianbergmann/diff",
1443 "keywords": [
1444 "diff"
1445 ],
1446 "time": "2015-12-08T07:14:41+00:00"
1447 },
1448 {
1449 "name": "sebastian/environment",
1450 "version": "1.3.8",
1451 "source": {
1452 "type": "git",
1453 "url": "https://github.com/sebastianbergmann/environment.git",
1454 "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea"
1455 },
1456 "dist": {
1457 "type": "zip",
1458 "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/be2c607e43ce4c89ecd60e75c6a85c126e754aea",
1459 "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea",
1460 "shasum": ""
1461 },
1462 "require": {
1463 "php": "^5.3.3 || ^7.0"
1464 },
1465 "require-dev": {
1466 "phpunit/phpunit": "^4.8 || ^5.0"
1467 },
1468 "type": "library",
1469 "extra": {
1470 "branch-alias": {
1471 "dev-master": "1.3.x-dev"
1472 }
1473 },
1474 "autoload": {
1475 "classmap": [
1476 "src/"
1477 ]
1478 },
1479 "notification-url": "https://packagist.org/downloads/",
1480 "license": [
1481 "BSD-3-Clause"
1482 ],
1483 "authors": [
1484 {
1485 "name": "Sebastian Bergmann",
1486 "email": "sebastian@phpunit.de"
1487 }
1488 ],
1489 "description": "Provides functionality to handle HHVM/PHP environments",
1490 "homepage": "http://www.github.com/sebastianbergmann/environment",
1491 "keywords": [
1492 "Xdebug",
1493 "environment",
1494 "hhvm"
1495 ],
1496 "time": "2016-08-18T05:49:44+00:00"
1497 },
1498 {
1499 "name": "sebastian/exporter",
1500 "version": "1.2.2",
1501 "source": {
1502 "type": "git",
1503 "url": "https://github.com/sebastianbergmann/exporter.git",
1504 "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4"
1505 },
1506 "dist": {
1507 "type": "zip",
1508 "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4",
1509 "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4",
1510 "shasum": ""
1511 },
1512 "require": {
1513 "php": ">=5.3.3",
1514 "sebastian/recursion-context": "~1.0"
1515 },
1516 "require-dev": {
1517 "ext-mbstring": "*",
1518 "phpunit/phpunit": "~4.4"
1519 },
1520 "type": "library",
1521 "extra": {
1522 "branch-alias": {
1523 "dev-master": "1.3.x-dev"
1524 }
1525 },
1526 "autoload": {
1527 "classmap": [
1528 "src/"
1529 ]
1530 },
1531 "notification-url": "https://packagist.org/downloads/",
1532 "license": [
1533 "BSD-3-Clause"
1534 ],
1535 "authors": [
1536 {
1537 "name": "Jeff Welch",
1538 "email": "whatthejeff@gmail.com"
1539 },
1540 {
1541 "name": "Volker Dusch",
1542 "email": "github@wallbash.com"
1543 },
1544 {
1545 "name": "Bernhard Schussek",
1546 "email": "bschussek@2bepublished.at"
1547 },
1548 {
1549 "name": "Sebastian Bergmann",
1550 "email": "sebastian@phpunit.de"
1551 },
1552 {
1553 "name": "Adam Harvey",
1554 "email": "aharvey@php.net"
1555 }
1556 ],
1557 "description": "Provides the functionality to export PHP variables for visualization",
1558 "homepage": "http://www.github.com/sebastianbergmann/exporter",
1559 "keywords": [
1560 "export",
1561 "exporter"
1562 ],
1563 "time": "2016-06-17T09:04:28+00:00"
1564 },
1565 {
1566 "name": "sebastian/finder-facade",
1567 "version": "1.2.1",
1568 "source": {
1569 "type": "git",
1570 "url": "https://github.com/sebastianbergmann/finder-facade.git",
1571 "reference": "2a6f7f57efc0aa2d23297d9fd9e2a03111a8c0b9"
1572 },
1573 "dist": {
1574 "type": "zip",
1575 "url": "https://api.github.com/repos/sebastianbergmann/finder-facade/zipball/2a6f7f57efc0aa2d23297d9fd9e2a03111a8c0b9",
1576 "reference": "2a6f7f57efc0aa2d23297d9fd9e2a03111a8c0b9",
1577 "shasum": ""
1578 },
1579 "require": {
1580 "symfony/finder": "~2.3|~3.0",
1581 "theseer/fdomdocument": "~1.3"
1582 },
1583 "type": "library",
1584 "autoload": {
1585 "classmap": [
1586 "src/"
1587 ]
1588 },
1589 "notification-url": "https://packagist.org/downloads/",
1590 "license": [
1591 "BSD-3-Clause"
1592 ],
1593 "authors": [
1594 {
1595 "name": "Sebastian Bergmann",
1596 "email": "sebastian@phpunit.de",
1597 "role": "lead"
1598 }
1599 ],
1600 "description": "FinderFacade is a convenience wrapper for Symfony's Finder component.",
1601 "homepage": "https://github.com/sebastianbergmann/finder-facade",
1602 "time": "2016-02-17T07:02:23+00:00"
1603 },
1604 {
1605 "name": "sebastian/global-state",
1606 "version": "1.1.1",
1607 "source": {
1608 "type": "git",
1609 "url": "https://github.com/sebastianbergmann/global-state.git",
1610 "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4"
1611 },
1612 "dist": {
1613 "type": "zip",
1614 "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4",
1615 "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4",
1616 "shasum": ""
1617 },
1618 "require": {
1619 "php": ">=5.3.3"
1620 },
1621 "require-dev": {
1622 "phpunit/phpunit": "~4.2"
1623 },
1624 "suggest": {
1625 "ext-uopz": "*"
1626 },
1627 "type": "library",
1628 "extra": {
1629 "branch-alias": {
1630 "dev-master": "1.0-dev"
1631 }
1632 },
1633 "autoload": {
1634 "classmap": [
1635 "src/"
1636 ]
1637 },
1638 "notification-url": "https://packagist.org/downloads/",
1639 "license": [
1640 "BSD-3-Clause"
1641 ],
1642 "authors": [
1643 {
1644 "name": "Sebastian Bergmann",
1645 "email": "sebastian@phpunit.de"
1646 }
1647 ],
1648 "description": "Snapshotting of global state",
1649 "homepage": "http://www.github.com/sebastianbergmann/global-state",
1650 "keywords": [
1651 "global state"
1652 ],
1653 "time": "2015-10-12T03:26:01+00:00"
1654 },
1655 {
1656 "name": "sebastian/phpcpd",
1657 "version": "2.0.4",
1658 "source": {
1659 "type": "git",
1660 "url": "https://github.com/sebastianbergmann/phpcpd.git",
1661 "reference": "24d9a880deadb0b8c9680e9cfe78e30b704225db"
1662 },
1663 "dist": {
1664 "type": "zip",
1665 "url": "https://api.github.com/repos/sebastianbergmann/phpcpd/zipball/24d9a880deadb0b8c9680e9cfe78e30b704225db",
1666 "reference": "24d9a880deadb0b8c9680e9cfe78e30b704225db",
1667 "shasum": ""
1668 },
1669 "require": {
1670 "php": ">=5.3.3",
1671 "phpunit/php-timer": ">=1.0.6",
1672 "sebastian/finder-facade": "~1.1",
1673 "sebastian/version": "~1.0|~2.0",
1674 "symfony/console": "~2.7|^3.0",
1675 "theseer/fdomdocument": "~1.4"
1676 },
1677 "bin": [
1678 "phpcpd"
1679 ],
1680 "type": "library",
1681 "extra": {
1682 "branch-alias": {
1683 "dev-master": "2.0-dev"
1684 }
1685 },
1686 "autoload": {
1687 "classmap": [
1688 "src/"
1689 ]
1690 },
1691 "notification-url": "https://packagist.org/downloads/",
1692 "license": [
1693 "BSD-3-Clause"
1694 ],
1695 "authors": [
1696 {
1697 "name": "Sebastian Bergmann",
1698 "email": "sebastian@phpunit.de",
1699 "role": "lead"
1700 }
1701 ],
1702 "description": "Copy/Paste Detector (CPD) for PHP code.",
1703 "homepage": "https://github.com/sebastianbergmann/phpcpd",
1704 "time": "2016-04-17T19:32:49+00:00"
1705 },
1706 {
1707 "name": "sebastian/recursion-context",
1708 "version": "1.0.5",
1709 "source": {
1710 "type": "git",
1711 "url": "https://github.com/sebastianbergmann/recursion-context.git",
1712 "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7"
1713 },
1714 "dist": {
1715 "type": "zip",
1716 "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/b19cc3298482a335a95f3016d2f8a6950f0fbcd7",
1717 "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7",
1718 "shasum": ""
1719 },
1720 "require": {
1721 "php": ">=5.3.3"
1722 },
1723 "require-dev": {
1724 "phpunit/phpunit": "~4.4"
1725 },
1726 "type": "library",
1727 "extra": {
1728 "branch-alias": {
1729 "dev-master": "1.0.x-dev"
1730 }
1731 },
1732 "autoload": {
1733 "classmap": [
1734 "src/"
1735 ]
1736 },
1737 "notification-url": "https://packagist.org/downloads/",
1738 "license": [
1739 "BSD-3-Clause"
1740 ],
1741 "authors": [
1742 {
1743 "name": "Jeff Welch",
1744 "email": "whatthejeff@gmail.com"
1745 },
1746 {
1747 "name": "Sebastian Bergmann",
1748 "email": "sebastian@phpunit.de"
1749 },
1750 {
1751 "name": "Adam Harvey",
1752 "email": "aharvey@php.net"
1753 }
1754 ],
1755 "description": "Provides functionality to recursively process PHP variables",
1756 "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
1757 "time": "2016-10-03T07:41:43+00:00"
1758 },
1759 {
1760 "name": "sebastian/version",
1761 "version": "1.0.6",
1762 "source": {
1763 "type": "git",
1764 "url": "https://github.com/sebastianbergmann/version.git",
1765 "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6"
1766 },
1767 "dist": {
1768 "type": "zip",
1769 "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
1770 "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
1771 "shasum": ""
1772 },
1773 "type": "library",
1774 "autoload": {
1775 "classmap": [
1776 "src/"
1777 ]
1778 },
1779 "notification-url": "https://packagist.org/downloads/",
1780 "license": [
1781 "BSD-3-Clause"
1782 ],
1783 "authors": [
1784 {
1785 "name": "Sebastian Bergmann",
1786 "email": "sebastian@phpunit.de",
1787 "role": "lead"
1788 }
1789 ],
1790 "description": "Library that helps with managing the version number of Git-hosted PHP projects",
1791 "homepage": "https://github.com/sebastianbergmann/version",
1792 "time": "2015-06-21T13:59:46+00:00"
1793 },
1794 {
1795 "name": "squizlabs/php_codesniffer",
1796 "version": "2.8.1",
1797 "source": {
1798 "type": "git",
1799 "url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
1800 "reference": "d7cf0d894e8aa4c73712ee4a331cc1eaa37cdc7d"
1801 },
1802 "dist": {
1803 "type": "zip",
1804 "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/d7cf0d894e8aa4c73712ee4a331cc1eaa37cdc7d",
1805 "reference": "d7cf0d894e8aa4c73712ee4a331cc1eaa37cdc7d",
1806 "shasum": ""
1807 },
1808 "require": {
1809 "ext-simplexml": "*",
1810 "ext-tokenizer": "*",
1811 "ext-xmlwriter": "*",
1812 "php": ">=5.1.2"
1813 },
1814 "require-dev": {
1815 "phpunit/phpunit": "~4.0"
1816 },
1817 "bin": [
1818 "scripts/phpcs",
1819 "scripts/phpcbf"
1820 ],
1821 "type": "library",
1822 "extra": {
1823 "branch-alias": {
1824 "dev-master": "2.x-dev"
1825 }
1826 },
1827 "autoload": {
1828 "classmap": [
1829 "CodeSniffer.php",
1830 "CodeSniffer/CLI.php",
1831 "CodeSniffer/Exception.php",
1832 "CodeSniffer/File.php",
1833 "CodeSniffer/Fixer.php",
1834 "CodeSniffer/Report.php",
1835 "CodeSniffer/Reporting.php",
1836 "CodeSniffer/Sniff.php",
1837 "CodeSniffer/Tokens.php",
1838 "CodeSniffer/Reports/",
1839 "CodeSniffer/Tokenizers/",
1840 "CodeSniffer/DocGenerators/",
1841 "CodeSniffer/Standards/AbstractPatternSniff.php",
1842 "CodeSniffer/Standards/AbstractScopeSniff.php",
1843 "CodeSniffer/Standards/AbstractVariableSniff.php",
1844 "CodeSniffer/Standards/IncorrectPatternException.php",
1845 "CodeSniffer/Standards/Generic/Sniffs/",
1846 "CodeSniffer/Standards/MySource/Sniffs/",
1847 "CodeSniffer/Standards/PEAR/Sniffs/",
1848 "CodeSniffer/Standards/PSR1/Sniffs/",
1849 "CodeSniffer/Standards/PSR2/Sniffs/",
1850 "CodeSniffer/Standards/Squiz/Sniffs/",
1851 "CodeSniffer/Standards/Zend/Sniffs/"
1852 ]
1853 },
1854 "notification-url": "https://packagist.org/downloads/",
1855 "license": [
1856 "BSD-3-Clause"
1857 ],
1858 "authors": [
1859 {
1860 "name": "Greg Sherwood",
1861 "role": "lead"
1862 }
1863 ],
1864 "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
1865 "homepage": "http://www.squizlabs.com/php-codesniffer",
1866 "keywords": [
1867 "phpcs",
1868 "standards"
1869 ],
1870 "time": "2017-03-01T22:17:45+00:00"
1871 },
1872 {
1873 "name": "symfony/config",
1874 "version": "v3.2.6",
1875 "source": {
1876 "type": "git",
1877 "url": "https://github.com/symfony/config.git",
1878 "reference": "741d6d4cd1414d67d48eb71aba6072b46ba740c2"
1879 },
1880 "dist": {
1881 "type": "zip",
1882 "url": "https://api.github.com/repos/symfony/config/zipball/741d6d4cd1414d67d48eb71aba6072b46ba740c2",
1883 "reference": "741d6d4cd1414d67d48eb71aba6072b46ba740c2",
1884 "shasum": ""
1885 },
1886 "require": {
1887 "php": ">=5.5.9",
1888 "symfony/filesystem": "~2.8|~3.0"
1889 },
1890 "require-dev": {
1891 "symfony/yaml": "~3.0"
1892 },
1893 "suggest": {
1894 "symfony/yaml": "To use the yaml reference dumper"
1895 },
1896 "type": "library",
1897 "extra": {
1898 "branch-alias": {
1899 "dev-master": "3.2-dev"
1900 }
1901 },
1902 "autoload": {
1903 "psr-4": {
1904 "Symfony\\Component\\Config\\": ""
1905 },
1906 "exclude-from-classmap": [
1907 "/Tests/"
1908 ]
1909 },
1910 "notification-url": "https://packagist.org/downloads/",
1911 "license": [
1912 "MIT"
1913 ],
1914 "authors": [
1915 {
1916 "name": "Fabien Potencier",
1917 "email": "fabien@symfony.com"
1918 },
1919 {
1920 "name": "Symfony Community",
1921 "homepage": "https://symfony.com/contributors"
1922 }
1923 ],
1924 "description": "Symfony Config Component",
1925 "homepage": "https://symfony.com",
1926 "time": "2017-03-01T18:18:25+00:00"
1927 },
1928 {
1929 "name": "symfony/console",
1930 "version": "v2.8.18",
1931 "source": {
1932 "type": "git",
1933 "url": "https://github.com/symfony/console.git",
1934 "reference": "81508e6fac4476771275a3f4f53c3fee9b956bfa"
1935 },
1936 "dist": {
1937 "type": "zip",
1938 "url": "https://api.github.com/repos/symfony/console/zipball/81508e6fac4476771275a3f4f53c3fee9b956bfa",
1939 "reference": "81508e6fac4476771275a3f4f53c3fee9b956bfa",
1940 "shasum": ""
1941 },
1942 "require": {
1943 "php": ">=5.3.9",
1944 "symfony/debug": "^2.7.2|~3.0.0",
1945 "symfony/polyfill-mbstring": "~1.0"
1946 },
1947 "require-dev": {
1948 "psr/log": "~1.0",
1949 "symfony/event-dispatcher": "~2.1|~3.0.0",
1950 "symfony/process": "~2.1|~3.0.0"
1951 },
1952 "suggest": {
1953 "psr/log": "For using the console logger",
1954 "symfony/event-dispatcher": "",
1955 "symfony/process": ""
1956 },
1957 "type": "library",
1958 "extra": {
1959 "branch-alias": {
1960 "dev-master": "2.8-dev"
1961 }
1962 },
1963 "autoload": {
1964 "psr-4": {
1965 "Symfony\\Component\\Console\\": ""
1966 },
1967 "exclude-from-classmap": [
1968 "/Tests/"
1969 ]
1970 },
1971 "notification-url": "https://packagist.org/downloads/",
1972 "license": [
1973 "MIT"
1974 ],
1975 "authors": [
1976 {
1977 "name": "Fabien Potencier",
1978 "email": "fabien@symfony.com"
1979 },
1980 {
1981 "name": "Symfony Community",
1982 "homepage": "https://symfony.com/contributors"
1983 }
1984 ],
1985 "description": "Symfony Console Component",
1986 "homepage": "https://symfony.com",
1987 "time": "2017-03-04T11:00:12+00:00"
1988 },
1989 {
1990 "name": "symfony/debug",
1991 "version": "v3.0.9",
1992 "source": {
1993 "type": "git",
1994 "url": "https://github.com/symfony/debug.git",
1995 "reference": "697c527acd9ea1b2d3efac34d9806bf255278b0a"
1996 },
1997 "dist": {
1998 "type": "zip",
1999 "url": "https://api.github.com/repos/symfony/debug/zipball/697c527acd9ea1b2d3efac34d9806bf255278b0a",
2000 "reference": "697c527acd9ea1b2d3efac34d9806bf255278b0a",
2001 "shasum": ""
2002 },
2003 "require": {
2004 "php": ">=5.5.9",
2005 "psr/log": "~1.0"
2006 },
2007 "conflict": {
2008 "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2"
2009 },
2010 "require-dev": {
2011 "symfony/class-loader": "~2.8|~3.0",
2012 "symfony/http-kernel": "~2.8|~3.0"
2013 },
2014 "type": "library",
2015 "extra": {
2016 "branch-alias": {
2017 "dev-master": "3.0-dev"
2018 }
2019 },
2020 "autoload": {
2021 "psr-4": {
2022 "Symfony\\Component\\Debug\\": ""
2023 },
2024 "exclude-from-classmap": [
2025 "/Tests/"
2026 ]
2027 },
2028 "notification-url": "https://packagist.org/downloads/",
2029 "license": [
2030 "MIT"
2031 ],
2032 "authors": [
2033 {
2034 "name": "Fabien Potencier",
2035 "email": "fabien@symfony.com"
2036 },
2037 {
2038 "name": "Symfony Community",
2039 "homepage": "https://symfony.com/contributors"
2040 }
2041 ],
2042 "description": "Symfony Debug Component",
2043 "homepage": "https://symfony.com",
2044 "time": "2016-07-30T07:22:48+00:00"
2045 },
2046 {
2047 "name": "symfony/dependency-injection",
2048 "version": "v3.2.6",
2049 "source": {
2050 "type": "git",
2051 "url": "https://github.com/symfony/dependency-injection.git",
2052 "reference": "74e0935e414ad33d5e82074212c0eedb4681a691"
2053 },
2054 "dist": {
2055 "type": "zip",
2056 "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/74e0935e414ad33d5e82074212c0eedb4681a691",
2057 "reference": "74e0935e414ad33d5e82074212c0eedb4681a691",
2058 "shasum": ""
2059 },
2060 "require": {
2061 "php": ">=5.5.9"
2062 },
2063 "conflict": {
2064 "symfony/yaml": "<3.2"
2065 },
2066 "require-dev": {
2067 "symfony/config": "~2.8|~3.0",
2068 "symfony/expression-language": "~2.8|~3.0",
2069 "symfony/yaml": "~3.2"
2070 },
2071 "suggest": {
2072 "symfony/config": "",
2073 "symfony/expression-language": "For using expressions in service container configuration",
2074 "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them",
2075 "symfony/yaml": ""
2076 },
2077 "type": "library",
2078 "extra": {
2079 "branch-alias": {
2080 "dev-master": "3.2-dev"
2081 }
2082 },
2083 "autoload": {
2084 "psr-4": {
2085 "Symfony\\Component\\DependencyInjection\\": ""
2086 },
2087 "exclude-from-classmap": [
2088 "/Tests/"
2089 ]
2090 },
2091 "notification-url": "https://packagist.org/downloads/",
2092 "license": [
2093 "MIT"
2094 ],
2095 "authors": [
2096 {
2097 "name": "Fabien Potencier",
2098 "email": "fabien@symfony.com"
2099 },
2100 {
2101 "name": "Symfony Community",
2102 "homepage": "https://symfony.com/contributors"
2103 }
2104 ],
2105 "description": "Symfony DependencyInjection Component",
2106 "homepage": "https://symfony.com",
2107 "time": "2017-03-05T00:06:55+00:00"
2108 },
2109 {
2110 "name": "symfony/filesystem",
2111 "version": "v3.2.6",
2112 "source": {
2113 "type": "git",
2114 "url": "https://github.com/symfony/filesystem.git",
2115 "reference": "bc0f17bed914df2cceb989972c3b996043c4da4a"
2116 },
2117 "dist": {
2118 "type": "zip",
2119 "url": "https://api.github.com/repos/symfony/filesystem/zipball/bc0f17bed914df2cceb989972c3b996043c4da4a",
2120 "reference": "bc0f17bed914df2cceb989972c3b996043c4da4a",
2121 "shasum": ""
2122 },
2123 "require": {
2124 "php": ">=5.5.9"
2125 },
2126 "type": "library",
2127 "extra": {
2128 "branch-alias": {
2129 "dev-master": "3.2-dev"
2130 }
2131 },
2132 "autoload": {
2133 "psr-4": {
2134 "Symfony\\Component\\Filesystem\\": ""
2135 },
2136 "exclude-from-classmap": [
2137 "/Tests/"
2138 ]
2139 },
2140 "notification-url": "https://packagist.org/downloads/",
2141 "license": [
2142 "MIT"
2143 ],
2144 "authors": [
2145 {
2146 "name": "Fabien Potencier",
2147 "email": "fabien@symfony.com"
2148 },
2149 {
2150 "name": "Symfony Community",
2151 "homepage": "https://symfony.com/contributors"
2152 }
2153 ],
2154 "description": "Symfony Filesystem Component",
2155 "homepage": "https://symfony.com",
2156 "time": "2017-03-06T19:30:27+00:00"
2157 },
2158 {
2159 "name": "symfony/finder",
2160 "version": "v3.2.6",
2161 "source": {
2162 "type": "git",
2163 "url": "https://github.com/symfony/finder.git",
2164 "reference": "92d7476d2df60cd851a3e13e078664b1deb8ce10"
2165 },
2166 "dist": {
2167 "type": "zip",
2168 "url": "https://api.github.com/repos/symfony/finder/zipball/92d7476d2df60cd851a3e13e078664b1deb8ce10",
2169 "reference": "92d7476d2df60cd851a3e13e078664b1deb8ce10",
2170 "shasum": ""
2171 },
2172 "require": {
2173 "php": ">=5.5.9"
2174 },
2175 "type": "library",
2176 "extra": {
2177 "branch-alias": {
2178 "dev-master": "3.2-dev"
2179 }
2180 },
2181 "autoload": {
2182 "psr-4": {
2183 "Symfony\\Component\\Finder\\": ""
2184 },
2185 "exclude-from-classmap": [
2186 "/Tests/"
2187 ]
2188 },
2189 "notification-url": "https://packagist.org/downloads/",
2190 "license": [
2191 "MIT"
2192 ],
2193 "authors": [
2194 {
2195 "name": "Fabien Potencier",
2196 "email": "fabien@symfony.com"
2197 },
2198 {
2199 "name": "Symfony Community",
2200 "homepage": "https://symfony.com/contributors"
2201 }
2202 ],
2203 "description": "Symfony Finder Component",
2204 "homepage": "https://symfony.com",
2205 "time": "2017-02-21T09:12:04+00:00"
2206 },
2207 {
2208 "name": "symfony/polyfill-mbstring",
2209 "version": "v1.3.0",
2210 "source": {
2211 "type": "git",
2212 "url": "https://github.com/symfony/polyfill-mbstring.git",
2213 "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4"
2214 },
2215 "dist": {
2216 "type": "zip",
2217 "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/e79d363049d1c2128f133a2667e4f4190904f7f4",
2218 "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4",
2219 "shasum": ""
2220 },
2221 "require": {
2222 "php": ">=5.3.3"
2223 },
2224 "suggest": {
2225 "ext-mbstring": "For best performance"
2226 },
2227 "type": "library",
2228 "extra": {
2229 "branch-alias": {
2230 "dev-master": "1.3-dev"
2231 }
2232 },
2233 "autoload": {
2234 "psr-4": {
2235 "Symfony\\Polyfill\\Mbstring\\": ""
2236 },
2237 "files": [
2238 "bootstrap.php"
2239 ]
2240 },
2241 "notification-url": "https://packagist.org/downloads/",
2242 "license": [
2243 "MIT"
2244 ],
2245 "authors": [
2246 {
2247 "name": "Nicolas Grekas",
2248 "email": "p@tchwork.com"
2249 },
2250 {
2251 "name": "Symfony Community",
2252 "homepage": "https://symfony.com/contributors"
2253 }
2254 ],
2255 "description": "Symfony polyfill for the Mbstring extension",
2256 "homepage": "https://symfony.com",
2257 "keywords": [
2258 "compatibility",
2259 "mbstring",
2260 "polyfill",
2261 "portable",
2262 "shim"
2263 ],
2264 "time": "2016-11-14T01:06:16+00:00"
2265 },
2266 {
2267 "name": "symfony/yaml",
2268 "version": "v3.2.6",
2269 "source": {
2270 "type": "git",
2271 "url": "https://github.com/symfony/yaml.git",
2272 "reference": "093e416ad096355149e265ea2e4cc1f9ee40ab1a"
2273 },
2274 "dist": {
2275 "type": "zip",
2276 "url": "https://api.github.com/repos/symfony/yaml/zipball/093e416ad096355149e265ea2e4cc1f9ee40ab1a",
2277 "reference": "093e416ad096355149e265ea2e4cc1f9ee40ab1a",
2278 "shasum": ""
2279 },
2280 "require": {
2281 "php": ">=5.5.9"
2282 },
2283 "require-dev": {
2284 "symfony/console": "~2.8|~3.0"
2285 },
2286 "suggest": {
2287 "symfony/console": "For validating YAML files using the lint command"
2288 },
2289 "type": "library",
2290 "extra": {
2291 "branch-alias": {
2292 "dev-master": "3.2-dev"
2293 }
2294 },
2295 "autoload": {
2296 "psr-4": {
2297 "Symfony\\Component\\Yaml\\": ""
2298 },
2299 "exclude-from-classmap": [
2300 "/Tests/"
2301 ]
2302 },
2303 "notification-url": "https://packagist.org/downloads/",
2304 "license": [
2305 "MIT"
2306 ],
2307 "authors": [
2308 {
2309 "name": "Fabien Potencier",
2310 "email": "fabien@symfony.com"
2311 },
2312 {
2313 "name": "Symfony Community",
2314 "homepage": "https://symfony.com/contributors"
2315 }
2316 ],
2317 "description": "Symfony Yaml Component",
2318 "homepage": "https://symfony.com",
2319 "time": "2017-03-07T16:47:02+00:00"
2320 },
2321 {
2322 "name": "theseer/fdomdocument",
2323 "version": "1.6.1",
2324 "source": {
2325 "type": "git",
2326 "url": "https://github.com/theseer/fDOMDocument.git",
2327 "reference": "d9ad139d6c2e8edf5e313ffbe37ff13344cf0684"
2328 },
2329 "dist": {
2330 "type": "zip",
2331 "url": "https://api.github.com/repos/theseer/fDOMDocument/zipball/d9ad139d6c2e8edf5e313ffbe37ff13344cf0684",
2332 "reference": "d9ad139d6c2e8edf5e313ffbe37ff13344cf0684",
2333 "shasum": ""
2334 },
2335 "require": {
2336 "ext-dom": "*",
2337 "lib-libxml": "*",
2338 "php": ">=5.3.3"
2339 },
2340 "type": "library",
2341 "autoload": {
2342 "classmap": [
2343 "src/"
2344 ]
2345 },
2346 "notification-url": "https://packagist.org/downloads/",
2347 "license": [
2348 "BSD-3-Clause"
2349 ],
2350 "authors": [
2351 {
2352 "name": "Arne Blankerts",
2353 "email": "arne@blankerts.de",
2354 "role": "lead"
2355 }
2356 ],
2357 "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.",
2358 "homepage": "https://github.com/theseer/fDOMDocument",
2359 "time": "2015-05-27T22:58:02+00:00"
2360 },
2361 {
2362 "name": "webmozart/assert",
2363 "version": "1.2.0",
2364 "source": {
2365 "type": "git",
2366 "url": "https://github.com/webmozart/assert.git",
2367 "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f"
2368 },
2369 "dist": {
2370 "type": "zip",
2371 "url": "https://api.github.com/repos/webmozart/assert/zipball/2db61e59ff05fe5126d152bd0655c9ea113e550f",
2372 "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f",
2373 "shasum": ""
2374 },
2375 "require": {
2376 "php": "^5.3.3 || ^7.0"
2377 },
2378 "require-dev": {
2379 "phpunit/phpunit": "^4.6",
2380 "sebastian/version": "^1.0.1"
2381 },
2382 "type": "library",
2383 "extra": {
2384 "branch-alias": {
2385 "dev-master": "1.3-dev"
2386 }
2387 },
2388 "autoload": {
2389 "psr-4": {
2390 "Webmozart\\Assert\\": "src/"
2391 }
2392 },
2393 "notification-url": "https://packagist.org/downloads/",
2394 "license": [
2395 "MIT"
2396 ],
2397 "authors": [
2398 {
2399 "name": "Bernhard Schussek",
2400 "email": "bschussek@gmail.com"
2401 }
2402 ],
2403 "description": "Assertions to validate method input/output with nice error messages.",
2404 "keywords": [
2405 "assert",
2406 "check",
2407 "validate"
2408 ],
2409 "time": "2016-11-23T20:04:58+00:00"
2410 }
2411 ],
2412 "aliases": [],
2413 "minimum-stability": "stable",
2414 "stability-flags": {
2415 "pubsubhubbub/publisher": 20,
2416 "phpmd/phpmd": 0
2417 },
2418 "prefer-stable": false,
2419 "prefer-lowest": false,
2420 "platform": {
2421 "php": ">=5.5"
2422 },
2423 "platform-dev": []
2424}
diff --git a/index.php b/index.php
index 7f357c69..76aa1ae0 100644
--- a/index.php
+++ b/index.php
@@ -1,8 +1,6 @@
1<?php 1<?php
2/** 2/**
3 * Shaarli v0.8.3 - Shaare your links... 3 * Shaarli - The personal, minimalist, super-fast, database free, bookmarking service.
4 *
5 * The personal, minimalist, super-fast, database free, bookmarking service.
6 * 4 *
7 * Friendly fork by the Shaarli community: 5 * Friendly fork by the Shaarli community:
8 * - https://github.com/shaarli/Shaarli 6 * - https://github.com/shaarli/Shaarli
@@ -25,7 +23,6 @@ if (date_default_timezone_get() == '') {
25/* 23/*
26 * PHP configuration 24 * PHP configuration
27 */ 25 */
28define('shaarli_version', '0.8.2');
29 26
30// http://server.com/x/shaarli --> /shaarli/ 27// http://server.com/x/shaarli --> /shaarli/
31define('WEB_PATH', substr($_SERVER['REQUEST_URI'], 0, 1+strrpos($_SERVER['REQUEST_URI'], '/', 0))); 28define('WEB_PATH', substr($_SERVER['REQUEST_URI'], 0, 1+strrpos($_SERVER['REQUEST_URI'], '/', 0)));
@@ -91,6 +88,8 @@ try {
91 exit; 88 exit;
92} 89}
93 90
91define('shaarli_version', ApplicationUtils::getVersion(__DIR__ .'/'. ApplicationUtils::$VERSION_FILE));
92
94// Force cookie path (but do not change lifetime) 93// Force cookie path (but do not change lifetime)
95$cookie = session_get_cookie_params(); 94$cookie = session_get_cookie_params();
96$cookiedir = ''; 95$cookiedir = '';
@@ -434,7 +433,7 @@ if (isset($_POST['login']))
434 // Optional redirect after login: 433 // Optional redirect after login:
435 if (isset($_GET['post'])) { 434 if (isset($_GET['post'])) {
436 $uri = '?post='. urlencode($_GET['post']); 435 $uri = '?post='. urlencode($_GET['post']);
437 foreach (array('description', 'source', 'title') as $param) { 436 foreach (array('description', 'source', 'title', 'tags') as $param) {
438 if (!empty($_GET[$param])) { 437 if (!empty($_GET[$param])) {
439 $uri .= '&'.$param.'='.urlencode($_GET[$param]); 438 $uri .= '&'.$param.'='.urlencode($_GET[$param]);
440 } 439 }
@@ -463,7 +462,7 @@ if (isset($_POST['login']))
463 $redir = '&username='. $_POST['login']; 462 $redir = '&username='. $_POST['login'];
464 if (isset($_GET['post'])) { 463 if (isset($_GET['post'])) {
465 $redir .= '&post=' . urlencode($_GET['post']); 464 $redir .= '&post=' . urlencode($_GET['post']);
466 foreach (array('description', 'source', 'title') as $param) { 465 foreach (array('description', 'source', 'title', 'tags') as $param) {
467 if (!empty($_GET[$param])) { 466 if (!empty($_GET[$param])) {
468 $redir .= '&' . $param . '=' . urlencode($_GET[$param]); 467 $redir .= '&' . $param . '=' . urlencode($_GET[$param]);
469 } 468 }
@@ -475,34 +474,6 @@ if (isset($_POST['login']))
475} 474}
476 475
477// ------------------------------------------------------------------------------------------ 476// ------------------------------------------------------------------------------------------
478// Misc utility functions:
479
480// Convert post_max_size/upload_max_filesize (e.g. '16M') parameters to bytes.
481function return_bytes($val)
482{
483 $val = trim($val); $last=strtolower($val[strlen($val)-1]);
484 switch($last)
485 {
486 case 'g': $val *= 1024;
487 case 'm': $val *= 1024;
488 case 'k': $val *= 1024;
489 }
490 return $val;
491}
492
493// Try to determine max file size for uploads (POST).
494// Returns an integer (in bytes)
495function getMaxFileSize()
496{
497 $size1 = return_bytes(ini_get('post_max_size'));
498 $size2 = return_bytes(ini_get('upload_max_filesize'));
499 // Return the smaller of two:
500 $maxsize = min($size1,$size2);
501 // FIXME: Then convert back to readable notations ? (e.g. 2M instead of 2000000)
502 return $maxsize;
503}
504
505// ------------------------------------------------------------------------------------------
506// Token management for XSRF protection 477// Token management for XSRF protection
507// Token should be used in any form which acts on data (create,update,delete,import...). 478// Token should be used in any form which acts on data (create,update,delete,import...).
508if (!isset($_SESSION['tokens'])) $_SESSION['tokens']=array(); // Token are attached to the session. 479if (!isset($_SESSION['tokens'])) $_SESSION['tokens']=array(); // Token are attached to the session.
@@ -697,9 +668,11 @@ function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager)
697 668
698 $dayDate = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $day.'_000000'); 669 $dayDate = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $day.'_000000');
699 $data = array( 670 $data = array(
671 'pagetitle' => $conf->get('general.title') .' - '. format_date($dayDate, false),
700 'linksToDisplay' => $linksToDisplay, 672 'linksToDisplay' => $linksToDisplay,
701 'cols' => $columns, 673 'cols' => $columns,
702 'day' => $dayDate->getTimestamp(), 674 'day' => $dayDate->getTimestamp(),
675 'dayDate' => $dayDate,
703 'previousday' => $previousday, 676 'previousday' => $previousday,
704 'nextday' => $nextday, 677 'nextday' => $nextday,
705 ); 678 );
@@ -1052,7 +1025,13 @@ function renderPage($conf, $pluginManager, $LINKSDB)
1052 // Show login screen, then redirect to ?post=... 1025 // Show login screen, then redirect to ?post=...
1053 if (isset($_GET['post'])) 1026 if (isset($_GET['post']))
1054 { 1027 {
1055 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. 1028 header( // Redirect to login page, then back to post link.
1029 'Location: ?do=login&post='.urlencode($_GET['post']).
1030 (!empty($_GET['title'])?'&title='.urlencode($_GET['title']):'').
1031 (!empty($_GET['description'])?'&description='.urlencode($_GET['description']):'').
1032 (!empty($_GET['tags'])?'&tags='.urlencode($_GET['tags']):'').
1033 (!empty($_GET['source'])?'&source='.urlencode($_GET['source']):'')
1034 );
1056 exit; 1035 exit;
1057 } 1036 }
1058 1037
@@ -1149,7 +1128,7 @@ function renderPage($conf, $pluginManager, $LINKSDB)
1149 $conf->set('feed.rss_permalinks', !empty($_POST['enableRssPermalinks'])); 1128 $conf->set('feed.rss_permalinks', !empty($_POST['enableRssPermalinks']));
1150 $conf->set('updates.check_updates', !empty($_POST['updateCheck'])); 1129 $conf->set('updates.check_updates', !empty($_POST['updateCheck']));
1151 $conf->set('privacy.hide_public_links', !empty($_POST['hidePublicLinks'])); 1130 $conf->set('privacy.hide_public_links', !empty($_POST['hidePublicLinks']));
1152 $conf->set('api.enabled', !empty($_POST['apiEnabled'])); 1131 $conf->set('api.enabled', !empty($_POST['enableApi']));
1153 $conf->set('api.secret', escape($_POST['apiSecret'])); 1132 $conf->set('api.secret', escape($_POST['apiSecret']));
1154 try { 1133 try {
1155 $conf->write(isLoggedIn()); 1134 $conf->write(isLoggedIn());
@@ -1175,9 +1154,12 @@ function renderPage($conf, $pluginManager, $LINKSDB)
1175 $PAGE->assign('theme', $conf->get('resource.theme')); 1154 $PAGE->assign('theme', $conf->get('resource.theme'));
1176 $PAGE->assign('theme_available', ThemeUtils::getThemes($conf->get('resource.raintpl_tpl'))); 1155 $PAGE->assign('theme_available', ThemeUtils::getThemes($conf->get('resource.raintpl_tpl')));
1177 $PAGE->assign('redirector', $conf->get('redirector.url')); 1156 $PAGE->assign('redirector', $conf->get('redirector.url'));
1178 list($timezone_form, $timezone_js) = generateTimeZoneForm($conf->get('general.timezone')); 1157 list($continents, $cities) = generateTimeZoneData(
1179 $PAGE->assign('timezone_form', $timezone_form); 1158 timezone_identifiers_list(),
1180 $PAGE->assign('timezone_js',$timezone_js); 1159 $conf->get('general.timezone')
1160 );
1161 $PAGE->assign('continents', $continents);
1162 $PAGE->assign('cities', $cities);
1181 $PAGE->assign('private_links_default', $conf->get('privacy.default_private_links', false)); 1163 $PAGE->assign('private_links_default', $conf->get('privacy.default_private_links', false));
1182 $PAGE->assign('session_protection_disabled', $conf->get('security.session_protection_disabled', false)); 1164 $PAGE->assign('session_protection_disabled', $conf->get('security.session_protection_disabled', false));
1183 $PAGE->assign('enable_rss_permalinks', $conf->get('feed.rss_permalinks', false)); 1165 $PAGE->assign('enable_rss_permalinks', $conf->get('feed.rss_permalinks', false));
@@ -1257,7 +1239,7 @@ function renderPage($conf, $pluginManager, $LINKSDB)
1257 } 1239 }
1258 1240
1259 // lf_id should only be present if the link exists. 1241 // lf_id should only be present if the link exists.
1260 $id = !empty($_POST['lf_id']) ? intval(escape($_POST['lf_id'])) : $LINKSDB->getNextId(); 1242 $id = isset($_POST['lf_id']) ? intval(escape($_POST['lf_id'])) : $LINKSDB->getNextId();
1261 // Linkdate is kept here to: 1243 // Linkdate is kept here to:
1262 // - use the same permalink for notes as they're displayed when creating them 1244 // - use the same permalink for notes as they're displayed when creating them
1263 // - let users hack creation date of their posts 1245 // - let users hack creation date of their posts
@@ -1337,9 +1319,13 @@ function renderPage($conf, $pluginManager, $LINKSDB)
1337 // -------- User clicked the "Cancel" button when editing a link. 1319 // -------- User clicked the "Cancel" button when editing a link.
1338 if (isset($_POST['cancel_edit'])) 1320 if (isset($_POST['cancel_edit']))
1339 { 1321 {
1322 $id = isset($_POST['lf_id']) ? (int) escape($_POST['lf_id']) : false;
1323 if (! isset($LINKSDB[$id])) {
1324 header('Location: ?');
1325 }
1340 // If we are called from the bookmarklet, we must close the popup: 1326 // If we are called from the bookmarklet, we must close the popup:
1341 if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { echo '<script>self.close();</script>'; exit; } 1327 if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { echo '<script>self.close();</script>'; exit; }
1342 $link = $LINKSDB[(int) escape($_POST['lf_id'])]; 1328 $link = $LINKSDB[$id];
1343 $returnurl = ( isset($_POST['returnurl']) ? $_POST['returnurl'] : '?' ); 1329 $returnurl = ( isset($_POST['returnurl']) ? $_POST['returnurl'] : '?' );
1344 // Scroll to the link which has been edited. 1330 // Scroll to the link which has been edited.
1345 $returnurl .= '#'. $link['shorturl']; 1331 $returnurl .= '#'. $link['shorturl'];
@@ -1525,7 +1511,22 @@ function renderPage($conf, $pluginManager, $LINKSDB)
1525 1511
1526 if (! isset($_POST['token']) || ! isset($_FILES['filetoupload'])) { 1512 if (! isset($_POST['token']) || ! isset($_FILES['filetoupload'])) {
1527 // Show import dialog 1513 // Show import dialog
1528 $PAGE->assign('maxfilesize', getMaxFileSize()); 1514 $PAGE->assign(
1515 'maxfilesize',
1516 get_max_upload_size(
1517 ini_get('post_max_size'),
1518 ini_get('upload_max_filesize'),
1519 false
1520 )
1521 );
1522 $PAGE->assign(
1523 'maxfilesizeHuman',
1524 get_max_upload_size(
1525 ini_get('post_max_size'),
1526 ini_get('upload_max_filesize'),
1527 true
1528 )
1529 );
1529 $PAGE->renderPage('import'); 1530 $PAGE->renderPage('import');
1530 exit; 1531 exit;
1531 } 1532 }
@@ -1535,7 +1536,7 @@ function renderPage($conf, $pluginManager, $LINKSDB)
1535 // The file is too big or some form field may be missing. 1536 // The file is too big or some form field may be missing.
1536 echo '<script>alert("The file you are trying to upload is probably' 1537 echo '<script>alert("The file you are trying to upload is probably'
1537 .' bigger than what this webserver can accept (' 1538 .' bigger than what this webserver can accept ('
1538 .getMaxFileSize().' bytes).' 1539 .get_max_upload_size(ini_get('post_max_size'), ini_get('upload_max_filesize')).').'
1539 .' Please upload in smaller chunks.");document.location=\'?do=' 1540 .' Please upload in smaller chunks.");document.location=\'?do='
1540 .Router::$PAGE_IMPORT .'\';</script>'; 1541 .Router::$PAGE_IMPORT .'\';</script>';
1541 exit; 1542 exit;
@@ -1992,16 +1993,10 @@ function install($conf)
1992 exit; 1993 exit;
1993 } 1994 }
1994 1995
1995 // Display config form:
1996 list($timezone_form, $timezone_js) = generateTimeZoneForm();
1997 $timezone_html = '';
1998 if ($timezone_form != '') {
1999 $timezone_html = '<tr><td><b>Timezone:</b></td><td>'.$timezone_form.'</td></tr>';
2000 }
2001
2002 $PAGE = new PageBuilder($conf); 1996 $PAGE = new PageBuilder($conf);
2003 $PAGE->assign('timezone_html',$timezone_html); 1997 list($continents, $cities) = generateTimeZoneData(timezone_identifiers_list(), date_default_timezone_get());
2004 $PAGE->assign('timezone_js',$timezone_js); 1998 $PAGE->assign('continents', $continents);
1999 $PAGE->assign('cities', $cities);
2005 $PAGE->renderPage('install'); 2000 $PAGE->renderPage('install');
2006 exit; 2001 exit;
2007} 2002}
@@ -2252,9 +2247,10 @@ $app = new \Slim\App($container);
2252 2247
2253// REST API routes 2248// REST API routes
2254$app->group('/api/v1', function() { 2249$app->group('/api/v1', function() {
2255 $this->get('/info', '\Shaarli\Api\Controllers\Info:getInfo'); 2250 $this->get('/info', '\Shaarli\Api\Controllers\Info:getInfo')->setName('getInfo');
2256 $this->get('/links', '\Shaarli\Api\Controllers\Links:getLinks'); 2251 $this->get('/links', '\Shaarli\Api\Controllers\Links:getLinks')->setName('getLinks');
2257 $this->get('/links/{id:[\d]+}', '\Shaarli\Api\Controllers\Links:getLink'); 2252 $this->get('/links/{id:[\d]+}', '\Shaarli\Api\Controllers\Links:getLink')->setName('getLink');
2253 $this->post('/links', '\Shaarli\Api\Controllers\Links:postLink')->setName('postLink');
2258})->add('\Shaarli\Api\ApiMiddleware'); 2254})->add('\Shaarli\Api\ApiMiddleware');
2259 2255
2260$response = $app->run(true); 2256$response = $app->run(true);
diff --git a/plugins/readityourself/book-open.png b/plugins/readityourself/book-open.png
deleted file mode 100644
index 36513d7b..00000000
--- a/plugins/readityourself/book-open.png
+++ /dev/null
Binary files differ
diff --git a/plugins/readityourself/readityourself.html b/plugins/readityourself/readityourself.html
deleted file mode 100644
index 5e200715..00000000
--- a/plugins/readityourself/readityourself.html
+++ /dev/null
@@ -1 +0,0 @@
1<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
index bd611dd0..00000000
--- a/plugins/readityourself/readityourself.meta
+++ /dev/null
@@ -1,2 +0,0 @@
1description="For each link, add a ReadItYourself icon to save the shaared URL."
2parameters=READITYOUSELF_URL; \ No newline at end of file
diff --git a/plugins/readityourself/readityourself.php b/plugins/readityourself/readityourself.php
deleted file mode 100644
index 961c5bda..00000000
--- a/plugins/readityourself/readityourself.php
+++ /dev/null
@@ -1,51 +0,0 @@
1<?php
2
3/**
4 * Plugin readityourself
5 */
6
7// If we're talking about https://github.com/memiks/readityourself
8// it seems kinda dead.
9// Not tested.
10
11/**
12 * Init function, return an error if the server is not set.
13 *
14 * @param $conf ConfigManager instance.
15 *
16 * @return array Eventual error.
17 */
18function readityourself_init($conf)
19{
20 $riyUrl = $conf->get('plugins.READITYOUSELF_URL');
21 if (empty($riyUrl)) {
22 $error = 'Readityourself plugin error: '.
23 'Please define the "READITYOUSELF_URL" setting in the plugin administration page.';
24 return array($error);
25 }
26}
27
28/**
29 * Add readityourself icon to link_plugin when rendering linklist.
30 *
31 * @param mixed $data Linklist data.
32 * @param ConfigManager $conf Configuration Manager instance.
33 *
34 * @return mixed - linklist data with readityourself plugin.
35 */
36function hook_readityourself_render_linklist($data, $conf)
37{
38 $riyUrl = $conf->get('plugins.READITYOUSELF_URL');
39 if (empty($riyUrl)) {
40 return $data;
41 }
42
43 $readityourself_html = file_get_contents(PluginManager::$PLUGINS_PATH . '/readityourself/readityourself.html');
44
45 foreach ($data['links'] as &$value) {
46 $readityourself = sprintf($readityourself_html, $riyUrl, $value['url'], PluginManager::$PLUGINS_PATH);
47 $value['link_plugin'][] = $readityourself;
48 }
49
50 return $data;
51}
diff --git a/shaarli_version.php b/shaarli_version.php
index 3f79a4c6..9167b43e 100644
--- a/shaarli_version.php
+++ b/shaarli_version.php
@@ -1 +1 @@
<?php /* 0.8.3 */ ?> <?php /* dev */ ?>
diff --git a/tests/ApplicationUtilsTest.php b/tests/ApplicationUtilsTest.php
index ad86e21c..ff4c9e17 100644
--- a/tests/ApplicationUtilsTest.php
+++ b/tests/ApplicationUtilsTest.php
@@ -17,7 +17,7 @@ class FakeApplicationUtils extends ApplicationUtils
17 /** 17 /**
18 * Toggle HTTP requests, allow overriding the version code 18 * Toggle HTTP requests, allow overriding the version code
19 */ 19 */
20 public static function getLatestGitVersionCode($url, $timeout=0) 20 public static function getVersion($url, $timeout=0)
21 { 21 {
22 return self::$VERSION_CODE; 22 return self::$VERSION_CODE;
23 } 23 }
@@ -45,17 +45,27 @@ class ApplicationUtilsTest extends PHPUnit_Framework_TestCase
45 } 45 }
46 46
47 /** 47 /**
48 * Remove test version file if it exists
49 */
50 public function tearDown()
51 {
52 if (is_file('sandbox/version.php')) {
53 unlink('sandbox/version.php');
54 }
55 }
56
57 /**
48 * Retrieve the latest version code available on Git 58 * Retrieve the latest version code available on Git
49 * 59 *
50 * Expected format: Semantic Versioning - major.minor.patch 60 * Expected format: Semantic Versioning - major.minor.patch
51 */ 61 */
52 public function testGetLatestGitVersionCode() 62 public function testGetVersionCode()
53 { 63 {
54 $testTimeout = 10; 64 $testTimeout = 10;
55 65
56 $this->assertEquals( 66 $this->assertEquals(
57 '0.5.4', 67 '0.5.4',
58 ApplicationUtils::getLatestGitVersionCode( 68 ApplicationUtils::getVersion(
59 'https://raw.githubusercontent.com/shaarli/Shaarli/' 69 'https://raw.githubusercontent.com/shaarli/Shaarli/'
60 .'v0.5.4/shaarli_version.php', 70 .'v0.5.4/shaarli_version.php',
61 $testTimeout 71 $testTimeout
@@ -63,23 +73,35 @@ class ApplicationUtilsTest extends PHPUnit_Framework_TestCase
63 ); 73 );
64 $this->assertRegExp( 74 $this->assertRegExp(
65 self::$versionPattern, 75 self::$versionPattern,
66 ApplicationUtils::getLatestGitVersionCode( 76 ApplicationUtils::getVersion(
67 'https://raw.githubusercontent.com/shaarli/Shaarli/' 77 'https://raw.githubusercontent.com/shaarli/Shaarli/'
68 .'master/shaarli_version.php', 78 .'latest/shaarli_version.php',
69 $testTimeout 79 $testTimeout
70 ) 80 )
71 ); 81 );
72 } 82 }
73 83
74 /** 84 /**
75 * Attempt to retrieve the latest version from an invalid URL 85 * Attempt to retrieve the latest version from an invalid File
86 */
87 public function testGetVersionCodeFromFile()
88 {
89 file_put_contents('sandbox/version.php', '<?php /* 1.2.3 */ ?>'. PHP_EOL);
90 $this->assertEquals(
91 '1.2.3',
92 ApplicationUtils::getVersion('sandbox/version.php', 1)
93 );
94 }
95
96 /**
97 * Attempt to retrieve the latest version from an invalid File
76 */ 98 */
77 public function testGetLatestGitVersionCodeInvalidUrl() 99 public function testGetVersionCodeInvalidFile()
78 { 100 {
79 $oldlog = ini_get('error_log'); 101 $oldlog = ini_get('error_log');
80 ini_set('error_log', '/dev/null'); 102 ini_set('error_log', '/dev/null');
81 $this->assertFalse( 103 $this->assertFalse(
82 ApplicationUtils::getLatestGitVersionCode('htttp://null.io', 1) 104 ApplicationUtils::getVersion('idontexist', 1)
83 ); 105 );
84 ini_set('error_log', $oldlog); 106 ini_set('error_log', $oldlog);
85 } 107 }
@@ -332,4 +354,15 @@ class ApplicationUtilsTest extends PHPUnit_Framework_TestCase
332 ApplicationUtils::checkResourcePermissions($conf) 354 ApplicationUtils::checkResourcePermissions($conf)
333 ); 355 );
334 } 356 }
357
358 /**
359 * Check update with 'dev' as curent version (master branch).
360 * It should always return false.
361 */
362 public function testCheckUpdateDev()
363 {
364 $this->assertFalse(
365 ApplicationUtils::checkUpdate('dev', self::$testUpdateFile, 100, true, true)
366 );
367 }
335} 368}
diff --git a/tests/TimeZoneTest.php b/tests/TimeZoneTest.php
index 2976d116..127fdc19 100644
--- a/tests/TimeZoneTest.php
+++ b/tests/TimeZoneTest.php
@@ -11,24 +11,45 @@ require_once 'application/TimeZone.php';
11class TimeZoneTest extends PHPUnit_Framework_TestCase 11class TimeZoneTest extends PHPUnit_Framework_TestCase
12{ 12{
13 /** 13 /**
14 * @var array of timezones
15 */
16 protected $installedTimezones;
17
18 public function setUp()
19 {
20 $this->installedTimezones = [
21 'Antarctica/Syowa',
22 'Europe/London',
23 'Europe/Paris',
24 'UTC'
25 ];
26 }
27
28 /**
14 * Generate a timezone selection form 29 * Generate a timezone selection form
15 */ 30 */
16 public function testGenerateTimeZoneForm() 31 public function testGenerateTimeZoneForm()
17 { 32 {
18 $generated = generateTimeZoneForm(); 33 $expected = [
34 'continents' => [
35 'Antarctica',
36 'Europe',
37 'UTC',
38 'selected' => '',
39 ],
40 'cities' => [
41 ['continent' => 'Antarctica', 'city' => 'Syowa'],
42 ['continent' => 'Europe', 'city' => 'London'],
43 ['continent' => 'Europe', 'city' => 'Paris'],
44 ['continent' => 'UTC', 'city' => 'UTC'],
45 'selected' => '',
46 ]
47 ];
19 48
20 // HTML form 49 list($continents, $cities) = generateTimeZoneData($this->installedTimezones);
21 $this->assertStringStartsWith('Continent:<select', $generated[0]);
22 $this->assertContains('selected="selected"', $generated[0]);
23 $this->assertStringEndsWith('</select><br />', $generated[0]);
24 50
25 // Javascript handler 51 $this->assertEquals($expected['continents'], $continents);
26 $this->assertStringStartsWith('<script>', $generated[1]); 52 $this->assertEquals($expected['cities'], $cities);
27 $this->assertContains(
28 '<option value=\"Bermuda\">Bermuda<\/option>',
29 $generated[1]
30 );
31 $this->assertStringEndsWith('</script>', $generated[1]);
32 } 53 }
33 54
34 /** 55 /**
@@ -36,28 +57,26 @@ class TimeZoneTest extends PHPUnit_Framework_TestCase
36 */ 57 */
37 public function testGenerateTimeZoneFormPreselected() 58 public function testGenerateTimeZoneFormPreselected()
38 { 59 {
39 $generated = generateTimeZoneForm('Antarctica/Syowa'); 60 $expected = [
40 61 'continents' => [
41 // HTML form 62 'Antarctica',
42 $this->assertStringStartsWith('Continent:<select', $generated[0]); 63 'Europe',
43 $this->assertContains( 64 'UTC',
44 'value="Antarctica" selected="selected"', 65 'selected' => 'Antarctica',
45 $generated[0] 66 ],
46 ); 67 'cities' => [
47 $this->assertContains( 68 ['continent' => 'Antarctica', 'city' => 'Syowa'],
48 'value="Syowa" selected="selected"', 69 ['continent' => 'Europe', 'city' => 'London'],
49 $generated[0] 70 ['continent' => 'Europe', 'city' => 'Paris'],
50 ); 71 ['continent' => 'UTC', 'city' => 'UTC'],
51 $this->assertStringEndsWith('</select><br />', $generated[0]); 72 'selected' => 'Syowa',
73 ]
74 ];
52 75
76 list($continents, $cities) = generateTimeZoneData($this->installedTimezones, 'Antarctica/Syowa');
53 77
54 // Javascript handler 78 $this->assertEquals($expected['continents'], $continents);
55 $this->assertStringStartsWith('<script>', $generated[1]); 79 $this->assertEquals($expected['cities'], $cities);
56 $this->assertContains(
57 '<option value=\"Bermuda\">Bermuda<\/option>',
58 $generated[1]
59 );
60 $this->assertStringEndsWith('</script>', $generated[1]);
61 } 80 }
62 81
63 /** 82 /**
diff --git a/tests/UtilsTest.php b/tests/UtilsTest.php
index e70cc1ae..d6a0aad5 100644
--- a/tests/UtilsTest.php
+++ b/tests/UtilsTest.php
@@ -4,6 +4,7 @@
4 */ 4 */
5 5
6require_once 'application/Utils.php'; 6require_once 'application/Utils.php';
7require_once 'application/Languages.php';
7require_once 'tests/utils/ReferenceSessionIdHashes.php'; 8require_once 'tests/utils/ReferenceSessionIdHashes.php';
8 9
9// Initialize reference data before PHPUnit starts a session 10// Initialize reference data before PHPUnit starts a session
@@ -326,4 +327,94 @@ class UtilsTest extends PHPUnit_Framework_TestCase
326 $this->assertFalse(format_date([])); 327 $this->assertFalse(format_date([]));
327 $this->assertFalse(format_date(null)); 328 $this->assertFalse(format_date(null));
328 } 329 }
330
331 /**
332 * Test is_integer_mixed with valid values
333 */
334 public function testIsIntegerMixedValid()
335 {
336 $this->assertTrue(is_integer_mixed(12));
337 $this->assertTrue(is_integer_mixed('12'));
338 $this->assertTrue(is_integer_mixed(-12));
339 $this->assertTrue(is_integer_mixed('-12'));
340 $this->assertTrue(is_integer_mixed(0));
341 $this->assertTrue(is_integer_mixed('0'));
342 $this->assertTrue(is_integer_mixed(0x0a));
343 }
344
345 /**
346 * Test is_integer_mixed with invalid values
347 */
348 public function testIsIntegerMixedInvalid()
349 {
350 $this->assertFalse(is_integer_mixed(true));
351 $this->assertFalse(is_integer_mixed(false));
352 $this->assertFalse(is_integer_mixed([]));
353 $this->assertFalse(is_integer_mixed(['test']));
354 $this->assertFalse(is_integer_mixed([12]));
355 $this->assertFalse(is_integer_mixed(new DateTime()));
356 $this->assertFalse(is_integer_mixed('0x0a'));
357 $this->assertFalse(is_integer_mixed('12k'));
358 $this->assertFalse(is_integer_mixed('k12'));
359 $this->assertFalse(is_integer_mixed(''));
360 }
361
362 /**
363 * Test return_bytes
364 */
365 public function testReturnBytes()
366 {
367 $this->assertEquals(2 * 1024, return_bytes('2k'));
368 $this->assertEquals(2 * 1024, return_bytes('2K'));
369 $this->assertEquals(2 * (pow(1024, 2)), return_bytes('2m'));
370 $this->assertEquals(2 * (pow(1024, 2)), return_bytes('2M'));
371 $this->assertEquals(2 * (pow(1024, 3)), return_bytes('2g'));
372 $this->assertEquals(2 * (pow(1024, 3)), return_bytes('2G'));
373 $this->assertEquals(374, return_bytes('374'));
374 $this->assertEquals(374, return_bytes(374));
375 $this->assertEquals(0, return_bytes('0'));
376 $this->assertEquals(0, return_bytes(0));
377 $this->assertEquals(-1, return_bytes('-1'));
378 $this->assertEquals(-1, return_bytes(-1));
379 $this->assertEquals('', return_bytes(''));
380 }
381
382 /**
383 * Test human_bytes
384 */
385 public function testHumanBytes()
386 {
387 $this->assertEquals('2kiB', human_bytes(2 * 1024));
388 $this->assertEquals('2kiB', human_bytes(strval(2 * 1024)));
389 $this->assertEquals('2MiB', human_bytes(2 * (pow(1024, 2))));
390 $this->assertEquals('2MiB', human_bytes(strval(2 * (pow(1024, 2)))));
391 $this->assertEquals('2GiB', human_bytes(2 * (pow(1024, 3))));
392 $this->assertEquals('2GiB', human_bytes(strval(2 * (pow(1024, 3)))));
393 $this->assertEquals('374B', human_bytes(374));
394 $this->assertEquals('374B', human_bytes('374'));
395 $this->assertEquals('232kiB', human_bytes(237481));
396 $this->assertEquals('Unlimited', human_bytes('0'));
397 $this->assertEquals('Unlimited', human_bytes(0));
398 $this->assertEquals('Setting not set', human_bytes(''));
399 }
400
401 /**
402 * Test get_max_upload_size with formatting
403 */
404 public function testGetMaxUploadSize()
405 {
406 $this->assertEquals('1MiB', get_max_upload_size(2097152, '1024k'));
407 $this->assertEquals('1MiB', get_max_upload_size('1m', '2m'));
408 $this->assertEquals('100B', get_max_upload_size(100, 100));
409 }
410
411 /**
412 * Test get_max_upload_size without formatting
413 */
414 public function testGetMaxUploadSizeRaw()
415 {
416 $this->assertEquals('1048576', get_max_upload_size(2097152, '1024k', false));
417 $this->assertEquals('1048576', get_max_upload_size('1m', '2m', false));
418 $this->assertEquals('100', get_max_upload_size(100, 100, false));
419 }
329} 420}
diff --git a/tests/api/controllers/PostLinkTest.php b/tests/api/controllers/PostLinkTest.php
new file mode 100644
index 00000000..3ed7bcb0
--- /dev/null
+++ b/tests/api/controllers/PostLinkTest.php
@@ -0,0 +1,193 @@
1<?php
2
3namespace Shaarli\Api\Controllers;
4
5
6use Shaarli\Config\ConfigManager;
7use Slim\Container;
8use Slim\Http\Environment;
9use Slim\Http\Request;
10use Slim\Http\Response;
11
12/**
13 * Class PostLinkTest
14 *
15 * Test POST Link REST API service.
16 *
17 * @package Shaarli\Api\Controllers
18 */
19class PostLinkTest extends \PHPUnit_Framework_TestCase
20{
21 /**
22 * @var string datastore to test write operations
23 */
24 protected static $testDatastore = 'sandbox/datastore.php';
25
26 /**
27 * @var ConfigManager instance
28 */
29 protected $conf;
30
31 /**
32 * @var \ReferenceLinkDB instance.
33 */
34 protected $refDB = null;
35
36 /**
37 * @var Container instance.
38 */
39 protected $container;
40
41 /**
42 * @var Links controller instance.
43 */
44 protected $controller;
45
46 /**
47 * Number of JSON field per link.
48 */
49 const NB_FIELDS_LINK = 9;
50
51 /**
52 * Before every test, instantiate a new Api with its config, plugins and links.
53 */
54 public function setUp()
55 {
56 $this->conf = new ConfigManager('tests/utils/config/configJson.json.php');
57 $this->refDB = new \ReferenceLinkDB();
58 $this->refDB->write(self::$testDatastore);
59
60 $this->container = new Container();
61 $this->container['conf'] = $this->conf;
62 $this->container['db'] = new \LinkDB(self::$testDatastore, true, false);
63
64 $this->controller = new Links($this->container);
65
66 $mock = $this->getMock('\Slim\Router', ['relativePathFor']);
67 $mock->expects($this->any())
68 ->method('relativePathFor')
69 ->willReturn('api/v1/links/1');
70
71 // affect @property-read... seems to work
72 $this->controller->getCi()->router = $mock;
73
74 // Used by index_url().
75 $this->controller->getCi()['environment'] = [
76 'SERVER_NAME' => 'domain.tld',
77 'SERVER_PORT' => 80,
78 'SCRIPT_NAME' => '/',
79 ];
80 }
81
82 /**
83 * After every test, remove the test datastore.
84 */
85 public function tearDown()
86 {
87 @unlink(self::$testDatastore);
88 }
89
90 /**
91 * Test link creation without any field: creates a blank note.
92 */
93 public function testPostLinkMinimal()
94 {
95 $env = Environment::mock([
96 'REQUEST_METHOD' => 'POST',
97 ]);
98
99 $request = Request::createFromEnvironment($env);
100
101 $response = $this->controller->postLink($request, new Response());
102 $this->assertEquals(201, $response->getStatusCode());
103 $this->assertEquals('api/v1/links/1', $response->getHeader('Location')[0]);
104 $data = json_decode((string) $response->getBody(), true);
105 $this->assertEquals(self::NB_FIELDS_LINK, count($data));
106 $this->assertEquals(43, $data['id']);
107 $this->assertRegExp('/[\w-_]{6}/', $data['shorturl']);
108 $this->assertEquals('http://domain.tld/?' . $data['shorturl'], $data['url']);
109 $this->assertEquals('?' . $data['shorturl'], $data['title']);
110 $this->assertEquals('', $data['description']);
111 $this->assertEquals([], $data['tags']);
112 $this->assertEquals(false, $data['private']);
113 $this->assertTrue(new \DateTime('5 seconds ago') < \DateTime::createFromFormat(\DateTime::ATOM, $data['created']));
114 $this->assertEquals('', $data['updated']);
115 }
116
117 /**
118 * Test link creation with all available fields.
119 */
120 public function testPostLinkFull()
121 {
122 $link = [
123 'url' => 'website.tld/test?foo=bar',
124 'title' => 'new entry',
125 'description' => 'shaare description',
126 'tags' => ['one', 'two'],
127 'private' => true,
128 ];
129 $env = Environment::mock([
130 'REQUEST_METHOD' => 'POST',
131 'CONTENT_TYPE' => 'application/json'
132 ]);
133
134 $request = Request::createFromEnvironment($env);
135 $request = $request->withParsedBody($link);
136 $response = $this->controller->postLink($request, new Response());
137
138 $this->assertEquals(201, $response->getStatusCode());
139 $this->assertEquals('api/v1/links/1', $response->getHeader('Location')[0]);
140 $data = json_decode((string) $response->getBody(), true);
141 $this->assertEquals(self::NB_FIELDS_LINK, count($data));
142 $this->assertEquals(43, $data['id']);
143 $this->assertRegExp('/[\w-_]{6}/', $data['shorturl']);
144 $this->assertEquals('http://' . $link['url'], $data['url']);
145 $this->assertEquals($link['title'], $data['title']);
146 $this->assertEquals($link['description'], $data['description']);
147 $this->assertEquals($link['tags'], $data['tags']);
148 $this->assertEquals(true, $data['private']);
149 $this->assertTrue(new \DateTime('2 seconds ago') < \DateTime::createFromFormat(\DateTime::ATOM, $data['created']));
150 $this->assertEquals('', $data['updated']);
151 }
152
153 /**
154 * Test link creation with an existing link (duplicate URL). Should return a 409 HTTP error and the existing link.
155 */
156 public function testPostLinkDuplicate()
157 {
158 $link = [
159 'url' => 'mediagoblin.org/',
160 'title' => 'new entry',
161 'description' => 'shaare description',
162 'tags' => ['one', 'two'],
163 'private' => true,
164 ];
165 $env = Environment::mock([
166 'REQUEST_METHOD' => 'POST',
167 'CONTENT_TYPE' => 'application/json'
168 ]);
169
170 $request = Request::createFromEnvironment($env);
171 $request = $request->withParsedBody($link);
172 $response = $this->controller->postLink($request, new Response());
173
174 $this->assertEquals(409, $response->getStatusCode());
175 $data = json_decode((string) $response->getBody(), true);
176 $this->assertEquals(self::NB_FIELDS_LINK, count($data));
177 $this->assertEquals(7, $data['id']);
178 $this->assertEquals('IuWvgA', $data['shorturl']);
179 $this->assertEquals('http://mediagoblin.org/', $data['url']);
180 $this->assertEquals('MediaGoblin', $data['title']);
181 $this->assertEquals('A free software media publishing platform #hashtagOther', $data['description']);
182 $this->assertEquals(['gnu', 'media', 'web', '.hidden', 'hashtag'], $data['tags']);
183 $this->assertEquals(false, $data['private']);
184 $this->assertEquals(
185 \DateTime::createFromFormat(\LinkDB::LINK_DATE_FORMAT, '20130614_184135'),
186 \DateTime::createFromFormat(\DateTime::ATOM, $data['created'])
187 );
188 $this->assertEquals(
189 \DateTime::createFromFormat(\LinkDB::LINK_DATE_FORMAT, '20130615_184230'),
190 \DateTime::createFromFormat(\DateTime::ATOM, $data['updated'])
191 );
192 }
193}
diff --git a/tests/languages/de/UtilsDeTest.php b/tests/languages/de/UtilsDeTest.php
index 545fa572..6c9c9adc 100644
--- a/tests/languages/de/UtilsDeTest.php
+++ b/tests/languages/de/UtilsDeTest.php
@@ -11,7 +11,16 @@ class UtilsDeTest extends UtilsTest
11 public function testDateFormat() 11 public function testDateFormat()
12 { 12 {
13 $date = DateTime::createFromFormat('Ymd_His', '20170101_101112'); 13 $date = DateTime::createFromFormat('Ymd_His', '20170101_101112');
14 $this->assertRegExp('/1. Januar 2017 (um )?10:11:12 GMT\+0?3(:00)?/', format_date($date, true)); 14 $this->assertRegExp('/1\. Januar 2017 (um )?10:11:12 GMT\+0?3(:00)?/', format_date($date, true, true));
15 }
16
17 /**
18 * Test date_format() without time.
19 */
20 public function testDateFormatNoTime()
21 {
22 $date = DateTime::createFromFormat('Ymd_His', '20170101_101112');
23 $this->assertRegExp('/1\. Januar 2017/', format_date($date, false,true));
15 } 24 }
16 25
17 /** 26 /**
@@ -20,7 +29,16 @@ class UtilsDeTest extends UtilsTest
20 public function testDateFormatDefault() 29 public function testDateFormatDefault()
21 { 30 {
22 $date = DateTime::createFromFormat('Ymd_His', '20170101_101112'); 31 $date = DateTime::createFromFormat('Ymd_His', '20170101_101112');
23 $this->assertEquals('So 01 Jan 2017 10:11:12 EAT', format_date($date, false)); 32 $this->assertEquals('So 01 Jan 2017 10:11:12 EAT', format_date($date, true, false));
33 }
34
35 /**
36 * Test date_format() using builtin PHP function strftime without time.
37 */
38 public function testDateFormatDefaultNoTime()
39 {
40 $date = DateTime::createFromFormat('Ymd_His', '20170201_101112');
41 $this->assertEquals('01.02.2017', format_date($date, false, false));
24 } 42 }
25 43
26 /** 44 /**
diff --git a/tests/languages/en/UtilsEnTest.php b/tests/languages/en/UtilsEnTest.php
index 7c829ac7..d8680b2b 100644
--- a/tests/languages/en/UtilsEnTest.php
+++ b/tests/languages/en/UtilsEnTest.php
@@ -11,7 +11,16 @@ class UtilsEnTest extends UtilsTest
11 public function testDateFormat() 11 public function testDateFormat()
12 { 12 {
13 $date = DateTime::createFromFormat('Ymd_His', '20170101_101112'); 13 $date = DateTime::createFromFormat('Ymd_His', '20170101_101112');
14 $this->assertRegExp('/January 1, 2017 (at )?10:11:12 AM GMT\+0?3(:00)?/', format_date($date, true)); 14 $this->assertRegExp('/January 1, 2017 (at )?10:11:12 AM GMT\+0?3(:00)?/', format_date($date, true, true));
15 }
16
17 /**
18 * Test date_format() without time.
19 */
20 public function testDateFormatNoTime()
21 {
22 $date = DateTime::createFromFormat('Ymd_His', '20170101_101112');
23 $this->assertRegExp('/January 1, 2017/', format_date($date, false, true));
15 } 24 }
16 25
17 /** 26 /**
@@ -20,7 +29,16 @@ class UtilsEnTest extends UtilsTest
20 public function testDateFormatDefault() 29 public function testDateFormatDefault()
21 { 30 {
22 $date = DateTime::createFromFormat('Ymd_His', '20170101_101112'); 31 $date = DateTime::createFromFormat('Ymd_His', '20170101_101112');
23 $this->assertEquals('Sun 01 Jan 2017 10:11:12 AM EAT', format_date($date, false)); 32 $this->assertEquals('Sun 01 Jan 2017 10:11:12 AM EAT', format_date($date, true, false));
33 }
34
35 /**
36 * Test date_format() using builtin PHP function strftime without time.
37 */
38 public function testDateFormatDefaultNoTime()
39 {
40 $date = DateTime::createFromFormat('Ymd_His', '20170201_101112');
41 $this->assertEquals('02/01/2017', format_date($date, false, false));
24 } 42 }
25 43
26 /** 44 /**
diff --git a/tests/languages/fr/UtilsFrTest.php b/tests/languages/fr/UtilsFrTest.php
index 45996ee2..0d50a878 100644
--- a/tests/languages/fr/UtilsFrTest.php
+++ b/tests/languages/fr/UtilsFrTest.php
@@ -15,12 +15,30 @@ class UtilsFrTest extends UtilsTest
15 } 15 }
16 16
17 /** 17 /**
18 * Test date_format() without time.
19 */
20 public function testDateFormatNoTime()
21 {
22 $date = DateTime::createFromFormat('Ymd_His', '20170101_101112');
23 $this->assertRegExp('/1 janvier 2017/', format_date($date, false, true));
24 }
25
26 /**
18 * Test date_format() using builtin PHP function strftime. 27 * Test date_format() using builtin PHP function strftime.
19 */ 28 */
20 public function testDateFormatDefault() 29 public function testDateFormatDefault()
21 { 30 {
22 $date = DateTime::createFromFormat('Ymd_His', '20170101_101112'); 31 $date = DateTime::createFromFormat('Ymd_His', '20170101_101112');
23 $this->assertEquals('dim. 01 janv. 2017 10:11:12 EAT', format_date($date, false)); 32 $this->assertEquals('dim. 01 janv. 2017 10:11:12 EAT', format_date($date, true, false));
33 }
34
35 /**
36 * Test date_format() using builtin PHP function strftime without time.
37 */
38 public function testDateFormatDefaultNoTime()
39 {
40 $date = DateTime::createFromFormat('Ymd_His', '20170201_101112');
41 $this->assertEquals('01/02/2017', format_date($date, false, false));
24 } 42 }
25 43
26 /** 44 /**
diff --git a/tests/plugins/PluginReadityourselfTest.php b/tests/plugins/PluginReadityourselfTest.php
deleted file mode 100644
index bbba9676..00000000
--- a/tests/plugins/PluginReadityourselfTest.php
+++ /dev/null
@@ -1,99 +0,0 @@
1<?php
2use Shaarli\Config\ConfigManager;
3
4/**
5 * PluginReadityourselfTest.php.php
6 */
7
8require_once 'plugins/readityourself/readityourself.php';
9
10/**
11 * Class PluginWallabagTest
12 * Unit test for the Wallabag plugin
13 */
14class PluginReadityourselfTest extends PHPUnit_Framework_TestCase
15{
16 /**
17 * Reset plugin path
18 */
19 public function setUp()
20 {
21 PluginManager::$PLUGINS_PATH = 'plugins';
22 }
23
24 /**
25 * Test Readityourself init without errors.
26 */
27 public function testReadityourselfInitNoError()
28 {
29 $conf = new ConfigManager('');
30 $conf->set('plugins.READITYOUSELF_URL', 'value');
31 $errors = readityourself_init($conf);
32 $this->assertEmpty($errors);
33 }
34
35 /**
36 * Test Readityourself init with errors.
37 */
38 public function testReadityourselfInitError()
39 {
40 $conf = new ConfigManager('');
41 $errors = readityourself_init($conf);
42 $this->assertNotEmpty($errors);
43 }
44
45 /**
46 * Test render_linklist hook.
47 */
48 public function testReadityourselfLinklist()
49 {
50 $conf = new ConfigManager('');
51 $conf->set('plugins.READITYOUSELF_URL', 'value');
52 $str = 'http://randomstr.com/test';
53 $data = array(
54 'title' => $str,
55 'links' => array(
56 array(
57 'url' => $str,
58 )
59 )
60 );
61
62 $data = hook_readityourself_render_linklist($data, $conf);
63 $link = $data['links'][0];
64 // data shouldn't be altered
65 $this->assertEquals($str, $data['title']);
66 $this->assertEquals($str, $link['url']);
67
68 // plugin data
69 $this->assertEquals(1, count($link['link_plugin']));
70 $this->assertNotFalse(strpos($link['link_plugin'][0], $str));
71 }
72
73 /**
74 * Test without config: nothing should happened.
75 */
76 public function testReadityourselfLinklistWithoutConfig()
77 {
78 $conf = new ConfigManager('');
79 $conf->set('plugins.READITYOUSELF_URL', null);
80 $str = 'http://randomstr.com/test';
81 $data = array(
82 'title' => $str,
83 'links' => array(
84 array(
85 'url' => $str,
86 )
87 )
88 );
89
90 $data = hook_readityourself_render_linklist($data, $conf);
91 $link = $data['links'][0];
92 // data shouldn't be altered
93 $this->assertEquals($str, $data['title']);
94 $this->assertEquals($str, $link['url']);
95
96 // plugin data
97 $this->assertArrayNotHasKey('link_plugin', $link);
98 }
99}
diff --git a/tests/utils/ReferenceLinkDB.php b/tests/utils/ReferenceLinkDB.php
index 36d58c68..1f4b3063 100644
--- a/tests/utils/ReferenceLinkDB.php
+++ b/tests/utils/ReferenceLinkDB.php
@@ -56,7 +56,7 @@ class ReferenceLinkDB
56 0, 56 0,
57 DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20130614_184135'), 57 DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20130614_184135'),
58 'gnu media web .hidden hashtag', 58 'gnu media web .hidden hashtag',
59 null, 59 DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20130615_184230'),
60 'IuWvgA' 60 'IuWvgA'
61 ); 61 );
62 62
diff --git a/tpl/default/configure.html b/tpl/default/configure.html
index d6536d47..7469ab59 100644
--- a/tpl/default/configure.html
+++ b/tpl/default/configure.html
@@ -73,15 +73,35 @@
73 <div class="pure-u-lg-{$ratioLabel} pure-u-1 "> 73 <div class="pure-u-lg-{$ratioLabel} pure-u-1 ">
74 <div class="form-label"> 74 <div class="form-label">
75 <label> 75 <label>
76 <span class="label-name">{'Timezone'|t}</span> 76 <span class="label-name">{'Timezone'|t}</span><br>
77 <span class="label-desc">{'Continent'|t} &middot; {'City'|t}</span>
77 </label> 78 </label>
78 </div> 79 </div>
79 </div> 80 </div>
80 <div class="pure-u-lg-{$ratioInput} pure-u-1 "> 81 <div class="pure-u-lg-{$ratioInput} pure-u-1 ">
81 <div class="form-input"> 82 <div class="form-input">
82 {ignore}FIXME! too hackish, needs to be fixed upstream{/ignore} 83 <div class="timezone">
83 <div class="timezone" id="timezone-remove">{$timezone_form}</div> 84 <select id="continent" name="continent">
84 <div class="timezone" id="timezone-add"></div> 85 {loop="$continents"}
86 {if="$key !== 'selected'"}
87 <option value="{$value}" {if="$continents.selected === $value"}selected{/if}>
88 {$value}
89 </option>
90 {/if}
91 {/loop}
92 </select>
93 <select id="city" name="city">
94 {loop="$cities"}
95 {if="$key !== 'selected'"}
96 <option value="{$value.city}"
97 {if="$cities.selected === $value.city"}selected{/if}
98 data-continent="{$value.continent}">
99 {$value.city}
100 </option>
101 {/if}
102 {/loop}
103 </select>
104 </div>
85 </div> 105 </div>
86 </div> 106 </div>
87 </div> 107 </div>
@@ -186,7 +206,7 @@
186 <div class="pure-g"> 206 <div class="pure-g">
187 <div class="pure-u-lg-{$ratioLabel} pure-u-{$ratioLabelMobile}"> 207 <div class="pure-u-lg-{$ratioLabel} pure-u-{$ratioLabelMobile}">
188 <div class="form-label"> 208 <div class="form-label">
189 <label for="apiEnabled"> 209 <label for="enableApi">
190 <span class="label-name">{'Enable REST API'|t}</span><br> 210 <span class="label-name">{'Enable REST API'|t}</span><br>
191 <span class="label-desc">{'Allow third party software to use Shaarli such as mobile application'|t}</span> 211 <span class="label-desc">{'Allow third party software to use Shaarli such as mobile application'|t}</span>
192 </label> 212 </label>
@@ -194,7 +214,7 @@
194 </div> 214 </div>
195 <div class="pure-u-lg-{$ratioInput} pure-u-{$ratioInputMobile}"> 215 <div class="pure-u-lg-{$ratioInput} pure-u-{$ratioInputMobile}">
196 <div class="form-input"> 216 <div class="form-input">
197 <input type="checkbox" name="apiEnabled" id="apiEnabled" 217 <input type="checkbox" name="enableApi" id="enableApi"
198 {if="$api_enabled"}checked{/if}/> 218 {if="$api_enabled"}checked{/if}/>
199 </div> 219 </div>
200 </div> 220 </div>
diff --git a/tpl/default/css/shaarli.css b/tpl/default/css/shaarli.css
index 8fcd13af..73fade5f 100644
--- a/tpl/default/css/shaarli.css
+++ b/tpl/default/css/shaarli.css
@@ -35,14 +35,29 @@ pre {
35} 35}
36 36
37@font-face { 37@font-face {
38 font-family: 'Roboto Slab'; 38 font-family: 'Roboto';
39 font-weight: 400; 39 font-weight: 400;
40 font-style: normal; 40 font-style: normal;
41 src: 41 src:
42 local('Fira Sans'), 42 local('Roboto'),
43 local('Fira-Sans-regular'), 43 local('Roboto-Regular'),
44 url('../fonts/Fira-Sans-regular.woff2') format('woff2'), 44 url('../fonts/Roboto-Regular.woff2') format('woff2'),
45 url('../fonts/Fira-Sans-regular.woff') format('woff'); 45 url('../fonts/Roboto-Regular.woff') format('woff');
46}
47
48@font-face {
49 font-family: 'Roboto';
50 font-weight: 700;
51 font-style: normal;
52 src:
53 local('Roboto'),
54 local('Roboto-Bold'),
55 url('../fonts/Roboto-Bold.woff2') format('woff2'),
56 url('../fonts/Roboto-Bold.woff') format('woff');
57}
58
59body, .pure-g [class*="pure-u"] {
60 font-family: Roboto, Arial, sans-serif;
46} 61}
47 62
48/** 63/**
@@ -68,10 +83,6 @@ pre {
68 .pure-u-xl-visible { display: inline-block !important; } 83 .pure-u-xl-visible { display: inline-block !important; }
69} 84}
70 85
71.pure-g [class*="pure-u"]{
72 font-family: Roboto Slab, Arial, sans-serif;
73}
74
75/** 86/**
76 * Make pure-extras alert closable. 87 * Make pure-extras alert closable.
77 */ 88 */
@@ -504,7 +515,6 @@ pre {
504 color: #252525; 515 color: #252525;
505 text-decoration: none; 516 text-decoration: none;
506 vertical-align: middle; 517 vertical-align: middle;
507 font-family: Roboto Slab, Arial, sans-serif;
508} 518}
509 519
510.linklist-item-title .linklist-link { 520.linklist-item-title .linklist-link {
@@ -560,7 +570,6 @@ pre {
560.linklist-item-description { 570.linklist-item-description {
561 position: relative; 571 position: relative;
562 padding: 10px; 572 padding: 10px;
563 font-family: Roboto Slab, Arial, sans-serif;
564 word-wrap: break-word; 573 word-wrap: break-word;
565 color: #252525; 574 color: #252525;
566 line-height: 1.3em; 575 line-height: 1.3em;
diff --git a/tpl/default/daily.html b/tpl/default/daily.html
index d8c91078..29d845d5 100644
--- a/tpl/default/daily.html
+++ b/tpl/default/daily.html
@@ -44,7 +44,7 @@
44 </div> 44 </div>
45 </div> 45 </div>
46 <div> 46 <div>
47 <h3 class="window-subtitle">{function="strftime('%A %d, %B %Y', $day)"}</h3> 47 <h3 class="window-subtitle">{function="format_date($dayDate, false)"}</h3>
48 48
49 <div id="plugin_zone_about_daily" class="plugin_zone"> 49 <div id="plugin_zone_about_daily" class="plugin_zone">
50 {loop="$daily_about_plugin"} 50 {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
index 014ac317..00000000
--- a/tpl/default/fonts/Fira-Sans-regular.woff
+++ /dev/null
Binary files differ
diff --git a/tpl/default/fonts/Fira-Sans-regular.woff2 b/tpl/default/fonts/Fira-Sans-regular.woff2
deleted file mode 100644
index bf3ad9a4..00000000
--- a/tpl/default/fonts/Fira-Sans-regular.woff2
+++ /dev/null
Binary files differ
diff --git a/tpl/default/fonts/Roboto-Bold.woff b/tpl/default/fonts/Roboto-Bold.woff
new file mode 100644
index 00000000..3d86753b
--- /dev/null
+++ b/tpl/default/fonts/Roboto-Bold.woff
Binary files differ
diff --git a/tpl/default/fonts/Roboto-Bold.woff2 b/tpl/default/fonts/Roboto-Bold.woff2
new file mode 100644
index 00000000..bd05e2ea
--- /dev/null
+++ b/tpl/default/fonts/Roboto-Bold.woff2
Binary files differ
diff --git a/tpl/default/fonts/Roboto-Regular.woff b/tpl/default/fonts/Roboto-Regular.woff
new file mode 100644
index 00000000..464d2062
--- /dev/null
+++ b/tpl/default/fonts/Roboto-Regular.woff
Binary files differ
diff --git a/tpl/default/fonts/Roboto-Regular.woff2 b/tpl/default/fonts/Roboto-Regular.woff2
new file mode 100644
index 00000000..f9661967
--- /dev/null
+++ b/tpl/default/fonts/Roboto-Regular.woff2
Binary files differ
diff --git a/tpl/default/import.html b/tpl/default/import.html
index e6e521e8..1f040685 100644
--- a/tpl/default/import.html
+++ b/tpl/default/import.html
@@ -18,6 +18,7 @@
18 <div class="center" id="import-field"> 18 <div class="center" id="import-field">
19 <input type="hidden" name="MAX_FILE_SIZE" value="{$maxfilesize}"> 19 <input type="hidden" name="MAX_FILE_SIZE" value="{$maxfilesize}">
20 <input type="file" name="filetoupload"> 20 <input type="file" name="filetoupload">
21 <p><br>Maximum size allowed: <strong>{$maxfilesizeHuman}</strong></p>
21 </div> 22 </div>
22 23
23 <div class="pure-g"> 24 <div class="pure-g">
diff --git a/tpl/default/install.html b/tpl/default/install.html
index 33f8a453..164d453b 100644
--- a/tpl/default/install.html
+++ b/tpl/default/install.html
@@ -7,6 +7,8 @@
7 7
8{$ratioLabel='1-4'} 8{$ratioLabel='1-4'}
9{$ratioInput='3-4'} 9{$ratioInput='3-4'}
10{$ratioLabelMobile='7-8'}
11{$ratioInputMobile='1-8'}
10 12
11<form method="POST" action="#" name="installform" id="installform"> 13<form method="POST" action="#" name="installform" id="installform">
12<div class="pure-g"> 14<div class="pure-g">
@@ -43,24 +45,22 @@
43 </div> 45 </div>
44 <div class="pure-u-lg-{$ratioInput} pure-u-1"> 46 <div class="pure-u-lg-{$ratioInput} pure-u-1">
45 <div class="form-input"> 47 <div class="form-input">
46 <input type="text" name="setpassword" id="password"> 48 <input type="password" name="setpassword" id="password">
47 </div> 49 </div>
48 </div> 50 </div>
49 </div> 51 </div>
50 52
51 <div class="pure-g"> 53 <div class="pure-g">
52 <div class="pure-u-lg-{$ratioLabel} pure-u-1 "> 54 <div class="pure-u-lg-{$ratioLabel} pure-u-1">
53 <div class="form-label"> 55 <div class="form-label">
54 <label> 56 <label for="title">
55 <span class="label-name">{'Timezone'|t}</span> 57 <span class="label-name">{'Shaarli title'|t}</span>
56 </label> 58 </label>
57 </div> 59 </div>
58 </div> 60 </div>
59 <div class="pure-u-lg-{$ratioInput} pure-u-1 "> 61 <div class="pure-u-lg-{$ratioInput} pure-u-1">
60 <div class="form-input"> 62 <div class="form-input">
61 {ignore}FIXME! too hackish, needs to be fixed upstream{/ignore} 63 <input type="text" name="title" id="title" placeholder="{'My links'|t}">
62 <div class="timezone" id="timezone-remove">{$timezone_html}</div>
63 <div class="timezone" id="timezone-add"></div>
64 </div> 64 </div>
65 </div> 65 </div>
66 </div> 66 </div>
@@ -68,14 +68,36 @@
68 <div class="pure-g"> 68 <div class="pure-g">
69 <div class="pure-u-lg-{$ratioLabel} pure-u-1"> 69 <div class="pure-u-lg-{$ratioLabel} pure-u-1">
70 <div class="form-label"> 70 <div class="form-label">
71 <label for="title"> 71 <label>
72 <span class="label-name">{'Shaarli title'|t}</span> 72 <span class="label-name">{'Timezone'|t}</span><br>
73 <span class="label-desc">{'Continent'|t} &middot; {'City'|t}</span>
73 </label> 74 </label>
74 </div> 75 </div>
75 </div> 76 </div>
76 <div class="pure-u-lg-{$ratioInput} pure-u-1"> 77 <div class="pure-u-lg-{$ratioInput} pure-u-1">
77 <div class="form-input"> 78 <div class="form-input">
78 <input type="text" name="title" id="title" placeholder="{'My links'|t}"> 79 <div class="timezone">
80 <select id="continent" name="continent">
81 {loop="$continents"}
82 {if="$key !== 'selected'"}
83 <option value="{$value}" {if="$continents.selected === $value"}selected{/if}>
84 {$value}
85 </option>
86 {/if}
87 {/loop}
88 </select>
89 <select id="city" name="city">
90 {loop="$cities"}
91 {if="$key !== 'selected'"}
92 <option value="{$value.city}"
93 {if="$cities.selected === $value.city"}selected{/if}
94 data-continent="{$value.continent}">
95 {$value.city}
96 </option>
97 {/if}
98 {/loop}
99 </select>
100 </div>
79 </div> 101 </div>
80 </div> 102 </div>
81 </div> 103 </div>
@@ -98,6 +120,22 @@
98 </div> 120 </div>
99 </div> 121 </div>
100 122
123 <div class="pure-g">
124 <div class="pure-u-lg-{$ratioLabel} pure-u-{$ratioLabelMobile}">
125 <div class="form-label">
126 <label for="enableApi">
127 <span class="label-name">{'Enable REST API'|t}</span><br>
128 <span class="label-desc">{'Allow third party software to use Shaarli such as mobile application'|t}</span>
129 </label>
130 </div>
131 </div>
132 <div class="pure-u-lg-{$ratioInput} pure-u-{$ratioInputMobile}">
133 <div class="form-input">
134 <input type="checkbox" name="enableApi" id="enableApi" checked />
135 </div>
136 </div>
137 </div>
138
101 <div class="center"> 139 <div class="center">
102 <input type="submit" value="{'Install'|t}" name="Save"> 140 <input type="submit" value="{'Install'|t}" name="Save">
103 </div> 141 </div>
diff --git a/tpl/default/js/shaarli.js b/tpl/default/js/shaarli.js
index 30d8ed6f..4d47fcd0 100644
--- a/tpl/default/js/shaarli.js
+++ b/tpl/default/js/shaarli.js
@@ -76,9 +76,12 @@ window.onload = function () {
76 } 76 }
77 } 77 }
78 78
79 document.getElementById('menu-toggle').addEventListener('click', function (e) { 79 var menuToggle = document.getElementById('menu-toggle');
80 toggleMenu(); 80 if (menuToggle != null) {
81 }); 81 menuToggle.addEventListener('click', function (e) {
82 toggleMenu();
83 });
84 }
82 85
83 window.addEventListener(WINDOW_CHANGE_EVENT, closeMenu); 86 window.addEventListener(WINDOW_CHANGE_EVENT, closeMenu);
84 })(this, this.document); 87 })(this, this.document);
@@ -255,10 +258,9 @@ window.onload = function () {
255 * Remove CSS target padding (for fixed bar) 258 * Remove CSS target padding (for fixed bar)
256 */ 259 */
257 if (location.hash != '') { 260 if (location.hash != '') {
258 var anchor = document.querySelector(location.hash); 261 var anchor = document.getElementById(location.hash.substr(1));
259 if (anchor != null) { 262 if (anchor != null) {
260 var padsize = anchor.clientHeight; 263 var padsize = anchor.clientHeight;
261 console.log(document.querySelector(location.hash).clientHeight);
262 this.window.scroll(0, this.window.scrollY - padsize); 264 this.window.scroll(0, this.window.scrollY - padsize);
263 anchor.style.paddingTop = 0; 265 anchor.style.paddingTop = 0;
264 } 266 }
@@ -300,21 +302,6 @@ window.onload = function () {
300 } 302 }
301 303
302 /** 304 /**
303 * TimeZome select
304 * FIXME! way too hackish
305 */
306 var toRemove = document.getElementById('timezone-remove');
307 if (toRemove != null) {
308 var firstSelect = toRemove.getElementsByTagName('select')[0];
309 var secondSelect = toRemove.getElementsByTagName('select')[1];
310 toRemove.parentNode.removeChild(toRemove);
311 var toAdd = document.getElementById('timezone-add');
312 var newTimezone = '<span class="timezone-continent">Continent ' + firstSelect.outerHTML + '</span>';
313 newTimezone += ' <span class="timezone-country">Country ' + secondSelect.outerHTML + '</span>';
314 toAdd.innerHTML = newTimezone;
315 }
316
317 /**
318 * Awesomplete trigger. 305 * Awesomplete trigger.
319 */ 306 */
320 var tags = document.getElementById('lf_tags'); 307 var tags = document.getElementById('lf_tags');
@@ -366,6 +353,15 @@ window.onload = function () {
366 } 353 }
367 }); 354 });
368 }); 355 });
356
357 var continent = document.getElementById('continent');
358 var city = document.getElementById('city');
359 if (continent != null && city != null) {
360 continent.addEventListener('change', function(event) {
361 hideTimezoneCities(city, continent.options[continent.selectedIndex].value, true);
362 });
363 hideTimezoneCities(city, continent.options[continent.selectedIndex].value, false);
364 }
369}; 365};
370 366
371function activateFirefoxSocial(node) { 367function activateFirefoxSocial(node) {
@@ -391,3 +387,25 @@ function activateFirefoxSocial(node) {
391 var activate = new CustomEvent("ActivateSocialFeature"); 387 var activate = new CustomEvent("ActivateSocialFeature");
392 node.dispatchEvent(activate); 388 node.dispatchEvent(activate);
393} 389}
390
391/**
392 * Add the class 'hidden' to city options not attached to the current selected continent.
393 *
394 * @param cities List of <option> elements
395 * @param currentContinent Current selected continent
396 * @param reset Set to true to reset the selected value
397 */
398function hideTimezoneCities(cities, currentContinent, reset = false) {
399 var first = true;
400 [].forEach.call(cities, function(option) {
401 if (option.getAttribute('data-continent') != currentContinent) {
402 option.className = 'hidden';
403 } else {
404 option.className = '';
405 if (reset === true && first === true) {
406 option.setAttribute('selected', 'selected');
407 first = false;
408 }
409 }
410 });
411}
diff --git a/tpl/vintage/configure.html b/tpl/vintage/configure.html
index 5820e6e4..7adc7545 100644
--- a/tpl/vintage/configure.html
+++ b/tpl/vintage/configure.html
@@ -4,7 +4,6 @@
4<body onload="document.configform.title.focus();"> 4<body onload="document.configform.title.focus();">
5<div id="pageheader"> 5<div id="pageheader">
6 {include="page.header"} 6 {include="page.header"}
7 {$timezone_js}
8 <form method="POST" action="#" name="configform" id="configform"> 7 <form method="POST" action="#" name="configform" id="configform">
9 <input type="hidden" name="token" value="{$token}"> 8 <input type="hidden" name="token" value="{$token}">
10 <table id="configuration_table"> 9 <table id="configuration_table">
@@ -35,7 +34,28 @@
35 34
36 <tr> 35 <tr>
37 <td><b>Timezone:</b></td> 36 <td><b>Timezone:</b></td>
38 <td>{$timezone_form}</td> 37 <td>
38 <select id="continent" name="continent">
39 {loop="$continents"}
40 {if="$key !== 'selected'"}
41 <option value="{$value}" {if="$continents.selected === $value"}selected{/if}>
42 {$value}
43 </option>
44 {/if}
45 {/loop}
46 </select>
47 <select id="city" name="city">
48 {loop="$cities"}
49 {if="$key !== 'selected'"}
50 <option value="{$value.city}"
51 {if="$cities.selected === $value.city"}selected{/if}
52 data-continent="{$value.continent}">
53 {$value.city}
54 </option>
55 {/if}
56 {/loop}
57 </select>
58 </td>
39 </tr> 59 </tr>
40 60
41 <tr> 61 <tr>
@@ -97,9 +117,9 @@
97 <tr> 117 <tr>
98 <td valign="top"><b>Enable REST API</b></td> 118 <td valign="top"><b>Enable REST API</b></td>
99 <td> 119 <td>
100 <input type="checkbox" name="apiEnabled" id="apiEnabled" 120 <input type="checkbox" name="enableApi" id="enableApi"
101 {if="$api_enabled"}checked{/if}/> 121 {if="$api_enabled"}checked{/if}/>
102 <label for="apiEnabled">&nbsp;Allow third party software to use Shaarli such as mobile application.</label> 122 <label for="enableApi">&nbsp;Allow third party software to use Shaarli such as mobile application.</label>
103 </td> 123 </td>
104 </tr> 124 </tr>
105 <tr> 125 <tr>
diff --git a/tpl/vintage/css/shaarli.css b/tpl/vintage/css/shaarli.css
index 7ca567e7..9c72d993 100644
--- a/tpl/vintage/css/shaarli.css
+++ b/tpl/vintage/css/shaarli.css
@@ -41,6 +41,10 @@ strong {
41 font-weight: bold; 41 font-weight: bold;
42} 42}
43 43
44.hidden {
45 display: none;
46}
47
44/* Buttons */ 48/* Buttons */
45.bigbutton, #pageheader a.bigbutton { 49.bigbutton, #pageheader a.bigbutton {
46 background-color: #c0c0c0; 50 background-color: #c0c0c0;
diff --git a/tpl/vintage/import.html b/tpl/vintage/import.html
index 071e1160..bb9e4a56 100644
--- a/tpl/vintage/import.html
+++ b/tpl/vintage/import.html
@@ -5,7 +5,7 @@
5<div id="pageheader"> 5<div id="pageheader">
6 {include="page.header"} 6 {include="page.header"}
7 <div id="uploaddiv"> 7 <div id="uploaddiv">
8 Import Netscape HTML bookmarks (as exported from Firefox/Chrome/Opera/Delicious/Diigo...) (Max: {$maxfilesize} bytes). 8 Import Netscape HTML bookmarks (as exported from Firefox/Chrome/Opera/Delicious/Diigo...) (Max: {$maxfilesize}).
9 <form method="POST" action="?do=import" enctype="multipart/form-data" 9 <form method="POST" action="?do=import" enctype="multipart/form-data"
10 name="uploadform" id="uploadform"> 10 name="uploadform" id="uploadform">
11 <input type="hidden" name="token" value="{$token}"> 11 <input type="hidden" name="token" value="{$token}">
diff --git a/tpl/vintage/install.html b/tpl/vintage/install.html
index 42874dcd..aca890d6 100644
--- a/tpl/vintage/install.html
+++ b/tpl/vintage/install.html
@@ -1,6 +1,6 @@
1<!DOCTYPE html> 1<!DOCTYPE html>
2<html> 2<html>
3<head>{include="includes"}{$timezone_js}</head> 3<head>{include="includes"}</head>
4<body onload="document.installform.setlogin.focus();"> 4<body onload="document.installform.setlogin.focus();">
5<div id="install"> 5<div id="install">
6 <h1>Shaarli</h1> 6 <h1>Shaarli</h1>
@@ -9,7 +9,31 @@
9 <table> 9 <table>
10 <tr><td><b>Login:</b></td><td><input type="text" name="setlogin" size="30"></td></tr> 10 <tr><td><b>Login:</b></td><td><input type="text" name="setlogin" size="30"></td></tr>
11 <tr><td><b>Password:</b></td><td><input type="password" name="setpassword" size="30"></td></tr> 11 <tr><td><b>Password:</b></td><td><input type="password" name="setpassword" size="30"></td></tr>
12 {$timezone_html} 12 <tr>
13 <td><b>Timezone:</b></td>
14 <td>
15 <select id="continent" name="continent">
16 {loop="$continents"}
17 {if="$key !== 'selected'"}
18 <option value="{$value}" {if="$continents.selected === $value"}selected{/if}>
19 {$value}
20 </option>
21 {/if}
22 {/loop}
23 </select>
24 <select id="city" name="city">
25 {loop="$cities"}
26 {if="$key !== 'selected'"}
27 <option value="{$value.city}"
28 {if="$cities.selected === $value.city"}selected{/if}
29 data-continent="{$value.continent}">
30 {$value.city}
31 </option>
32 {/if}
33 {/loop}
34 </select>
35 </td>
36 </tr>
13 <tr><td><b>Page title:</b></td><td><input type="text" name="title" size="30"></td></tr> 37 <tr><td><b>Page title:</b></td><td><input type="text" name="title" size="30"></td></tr>
14 <tr><td valign="top"><b>Update:</b></td><td> 38 <tr><td valign="top"><b>Update:</b></td><td>
15 <input type="checkbox" name="updateCheck" id="updateCheck" checked="checked"><label for="updateCheck">&nbsp;Notify me when a new release is ready</label></td> 39 <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
index 00000000..9bcc96fb
--- /dev/null
+++ b/tpl/vintage/js/shaarli.js
@@ -0,0 +1,32 @@
1window.onload = function () {
2 var continent = document.getElementById('continent');
3 var city = document.getElementById('city');
4 if (continent != null && city != null) {
5 continent.addEventListener('change', function(event) {
6 hideTimezoneCities(city, continent.options[continent.selectedIndex].value, true);
7 });
8 hideTimezoneCities(city, continent.options[continent.selectedIndex].value, false);
9 }
10};
11
12/**
13 * Add the class 'hidden' to city options not attached to the current selected continent.
14 *
15 * @param cities List of <option> elements
16 * @param currentContinent Current selected continent
17 * @param reset Set to true to reset the selected value
18 */
19function hideTimezoneCities(cities, currentContinent, reset = false) {
20 var first = true;
21 [].forEach.call(cities, function(option) {
22 if (option.getAttribute('data-continent') != currentContinent) {
23 option.className = 'hidden';
24 } else {
25 option.className = '';
26 if (reset === true && first === true) {
27 option.setAttribute('selected', 'selected');
28 first = false;
29 }
30 }
31 });
32}
diff --git a/tpl/vintage/page.footer.html b/tpl/vintage/page.footer.html
index 006d1d68..4ce0803a 100644
--- a/tpl/vintage/page.footer.html
+++ b/tpl/vintage/page.footer.html
@@ -26,6 +26,7 @@
26<script>function confirmDeleteLink() { var agree=confirm("Are you sure you want to delete this link ?"); if (agree) return true ; else return false ; }</script> 26<script>function confirmDeleteLink() { var agree=confirm("Are you sure you want to delete this link ?"); if (agree) return true ; else return false ; }</script>
27{/if} 27{/if}
28 28
29<script src="js/shaarli.js"></script>
29{loop="$plugins_footer.js_files"} 30{loop="$plugins_footer.js_files"}
30 <script src="{$value}#"></script> 31 <script src="{$value}#"></script>
31{/loop} 32{/loop}