diff options
129 files changed, 4358 insertions, 723 deletions
@@ -1,11 +1,11 @@ | |||
1 | 327 ArthurHoaro <arthur@hoa.ro> | 1 | 472 ArthurHoaro <arthur@hoa.ro> |
2 | 188 VirtualTam <virtualtam@flibidi.net> | 2 | 201 VirtualTam <virtualtam@flibidi.net> |
3 | 132 nodiscc <nodiscc@gmail.com> | 3 | 132 nodiscc <nodiscc@gmail.com> |
4 | 56 Sébastien Sauvage <sebsauvage@sebsauvage.net> | 4 | 56 Sébastien Sauvage <sebsauvage@sebsauvage.net> |
5 | 15 Florian Eula <eula.florian@gmail.com> | 5 | 15 Florian Eula <eula.florian@gmail.com> |
6 | 13 Emilien Klein <emilien@klein.st> | 6 | 13 Emilien Klein <emilien@klein.st> |
7 | 12 Nicolas Danelon <hi@nicolasmd.com.ar> | 7 | 12 Nicolas Danelon <hi@nicolasmd.com.ar> |
8 | 7 Christophe HENRY <christophe.henry@sbgodin.fr> | 8 | 8 Christophe HENRY <christophe.henry@sbgodin.fr> |
9 | 4 Alexandre Alapetite <alexandre@alapetite.fr> | 9 | 4 Alexandre Alapetite <alexandre@alapetite.fr> |
10 | 4 David Sferruzza <david.sferruzza@gmail.com> | 10 | 4 David Sferruzza <david.sferruzza@gmail.com> |
11 | 3 Teromene <teromene@teromene.fr> | 11 | 3 Teromene <teromene@teromene.fr> |
@@ -38,3 +38,4 @@ | |||
38 | 1 Sbgodin <Sbgodin@users.noreply.github.com> | 38 | 1 Sbgodin <Sbgodin@users.noreply.github.com> |
39 | 1 TsT <tst2005@gmail.com> | 39 | 1 TsT <tst2005@gmail.com> |
40 | 1 dimtion <zizou.xena@gmail.com> | 40 | 1 dimtion <zizou.xena@gmail.com> |
41 | 1 philipp-r <philipp-r@users.noreply.github.com> | ||
diff --git a/CHANGELOG.md b/CHANGELOG.md index f0813b97..c63337ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md | |||
@@ -5,7 +5,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) | |||
5 | and this project adheres to [Semantic Versioning](http://semver.org/). | 5 | and this project adheres to [Semantic Versioning](http://semver.org/). |
6 | 6 | ||
7 | 7 | ||
8 | ## [v0.9.0](https://github.com/shaarli/Shaarli/releases/tag/v0.9.0) - UNPUBLISHED | 8 | ## [v0.9.0](https://github.com/shaarli/Shaarli/releases/tag/v0.9.0) - 2017-05-07 |
9 | 9 | ||
10 | This release introduces the REST API, and requires updating HTTP server | 10 | This release introduces the REST API, and requires updating HTTP server |
11 | configuration to enable URL rewriting, see: | 11 | configuration to enable URL rewriting, see: |
@@ -21,6 +21,7 @@ configuration to enable URL rewriting, see: | |||
21 | - versioned API endpoints: | 21 | - versioned API endpoints: |
22 | - `/api/v1/info`: get general information on the Shaarli instance | 22 | - `/api/v1/info`: get general information on the Shaarli instance |
23 | - `/api/v1/links`: get a list of shaared links | 23 | - `/api/v1/links`: get a list of shaared links |
24 | - `/api/v1/history`: get a list of latest actions | ||
24 | Theming: | 25 | Theming: |
25 | - Introduce a new theme | 26 | - Introduce a new theme |
26 | - Allow selecting themes/templates from the configuration page | 27 | - Allow selecting themes/templates from the configuration page |
@@ -31,6 +32,8 @@ Theming: | |||
31 | - Add `campaign_` to the URL cleanup pattern list | 32 | - Add `campaign_` to the URL cleanup pattern list |
32 | - Add an AUTHORS file and Makefile target to list authors from Git commit data | 33 | - Add an AUTHORS file and Makefile target to list authors from Git commit data |
33 | - Link imports are now logged in `data/` folder, and can be debug using `dev.debug=true` setting. | 34 | - Link imports are now logged in `data/` folder, and can be debug using `dev.debug=true` setting. |
35 | - `composer.lock` is now included in git file to allow proper `composer install` | ||
36 | - History mechanism which logs link addition/modification/deletion | ||
34 | 37 | ||
35 | ### Changed | 38 | ### Changed |
36 | - Docker: enable nginx URL rewriting for the REST API | 39 | - Docker: enable nginx URL rewriting for the REST API |
@@ -40,6 +43,8 @@ Theming: | |||
40 | - Rename the legacy theme to `vintage` | 43 | - Rename the legacy theme to `vintage` |
41 | - Private only filter is now displayed as a search parameter | 44 | - Private only filter is now displayed as a search parameter |
42 | - Autocomplete: pre-select the first element | 45 | - Autocomplete: pre-select the first element |
46 | - Display daily date in the page title (browser title) | ||
47 | - Timezone lists are now passed as an array instead of raw HTML | ||
43 | - Move PubSubHub to a dedicated plugin | 48 | - Move PubSubHub to a dedicated plugin |
44 | - Coding style: | 49 | - Coding style: |
45 | - explicit method visibility | 50 | - explicit method visibility |
@@ -50,9 +55,13 @@ Theming: | |||
50 | - Improved client locale detection | 55 | - Improved client locale detection |
51 | - Improved date time display depending on the locale | 56 | - Improved date time display depending on the locale |
52 | - Partial namespace support for Shaarli classes | 57 | - Partial namespace support for Shaarli classes |
58 | - Shaarli version is now only present in `shaarli_version.php` | ||
59 | - Human readable maximum file size upload | ||
60 | |||
53 | 61 | ||
54 | ### Removed | 62 | ### Removed |
55 | - PHP < 5.5 compatibility | 63 | - PHP < 5.5 compatibility |
64 | - ReadItYourself plugin | ||
56 | 65 | ||
57 | ### Fixed | 66 | ### Fixed |
58 | - Ignore generated release tarballs | 67 | - Ignore generated release tarballs |
@@ -67,6 +76,7 @@ Theming: | |||
67 | - Remove extra spaces in the bookmarklet's name | 76 | - Remove extra spaces in the bookmarklet's name |
68 | - Piwik plugin: Piwik URL protocol can now be set (http or https) | 77 | - Piwik plugin: Piwik URL protocol can now be set (http or https) |
69 | - All inline JS has been moved to dedicated JS files | 78 | - All inline JS has been moved to dedicated JS files |
79 | - Keep tags after login redirection | ||
70 | 80 | ||
71 | ### Security | 81 | ### Security |
72 | - Markdown plugin: escape HTML entities by default | 82 | - Markdown plugin: escape HTML entities by default |
@@ -6,10 +6,10 @@ _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/badge/stable-v0.7.1-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.7.1) | 9 | [![](https://img.shields.io/badge/stable-v0.8.4-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.8.4) |
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 | • | 11 | • |
12 | [![](https://img.shields.io/badge/latest-v0.8.4-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.8.4) | 12 | [![](https://img.shields.io/badge/latest-v0.9.0-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.9.0) |
13 | [![](https://img.shields.io/travis/shaarli/Shaarli/latest.svg?label=latest)](https://travis-ci.org/shaarli/Shaarli) | 13 | [![](https://img.shields.io/travis/shaarli/Shaarli/latest.svg?label=latest)](https://travis-ci.org/shaarli/Shaarli) |
14 | • | 14 | • |
15 | [![](https://img.shields.io/badge/master-v0.9.x-blue.svg)](https://github.com/shaarli/Shaarli) | 15 | [![](https://img.shields.io/badge/master-v0.9.x-blue.svg)](https://github.com/shaarli/Shaarli) |
@@ -85,6 +85,12 @@ dailymotion, flickr, imageshack, imgur, vimeo, xkcd, youtube... | |||
85 | - URL cleanup: automatic removal of `?utm_source=...`, `fb=...` | 85 | - URL cleanup: automatic removal of `?utm_source=...`, `fb=...` |
86 | - discreet pop-up notification when a new release is available | 86 | - discreet pop-up notification when a new release is available |
87 | 87 | ||
88 | ### REST API | ||
89 | |||
90 | Easily extensible by any client using the REST API exposed by Shaarli. | ||
91 | |||
92 | See the [API documentation](http://shaarli.github.io/api-documentation/). | ||
93 | |||
88 | ### Other usages | 94 | ### Other usages |
89 | Though Shaarli is primarily a bookmarking application, it can serve other purposes | 95 | Though Shaarli is primarily a bookmarking application, it can serve other purposes |
90 | (see [usage examples](https://github.com/shaarli/Shaarli/wiki#usage-examples)): | 96 | (see [usage examples](https://github.com/shaarli/Shaarli/wiki#usage-examples)): |
diff --git a/application/FileUtils.php b/application/FileUtils.php index 6cac9825..a167f642 100644 --- a/application/FileUtils.php +++ b/application/FileUtils.php | |||
@@ -1,21 +1,76 @@ | |||
1 | <?php | 1 | <?php |
2 | |||
3 | require_once 'exceptions/IOException.php'; | ||
4 | |||
2 | /** | 5 | /** |
3 | * Exception class thrown when a filesystem access failure happens | 6 | * Class FileUtils |
7 | * | ||
8 | * Utility class for file manipulation. | ||
4 | */ | 9 | */ |
5 | class IOException extends Exception | 10 | class FileUtils |
6 | { | 11 | { |
7 | private $path; | 12 | /** |
13 | * @var string | ||
14 | */ | ||
15 | protected static $phpPrefix = '<?php /* '; | ||
16 | |||
17 | /** | ||
18 | * @var string | ||
19 | */ | ||
20 | protected static $phpSuffix = ' */ ?>'; | ||
8 | 21 | ||
9 | /** | 22 | /** |
10 | * Construct a new IOException | 23 | * Write data into a file (Shaarli database format). |
24 | * The data is stored in a PHP file, as a comment, in compressed base64 format. | ||
25 | * | ||
26 | * The file will be created if it doesn't exist. | ||
27 | * | ||
28 | * @param string $file File path. | ||
29 | * @param mixed $content Content to write. | ||
30 | * | ||
31 | * @return int|bool Number of bytes written or false if it fails. | ||
11 | * | 32 | * |
12 | * @param string $path path to the resource that cannot be accessed | 33 | * @throws IOException The destination file can't be written. |
13 | * @param string $message Custom exception message. | ||
14 | */ | 34 | */ |
15 | public function __construct($path, $message = '') | 35 | public static function writeFlatDB($file, $content) |
16 | { | 36 | { |
17 | $this->path = $path; | 37 | if (is_file($file) && !is_writeable($file)) { |
18 | $this->message = empty($message) ? 'Error accessing' : $message; | 38 | // The datastore exists but is not writeable |
19 | $this->message .= PHP_EOL . $this->path; | 39 | throw new IOException($file); |
40 | } else if (!is_file($file) && !is_writeable(dirname($file))) { | ||
41 | // The datastore does not exist and its parent directory is not writeable | ||
42 | throw new IOException(dirname($file)); | ||
43 | } | ||
44 | |||
45 | return file_put_contents( | ||
46 | $file, | ||
47 | self::$phpPrefix.base64_encode(gzdeflate(serialize($content))).self::$phpSuffix | ||
48 | ); | ||
49 | } | ||
50 | |||
51 | /** | ||
52 | * Read data from a file containing Shaarli database format content. | ||
53 | * If the file isn't readable or doesn't exists, default data will be returned. | ||
54 | * | ||
55 | * @param string $file File path. | ||
56 | * @param mixed $default The default value to return if the file isn't readable. | ||
57 | * | ||
58 | * @return mixed The content unserialized, or default if the file isn't readable, or false if it fails. | ||
59 | */ | ||
60 | public static function readFlatDB($file, $default = null) | ||
61 | { | ||
62 | // Note that gzinflate is faster than gzuncompress. | ||
63 | // See: http://www.php.net/manual/en/function.gzdeflate.php#96439 | ||
64 | if (is_readable($file)) { | ||
65 | return unserialize( | ||
66 | gzinflate( | ||
67 | base64_decode( | ||
68 | substr(file_get_contents($file), strlen(self::$phpPrefix), -strlen(self::$phpSuffix)) | ||
69 | ) | ||
70 | ) | ||
71 | ); | ||
72 | } | ||
73 | |||
74 | return $default; | ||
20 | } | 75 | } |
21 | } | 76 | } |
diff --git a/application/History.php b/application/History.php new file mode 100644 index 00000000..116b9264 --- /dev/null +++ b/application/History.php | |||
@@ -0,0 +1,200 @@ | |||
1 | <?php | ||
2 | |||
3 | /** | ||
4 | * Class History | ||
5 | * | ||
6 | * Handle the history file tracing events in Shaarli. | ||
7 | * The history is stored as JSON in a file set by 'resource.history' setting. | ||
8 | * | ||
9 | * Available data: | ||
10 | * - event: event key | ||
11 | * - datetime: event date, in ISO8601 format. | ||
12 | * - id: event item identifier (currently only link IDs). | ||
13 | * | ||
14 | * Available event keys: | ||
15 | * - CREATED: new link | ||
16 | * - UPDATED: link updated | ||
17 | * - DELETED: link deleted | ||
18 | * - SETTINGS: the settings have been updated through the UI. | ||
19 | * | ||
20 | * Note: new events are put at the beginning of the file and history array. | ||
21 | */ | ||
22 | class History | ||
23 | { | ||
24 | /** | ||
25 | * @var string Action key: a new link has been created. | ||
26 | */ | ||
27 | const CREATED = 'CREATED'; | ||
28 | |||
29 | /** | ||
30 | * @var string Action key: a link has been updated. | ||
31 | */ | ||
32 | const UPDATED = 'UPDATED'; | ||
33 | |||
34 | /** | ||
35 | * @var string Action key: a link has been deleted. | ||
36 | */ | ||
37 | const DELETED = 'DELETED'; | ||
38 | |||
39 | /** | ||
40 | * @var string Action key: settings have been updated. | ||
41 | */ | ||
42 | const SETTINGS = 'SETTINGS'; | ||
43 | |||
44 | /** | ||
45 | * @var string History file path. | ||
46 | */ | ||
47 | protected $historyFilePath; | ||
48 | |||
49 | /** | ||
50 | * @var array History data. | ||
51 | */ | ||
52 | protected $history; | ||
53 | |||
54 | /** | ||
55 | * @var int History retention time in seconds (1 month). | ||
56 | */ | ||
57 | protected $retentionTime = 2678400; | ||
58 | |||
59 | /** | ||
60 | * History constructor. | ||
61 | * | ||
62 | * @param string $historyFilePath History file path. | ||
63 | * @param int $retentionTime History content rentention time in seconds. | ||
64 | * | ||
65 | * @throws Exception if something goes wrong. | ||
66 | */ | ||
67 | public function __construct($historyFilePath, $retentionTime = null) | ||
68 | { | ||
69 | $this->historyFilePath = $historyFilePath; | ||
70 | if ($retentionTime !== null) { | ||
71 | $this->retentionTime = $retentionTime; | ||
72 | } | ||
73 | } | ||
74 | |||
75 | /** | ||
76 | * Initialize: read history file. | ||
77 | * | ||
78 | * Allow lazy loading (don't read the file if it isn't necessary). | ||
79 | */ | ||
80 | protected function initialize() | ||
81 | { | ||
82 | $this->check(); | ||
83 | $this->read(); | ||
84 | } | ||
85 | |||
86 | /** | ||
87 | * Add Event: new link. | ||
88 | * | ||
89 | * @param array $link Link data. | ||
90 | */ | ||
91 | public function addLink($link) | ||
92 | { | ||
93 | $this->addEvent(self::CREATED, $link['id']); | ||
94 | } | ||
95 | |||
96 | /** | ||
97 | * Add Event: update existing link. | ||
98 | * | ||
99 | * @param array $link Link data. | ||
100 | */ | ||
101 | public function updateLink($link) | ||
102 | { | ||
103 | $this->addEvent(self::UPDATED, $link['id']); | ||
104 | } | ||
105 | |||
106 | /** | ||
107 | * Add Event: delete existing link. | ||
108 | * | ||
109 | * @param array $link Link data. | ||
110 | */ | ||
111 | public function deleteLink($link) | ||
112 | { | ||
113 | $this->addEvent(self::DELETED, $link['id']); | ||
114 | } | ||
115 | |||
116 | /** | ||
117 | * Add Event: settings updated. | ||
118 | */ | ||
119 | public function updateSettings() | ||
120 | { | ||
121 | $this->addEvent(self::SETTINGS); | ||
122 | } | ||
123 | |||
124 | /** | ||
125 | * Save a new event and write it in the history file. | ||
126 | * | ||
127 | * @param string $status Event key, should be defined as constant. | ||
128 | * @param mixed $id Event item identifier (e.g. link ID). | ||
129 | */ | ||
130 | protected function addEvent($status, $id = null) | ||
131 | { | ||
132 | if ($this->history === null) { | ||
133 | $this->initialize(); | ||
134 | } | ||
135 | |||
136 | $item = [ | ||
137 | 'event' => $status, | ||
138 | 'datetime' => new DateTime(), | ||
139 | 'id' => $id !== null ? $id : '', | ||
140 | ]; | ||
141 | $this->history = array_merge([$item], $this->history); | ||
142 | $this->write(); | ||
143 | } | ||
144 | |||
145 | /** | ||
146 | * Check that the history file is writable. | ||
147 | * Create the file if it doesn't exist. | ||
148 | * | ||
149 | * @throws Exception if it isn't writable. | ||
150 | */ | ||
151 | protected function check() | ||
152 | { | ||
153 | if (! is_file($this->historyFilePath)) { | ||
154 | FileUtils::writeFlatDB($this->historyFilePath, []); | ||
155 | } | ||
156 | |||
157 | if (! is_writable($this->historyFilePath)) { | ||
158 | throw new Exception('History file isn\'t readable or writable'); | ||
159 | } | ||
160 | } | ||
161 | |||
162 | /** | ||
163 | * Read JSON history file. | ||
164 | */ | ||
165 | protected function read() | ||
166 | { | ||
167 | $this->history = FileUtils::readFlatDB($this->historyFilePath, []); | ||
168 | if ($this->history === false) { | ||
169 | throw new Exception('Could not parse history file'); | ||
170 | } | ||
171 | } | ||
172 | |||
173 | /** | ||
174 | * Write JSON history file and delete old entries. | ||
175 | */ | ||
176 | protected function write() | ||
177 | { | ||
178 | $comparaison = new DateTime('-'. $this->retentionTime . ' seconds'); | ||
179 | foreach ($this->history as $key => $value) { | ||
180 | if ($value['datetime'] < $comparaison) { | ||
181 | unset($this->history[$key]); | ||
182 | } | ||
183 | } | ||
184 | FileUtils::writeFlatDB($this->historyFilePath, array_values($this->history)); | ||
185 | } | ||
186 | |||
187 | /** | ||
188 | * Get the History. | ||
189 | * | ||
190 | * @return array | ||
191 | */ | ||
192 | public function getHistory() | ||
193 | { | ||
194 | if ($this->history === null) { | ||
195 | $this->initialize(); | ||
196 | } | ||
197 | |||
198 | return $this->history; | ||
199 | } | ||
200 | } | ||
diff --git a/application/LinkDB.php b/application/LinkDB.php index 4cee2af9..7802cc8a 100644 --- a/application/LinkDB.php +++ b/application/LinkDB.php | |||
@@ -50,12 +50,6 @@ class LinkDB implements Iterator, Countable, ArrayAccess | |||
50 | // Link date storage format | 50 | // Link date storage format |
51 | const LINK_DATE_FORMAT = 'Ymd_His'; | 51 | const LINK_DATE_FORMAT = 'Ymd_His'; |
52 | 52 | ||
53 | // Datastore PHP prefix | ||
54 | protected static $phpPrefix = '<?php /* '; | ||
55 | |||
56 | // Datastore PHP suffix | ||
57 | protected static $phpSuffix = ' */ ?>'; | ||
58 | |||
59 | // List of links (associative array) | 53 | // List of links (associative array) |
60 | // - key: link date (e.g. "20110823_124546"), | 54 | // - key: link date (e.g. "20110823_124546"), |
61 | // - value: associative array (keys: title, description...) | 55 | // - value: associative array (keys: title, description...) |
@@ -144,10 +138,10 @@ class LinkDB implements Iterator, Countable, ArrayAccess | |||
144 | if (!isset($value['id']) || empty($value['url'])) { | 138 | if (!isset($value['id']) || empty($value['url'])) { |
145 | 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.'); |
146 | } | 140 | } |
147 | if ((! empty($offset) && ! is_int($offset)) || ! is_int($value['id'])) { | 141 | if (($offset !== null && ! is_int($offset)) || ! is_int($value['id'])) { |
148 | die('You must specify an integer as a key.'); | 142 | die('You must specify an integer as a key.'); |
149 | } | 143 | } |
150 | if (! empty($offset) && $offset !== $value['id']) { | 144 | if ($offset !== null && $offset !== $value['id']) { |
151 | die('Array offset and link ID must be equal.'); | 145 | die('Array offset and link ID must be equal.'); |
152 | } | 146 | } |
153 | 147 | ||
@@ -295,16 +289,7 @@ You use the community supported version of the original Shaarli project, by Seba | |||
295 | return; | 289 | return; |
296 | } | 290 | } |
297 | 291 | ||
298 | // Read data | 292 | $this->links = FileUtils::readFlatDB($this->datastore, []); |
299 | // Note that gzinflate is faster than gzuncompress. | ||
300 | // See: http://www.php.net/manual/en/function.gzdeflate.php#96439 | ||
301 | $this->links = array(); | ||
302 | |||
303 | if (file_exists($this->datastore)) { | ||
304 | $this->links = unserialize(gzinflate(base64_decode( | ||
305 | substr(file_get_contents($this->datastore), | ||
306 | strlen(self::$phpPrefix), -strlen(self::$phpSuffix))))); | ||
307 | } | ||
308 | 293 | ||
309 | $toremove = array(); | 294 | $toremove = array(); |
310 | foreach ($this->links as $key => &$link) { | 295 | foreach ($this->links as $key => &$link) { |
@@ -361,19 +346,7 @@ You use the community supported version of the original Shaarli project, by Seba | |||
361 | */ | 346 | */ |
362 | private function write() | 347 | private function write() |
363 | { | 348 | { |
364 | if (is_file($this->datastore) && !is_writeable($this->datastore)) { | 349 | FileUtils::writeFlatDB($this->datastore, $this->links); |
365 | // The datastore exists but is not writeable | ||
366 | throw new IOException($this->datastore); | ||
367 | } else if (!is_file($this->datastore) && !is_writeable(dirname($this->datastore))) { | ||
368 | // The datastore does not exist and its parent directory is not writeable | ||
369 | throw new IOException(dirname($this->datastore)); | ||
370 | } | ||
371 | |||
372 | file_put_contents( | ||
373 | $this->datastore, | ||
374 | self::$phpPrefix.base64_encode(gzdeflate(serialize($this->links))).self::$phpSuffix | ||
375 | ); | ||
376 | |||
377 | } | 350 | } |
378 | 351 | ||
379 | /** | 352 | /** |
@@ -479,14 +452,17 @@ You use the community supported version of the original Shaarli project, by Seba | |||
479 | } | 452 | } |
480 | 453 | ||
481 | /** | 454 | /** |
482 | * Returns the list of all tags | 455 | * Returns the list tags appearing in the links with the given tags |
483 | * Output: associative array key=tags, value=0 | 456 | * @param $filteringTags: tags selecting the links to consider |
457 | * @param $visibility: process only all/private/public links | ||
458 | * @return: a tag=>linksCount array | ||
484 | */ | 459 | */ |
485 | public function allTags() | 460 | public function linksCountPerTag($filteringTags = [], $visibility = 'all') |
486 | { | 461 | { |
462 | $links = empty($filteringTags) ? $this->links : $this->filterSearch(['searchtags' => $filteringTags], false, $visibility); | ||
487 | $tags = array(); | 463 | $tags = array(); |
488 | $caseMapping = array(); | 464 | $caseMapping = array(); |
489 | foreach ($this->links as $link) { | 465 | foreach ($links as $link) { |
490 | foreach (preg_split('/\s+/', $link['tags'], 0, PREG_SPLIT_NO_EMPTY) as $tag) { | 466 | foreach (preg_split('/\s+/', $link['tags'], 0, PREG_SPLIT_NO_EMPTY) as $tag) { |
491 | if (empty($tag)) { | 467 | if (empty($tag)) { |
492 | continue; | 468 | continue; |
diff --git a/application/NetscapeBookmarkUtils.php b/application/NetscapeBookmarkUtils.php index ab346f81..2a10ff22 100644 --- a/application/NetscapeBookmarkUtils.php +++ b/application/NetscapeBookmarkUtils.php | |||
@@ -95,10 +95,11 @@ class NetscapeBookmarkUtils | |||
95 | * @param array $files Server $_FILES parameters | 95 | * @param array $files Server $_FILES parameters |
96 | * @param LinkDB $linkDb Loaded LinkDB instance | 96 | * @param LinkDB $linkDb Loaded LinkDB instance |
97 | * @param ConfigManager $conf instance | 97 | * @param ConfigManager $conf instance |
98 | * @param History $history History instance | ||
98 | * | 99 | * |
99 | * @return string Summary of the bookmark import status | 100 | * @return string Summary of the bookmark import status |
100 | */ | 101 | */ |
101 | public static function import($post, $files, $linkDb, $conf) | 102 | public static function import($post, $files, $linkDb, $conf, $history) |
102 | { | 103 | { |
103 | $filename = $files['filetoupload']['name']; | 104 | $filename = $files['filetoupload']['name']; |
104 | $filesize = $files['filetoupload']['size']; | 105 | $filesize = $files['filetoupload']['size']; |
@@ -179,9 +180,11 @@ class NetscapeBookmarkUtils | |||
179 | $newLink['id'] = $existingLink['id']; | 180 | $newLink['id'] = $existingLink['id']; |
180 | $newLink['created'] = $existingLink['created']; | 181 | $newLink['created'] = $existingLink['created']; |
181 | $newLink['updated'] = new DateTime(); | 182 | $newLink['updated'] = new DateTime(); |
183 | $newLink['shorturl'] = $existingLink['shorturl']; | ||
182 | $linkDb[$existingLink['id']] = $newLink; | 184 | $linkDb[$existingLink['id']] = $newLink; |
183 | $importCount++; | 185 | $importCount++; |
184 | $overwriteCount++; | 186 | $overwriteCount++; |
187 | $history->updateLink($newLink); | ||
185 | continue; | 188 | continue; |
186 | } | 189 | } |
187 | 190 | ||
@@ -193,6 +196,7 @@ class NetscapeBookmarkUtils | |||
193 | $newLink['shorturl'] = link_small_hash($newLink['created'], $newLink['id']); | 196 | $newLink['shorturl'] = link_small_hash($newLink['created'], $newLink['id']); |
194 | $linkDb[$newLink['id']] = $newLink; | 197 | $linkDb[$newLink['id']] = $newLink; |
195 | $importCount++; | 198 | $importCount++; |
199 | $history->addLink($newLink); | ||
196 | } | 200 | } |
197 | 201 | ||
198 | $linkDb->save($conf->get('resource.page_cache')); | 202 | $linkDb->save($conf->get('resource.page_cache')); |
diff --git a/application/PageBuilder.php b/application/PageBuilder.php index b133dee8..c86621a2 100644 --- a/application/PageBuilder.php +++ b/application/PageBuilder.php | |||
@@ -1,5 +1,7 @@ | |||
1 | <?php | 1 | <?php |
2 | 2 | ||
3 | use 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.) |
@@ -20,15 +22,22 @@ class PageBuilder | |||
20 | protected $conf; | 22 | protected $conf; |
21 | 23 | ||
22 | /** | 24 | /** |
25 | * @var LinkDB $linkDB instance. | ||
26 | */ | ||
27 | protected $linkDB; | ||
28 | |||
29 | /** | ||
23 | * PageBuilder constructor. | 30 | * PageBuilder constructor. |
24 | * $tpl is initialized at false for lazy loading. | 31 | * $tpl is initialized at false for lazy loading. |
25 | * | 32 | * |
26 | * @param ConfigManager $conf Configuration Manager instance (reference). | 33 | * @param ConfigManager $conf Configuration Manager instance (reference). |
34 | * @param LinkDB $linkDB instance. | ||
27 | */ | 35 | */ |
28 | public function __construct(&$conf) | 36 | public function __construct(&$conf, $linkDB = null) |
29 | { | 37 | { |
30 | $this->tpl = false; | 38 | $this->tpl = false; |
31 | $this->conf = $conf; | 39 | $this->conf = $conf; |
40 | $this->linkDB = $linkDB; | ||
32 | } | 41 | } |
33 | 42 | ||
34 | /** | 43 | /** |
@@ -79,6 +88,9 @@ class PageBuilder | |||
79 | $this->tpl->assign('feed_type', $this->conf->get('feed.show_atom', true) !== false ? 'atom' : 'rss'); | 88 | $this->tpl->assign('feed_type', $this->conf->get('feed.show_atom', true) !== false ? 'atom' : 'rss'); |
80 | $this->tpl->assign('hide_timestamps', $this->conf->get('privacy.hide_timestamps', false)); | 89 | $this->tpl->assign('hide_timestamps', $this->conf->get('privacy.hide_timestamps', false)); |
81 | $this->tpl->assign('token', getToken($this->conf)); | 90 | $this->tpl->assign('token', getToken($this->conf)); |
91 | if ($this->linkDB !== null) { | ||
92 | $this->tpl->assign('tags', $this->linkDB->linksCountPerTag()); | ||
93 | } | ||
82 | // To be removed with a proper theme configuration. | 94 | // To be removed with a proper theme configuration. |
83 | $this->tpl->assign('conf', $this->conf); | 95 | $this->tpl->assign('conf', $this->conf); |
84 | } | 96 | } |
diff --git a/application/Router.php b/application/Router.php index c9a51912..4df0387c 100644 --- a/application/Router.php +++ b/application/Router.php | |||
@@ -13,6 +13,8 @@ class Router | |||
13 | 13 | ||
14 | public static $PAGE_TAGCLOUD = 'tagcloud'; | 14 | public static $PAGE_TAGCLOUD = 'tagcloud'; |
15 | 15 | ||
16 | public static $PAGE_TAGLIST = 'taglist'; | ||
17 | |||
16 | public static $PAGE_DAILY = 'daily'; | 18 | public static $PAGE_DAILY = 'daily'; |
17 | 19 | ||
18 | public static $PAGE_FEED_ATOM = 'atom'; | 20 | public static $PAGE_FEED_ATOM = 'atom'; |
@@ -45,6 +47,8 @@ class Router | |||
45 | 47 | ||
46 | public static $PAGE_SAVE_PLUGINSADMIN = 'save_pluginadmin'; | 48 | public static $PAGE_SAVE_PLUGINSADMIN = 'save_pluginadmin'; |
47 | 49 | ||
50 | public static $GET_TOKEN = 'token'; | ||
51 | |||
48 | /** | 52 | /** |
49 | * Reproducing renderPage() if hell, to avoid regression. | 53 | * Reproducing renderPage() if hell, to avoid regression. |
50 | * | 54 | * |
@@ -77,6 +81,10 @@ class Router | |||
77 | return self::$PAGE_TAGCLOUD; | 81 | return self::$PAGE_TAGCLOUD; |
78 | } | 82 | } |
79 | 83 | ||
84 | if (startsWith($query, 'do='. self::$PAGE_TAGLIST)) { | ||
85 | return self::$PAGE_TAGLIST; | ||
86 | } | ||
87 | |||
80 | if (startsWith($query, 'do='. self::$PAGE_OPENSEARCH)) { | 88 | if (startsWith($query, 'do='. self::$PAGE_OPENSEARCH)) { |
81 | return self::$PAGE_OPENSEARCH; | 89 | return self::$PAGE_OPENSEARCH; |
82 | } | 90 | } |
@@ -142,6 +150,10 @@ class Router | |||
142 | return self::$PAGE_SAVE_PLUGINSADMIN; | 150 | return self::$PAGE_SAVE_PLUGINSADMIN; |
143 | } | 151 | } |
144 | 152 | ||
153 | if (startsWith($query, 'do='. self::$GET_TOKEN)) { | ||
154 | return self::$GET_TOKEN; | ||
155 | } | ||
156 | |||
145 | return self::$PAGE_LINKLIST; | 157 | return self::$PAGE_LINKLIST; |
146 | } | 158 | } |
147 | } | 159 | } |
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 | **/ |
14 | function generateTimeZoneForm($preselectedTimezone='') | 38 | function 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 .= ' 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 0fb68c5a..40a15906 100644 --- a/application/Updater.php +++ b/application/Updater.php | |||
@@ -329,21 +329,6 @@ class Updater | |||
329 | } | 329 | } |
330 | 330 | ||
331 | /** | 331 | /** |
332 | * While the new default theme is in an unstable state | ||
333 | * continue to use the vintage theme | ||
334 | */ | ||
335 | public function updateMethodDefaultThemeVintage() | ||
336 | { | ||
337 | if ($this->conf->get('resource.theme') !== 'default') { | ||
338 | return true; | ||
339 | } | ||
340 | $this->conf->set('resource.theme', 'vintage'); | ||
341 | $this->conf->write($this->isLoggedIn); | ||
342 | |||
343 | return true; | ||
344 | } | ||
345 | |||
346 | /** | ||
347 | * * `markdown_escape` is a new setting, set to true as default. | 332 | * * `markdown_escape` is a new setting, set to true as default. |
348 | * | 333 | * |
349 | * If the markdown plugin was already enabled, escaping is disabled to avoid | 334 | * If the markdown plugin was already enabled, escaping is disabled to avoid |
@@ -440,6 +425,17 @@ class Updater | |||
440 | $this->conf->write($this->isLoggedIn); | 425 | $this->conf->write($this->isLoggedIn); |
441 | return true; | 426 | return true; |
442 | } | 427 | } |
428 | |||
429 | /** | ||
430 | * Reset history store file due to date format change. | ||
431 | */ | ||
432 | public function updateMethodResetHistoryFile() | ||
433 | { | ||
434 | if (is_file($this->conf->get('resource.history'))) { | ||
435 | unlink($this->conf->get('resource.history')); | ||
436 | } | ||
437 | return true; | ||
438 | } | ||
443 | } | 439 | } |
444 | 440 | ||
445 | /** | 441 | /** |
diff --git a/application/Utils.php b/application/Utils.php index 5c077450..9d0ebc5e 100644 --- a/application/Utils.php +++ b/application/Utils.php | |||
@@ -321,25 +321,148 @@ 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 | */ |
328 | function format_date($date, $intl = true) | 329 | function 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 | */ | ||
360 | function 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 | */ | ||
376 | function 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 | */ | ||
399 | function 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 | */ | ||
430 | function 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 | } | ||
438 | |||
439 | /** | ||
440 | * Sort the given array alphabetically using php-intl if available. | ||
441 | * Case sensitive. | ||
442 | * | ||
443 | * Note: doesn't support multidimensional arrays | ||
444 | * | ||
445 | * @param array $data Input array, passed by reference | ||
446 | * @param bool $reverse Reverse sort if set to true | ||
447 | * @param bool $byKeys Sort the array by keys if set to true, by value otherwise. | ||
448 | */ | ||
449 | function alphabetical_sort(&$data, $reverse = false, $byKeys = false) | ||
450 | { | ||
451 | $callback = function($a, $b) use ($reverse) { | ||
452 | // Collator is part of PHP intl. | ||
453 | if (class_exists('Collator')) { | ||
454 | $collator = new Collator(setlocale(LC_COLLATE, 0)); | ||
455 | if (!intl_is_failure(intl_get_error_code())) { | ||
456 | return $collator->compare($a, $b) * ($reverse ? -1 : 1); | ||
457 | } | ||
458 | } | ||
459 | |||
460 | return strcasecmp($a, $b) * ($reverse ? -1 : 1); | ||
461 | }; | ||
462 | |||
463 | if ($byKeys) { | ||
464 | uksort($data, $callback); | ||
465 | } else { | ||
466 | usort($data, $callback); | ||
467 | } | ||
468 | } | ||
diff --git a/application/api/ApiMiddleware.php b/application/api/ApiMiddleware.php index 4120f7a9..ff209393 100644 --- a/application/api/ApiMiddleware.php +++ b/application/api/ApiMiddleware.php | |||
@@ -4,6 +4,7 @@ namespace Shaarli\Api; | |||
4 | use Shaarli\Api\Exceptions\ApiException; | 4 | use Shaarli\Api\Exceptions\ApiException; |
5 | use Shaarli\Api\Exceptions\ApiAuthorizationException; | 5 | use Shaarli\Api\Exceptions\ApiAuthorizationException; |
6 | 6 | ||
7 | use Shaarli\Config\ConfigManager; | ||
7 | use Slim\Container; | 8 | use Slim\Container; |
8 | use Slim\Http\Request; | 9 | use Slim\Http\Request; |
9 | use Slim\Http\Response; | 10 | use Slim\Http\Response; |
@@ -31,7 +32,7 @@ class ApiMiddleware | |||
31 | protected $container; | 32 | protected $container; |
32 | 33 | ||
33 | /** | 34 | /** |
34 | * @var \ConfigManager instance. | 35 | * @var ConfigManager instance. |
35 | */ | 36 | */ |
36 | protected $conf; | 37 | protected $conf; |
37 | 38 | ||
@@ -121,7 +122,7 @@ class ApiMiddleware | |||
121 | * | 122 | * |
122 | * FIXME! LinkDB could use a refactoring to avoid this trick. | 123 | * FIXME! LinkDB could use a refactoring to avoid this trick. |
123 | * | 124 | * |
124 | * @param \ConfigManager $conf instance. | 125 | * @param ConfigManager $conf instance. |
125 | */ | 126 | */ |
126 | protected function setLinkDb($conf) | 127 | protected function setLinkDb($conf) |
127 | { | 128 | { |
diff --git a/application/api/ApiUtils.php b/application/api/ApiUtils.php index d4015865..f154bb52 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,61 @@ 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 | } | ||
111 | |||
112 | /** | ||
113 | * Update link fields using an updated link object. | ||
114 | * | ||
115 | * @param array $oldLink data | ||
116 | * @param array $newLink data | ||
117 | * | ||
118 | * @return array $oldLink updated with $newLink values | ||
119 | */ | ||
120 | public static function updateLink($oldLink, $newLink) | ||
121 | { | ||
122 | foreach (['title', 'url', 'description', 'tags', 'private'] as $field) { | ||
123 | $oldLink[$field] = $newLink[$field]; | ||
124 | } | ||
125 | $oldLink['updated'] = new \DateTime(); | ||
126 | |||
127 | if (empty($oldLink['url'])) { | ||
128 | $oldLink['url'] = '?' . $oldLink['shorturl']; | ||
129 | } | ||
130 | |||
131 | if (empty($oldLink['title'])) { | ||
132 | $oldLink['title'] = $oldLink['url']; | ||
133 | } | ||
134 | |||
135 | return $oldLink; | ||
136 | } | ||
80 | } | 137 | } |
diff --git a/application/api/controllers/ApiController.php b/application/api/controllers/ApiController.php index 1dd47f17..3be85b98 100644 --- a/application/api/controllers/ApiController.php +++ b/application/api/controllers/ApiController.php | |||
@@ -2,6 +2,7 @@ | |||
2 | 2 | ||
3 | namespace Shaarli\Api\Controllers; | 3 | namespace Shaarli\Api\Controllers; |
4 | 4 | ||
5 | use Shaarli\Config\ConfigManager; | ||
5 | use \Slim\Container; | 6 | use \Slim\Container; |
6 | 7 | ||
7 | /** | 8 | /** |
@@ -19,7 +20,7 @@ abstract class ApiController | |||
19 | protected $ci; | 20 | protected $ci; |
20 | 21 | ||
21 | /** | 22 | /** |
22 | * @var \ConfigManager | 23 | * @var ConfigManager |
23 | */ | 24 | */ |
24 | protected $conf; | 25 | protected $conf; |
25 | 26 | ||
@@ -29,6 +30,11 @@ abstract class ApiController | |||
29 | protected $linkDb; | 30 | protected $linkDb; |
30 | 31 | ||
31 | /** | 32 | /** |
33 | * @var \History | ||
34 | */ | ||
35 | protected $history; | ||
36 | |||
37 | /** | ||
32 | * @var int|null JSON style option. | 38 | * @var int|null JSON style option. |
33 | */ | 39 | */ |
34 | protected $jsonStyle; | 40 | protected $jsonStyle; |
@@ -45,10 +51,21 @@ abstract class ApiController | |||
45 | $this->ci = $ci; | 51 | $this->ci = $ci; |
46 | $this->conf = $ci->get('conf'); | 52 | $this->conf = $ci->get('conf'); |
47 | $this->linkDb = $ci->get('db'); | 53 | $this->linkDb = $ci->get('db'); |
54 | $this->history = $ci->get('history'); | ||
48 | if ($this->conf->get('dev.debug', false)) { | 55 | if ($this->conf->get('dev.debug', false)) { |
49 | $this->jsonStyle = JSON_PRETTY_PRINT; | 56 | $this->jsonStyle = JSON_PRETTY_PRINT; |
50 | } else { | 57 | } else { |
51 | $this->jsonStyle = null; | 58 | $this->jsonStyle = null; |
52 | } | 59 | } |
53 | } | 60 | } |
61 | |||
62 | /** | ||
63 | * Get the container. | ||
64 | * | ||
65 | * @return Container | ||
66 | */ | ||
67 | public function getCi() | ||
68 | { | ||
69 | return $this->ci; | ||
70 | } | ||
54 | } | 71 | } |
diff --git a/application/api/controllers/History.php b/application/api/controllers/History.php new file mode 100644 index 00000000..2ff9deaf --- /dev/null +++ b/application/api/controllers/History.php | |||
@@ -0,0 +1,70 @@ | |||
1 | <?php | ||
2 | |||
3 | |||
4 | namespace Shaarli\Api\Controllers; | ||
5 | |||
6 | use Shaarli\Api\Exceptions\ApiBadParametersException; | ||
7 | use Slim\Http\Request; | ||
8 | use Slim\Http\Response; | ||
9 | |||
10 | /** | ||
11 | * Class History | ||
12 | * | ||
13 | * REST API Controller: /history | ||
14 | * | ||
15 | * @package Shaarli\Api\Controllers | ||
16 | */ | ||
17 | class History extends ApiController | ||
18 | { | ||
19 | /** | ||
20 | * Service providing operation regarding Shaarli datastore and settings. | ||
21 | * | ||
22 | * @param Request $request Slim request. | ||
23 | * @param Response $response Slim response. | ||
24 | * | ||
25 | * @return Response response. | ||
26 | * | ||
27 | * @throws ApiBadParametersException Invalid parameters. | ||
28 | */ | ||
29 | public function getHistory($request, $response) | ||
30 | { | ||
31 | $history = $this->history->getHistory(); | ||
32 | |||
33 | // Return history operations from the {offset}th, starting from {since}. | ||
34 | $since = \DateTime::createFromFormat(\DateTime::ATOM, $request->getParam('since')); | ||
35 | $offset = $request->getParam('offset'); | ||
36 | if (empty($offset)) { | ||
37 | $offset = 0; | ||
38 | } | ||
39 | else if (ctype_digit($offset)) { | ||
40 | $offset = (int) $offset; | ||
41 | } else { | ||
42 | throw new ApiBadParametersException('Invalid offset'); | ||
43 | } | ||
44 | |||
45 | // limit parameter is either a number of links or 'all' for everything. | ||
46 | $limit = $request->getParam('limit'); | ||
47 | if (empty($limit)) { | ||
48 | $limit = count($history); | ||
49 | } else if (ctype_digit($limit)) { | ||
50 | $limit = (int) $limit; | ||
51 | } else { | ||
52 | throw new ApiBadParametersException('Invalid limit'); | ||
53 | } | ||
54 | |||
55 | $out = []; | ||
56 | $i = 0; | ||
57 | foreach ($history as $entry) { | ||
58 | if ((! empty($since) && $entry['datetime'] <= $since) || count($out) >= $limit) { | ||
59 | break; | ||
60 | } | ||
61 | if (++$i > $offset) { | ||
62 | $out[$i] = $entry; | ||
63 | $out[$i]['datetime'] = $out[$i]['datetime']->format(\DateTime::ATOM); | ||
64 | } | ||
65 | } | ||
66 | $out = array_values($out); | ||
67 | |||
68 | return $response->withJson($out, 200, $this->jsonStyle); | ||
69 | } | ||
70 | } | ||
diff --git a/application/api/controllers/Links.php b/application/api/controllers/Links.php index d4f1a09c..eb78dd26 100644 --- a/application/api/controllers/Links.php +++ b/application/api/controllers/Links.php | |||
@@ -97,11 +97,121 @@ 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 | $this->history->addLink($link); | ||
145 | $out = ApiUtils::formatLink($link, index_url($this->ci['environment'])); | ||
146 | $redirect = $this->ci->router->relativePathFor('getLink', ['id' => $link['id']]); | ||
147 | return $response->withAddedHeader('Location', $redirect) | ||
148 | ->withJson($out, 201, $this->jsonStyle); | ||
149 | } | ||
150 | |||
151 | /** | ||
152 | * Updates an existing link from posted request body. | ||
153 | * | ||
154 | * @param Request $request Slim request. | ||
155 | * @param Response $response Slim response. | ||
156 | * @param array $args Path parameters. including the ID. | ||
157 | * | ||
158 | * @return Response response. | ||
159 | * | ||
160 | * @throws ApiLinkNotFoundException generating a 404 error. | ||
161 | */ | ||
162 | public function putLink($request, $response, $args) | ||
163 | { | ||
164 | if (! isset($this->linkDb[$args['id']])) { | ||
165 | throw new ApiLinkNotFoundException(); | ||
166 | } | ||
167 | |||
168 | $index = index_url($this->ci['environment']); | ||
169 | $data = $request->getParsedBody(); | ||
170 | |||
171 | $requestLink = ApiUtils::buildLinkFromRequest($data, $this->conf->get('privacy.default_private_links')); | ||
172 | // duplicate URL on a different link, return 409 Conflict | ||
173 | if (! empty($requestLink['url']) | ||
174 | && ! empty($dup = $this->linkDb->getLinkFromUrl($requestLink['url'])) | ||
175 | && $dup['id'] != $args['id'] | ||
176 | ) { | ||
177 | return $response->withJson( | ||
178 | ApiUtils::formatLink($dup, $index), | ||
179 | 409, | ||
180 | $this->jsonStyle | ||
181 | ); | ||
182 | } | ||
183 | |||
184 | $responseLink = $this->linkDb[$args['id']]; | ||
185 | $responseLink = ApiUtils::updateLink($responseLink, $requestLink); | ||
186 | $this->linkDb[$responseLink['id']] = $responseLink; | ||
187 | $this->linkDb->save($this->conf->get('resource.page_cache')); | ||
188 | $this->history->updateLink($responseLink); | ||
189 | |||
190 | $out = ApiUtils::formatLink($responseLink, $index); | ||
191 | return $response->withJson($out, 200, $this->jsonStyle); | ||
192 | } | ||
193 | |||
194 | /** | ||
195 | * Delete an existing link by its ID. | ||
196 | * | ||
197 | * @param Request $request Slim request. | ||
198 | * @param Response $response Slim response. | ||
199 | * @param array $args Path parameters. including the ID. | ||
200 | * | ||
201 | * @return Response response. | ||
202 | * | ||
203 | * @throws ApiLinkNotFoundException generating a 404 error. | ||
204 | */ | ||
205 | public function deleteLink($request, $response, $args) | ||
206 | { | ||
207 | if (! isset($this->linkDb[$args['id']])) { | ||
208 | throw new ApiLinkNotFoundException(); | ||
209 | } | ||
210 | $link = $this->linkDb[$args['id']]; | ||
211 | unset($this->linkDb[(int) $args['id']]); | ||
212 | $this->linkDb->save($this->conf->get('resource.page_cache')); | ||
213 | $this->history->deleteLink($link); | ||
214 | |||
215 | return $response->withStatus(204); | ||
216 | } | ||
107 | } | 217 | } |
diff --git a/application/config/ConfigManager.php b/application/config/ConfigManager.php index 7bfbfc72..86a917fb 100644 --- a/application/config/ConfigManager.php +++ b/application/config/ConfigManager.php | |||
@@ -301,6 +301,7 @@ class ConfigManager | |||
301 | $this->setEmpty('resource.updates', 'data/updates.txt'); | 301 | $this->setEmpty('resource.updates', 'data/updates.txt'); |
302 | $this->setEmpty('resource.log', 'data/log.txt'); | 302 | $this->setEmpty('resource.log', 'data/log.txt'); |
303 | $this->setEmpty('resource.update_check', 'data/lastupdatecheck.txt'); | 303 | $this->setEmpty('resource.update_check', 'data/lastupdatecheck.txt'); |
304 | $this->setEmpty('resource.history', 'data/history.php'); | ||
304 | $this->setEmpty('resource.raintpl_tpl', 'tpl/'); | 305 | $this->setEmpty('resource.raintpl_tpl', 'tpl/'); |
305 | $this->setEmpty('resource.theme', 'default'); | 306 | $this->setEmpty('resource.theme', 'default'); |
306 | $this->setEmpty('resource.raintpl_tmp', 'tmp/'); | 307 | $this->setEmpty('resource.raintpl_tmp', 'tmp/'); |
diff --git a/application/exceptions/IOException.php b/application/exceptions/IOException.php new file mode 100644 index 00000000..b563b23d --- /dev/null +++ b/application/exceptions/IOException.php | |||
@@ -0,0 +1,22 @@ | |||
1 | <?php | ||
2 | |||
3 | /** | ||
4 | * Exception class thrown when a filesystem access failure happens | ||
5 | */ | ||
6 | class IOException extends Exception | ||
7 | { | ||
8 | private $path; | ||
9 | |||
10 | /** | ||
11 | * Construct a new IOException | ||
12 | * | ||
13 | * @param string $path path to the resource that cannot be accessed | ||
14 | * @param string $message Custom exception message. | ||
15 | */ | ||
16 | public function __construct($path, $message = '') | ||
17 | { | ||
18 | $this->path = $path; | ||
19 | $this->message = empty($message) ? 'Error accessing' : $message; | ||
20 | $this->message .= ' "' . $this->path .'"'; | ||
21 | } | ||
22 | } | ||
diff --git a/composer.lock b/composer.lock index b285fcc9..0c1efa6b 100644 --- a/composer.lock +++ b/composer.lock | |||
@@ -1793,16 +1793,16 @@ | |||
1793 | }, | 1793 | }, |
1794 | { | 1794 | { |
1795 | "name": "squizlabs/php_codesniffer", | 1795 | "name": "squizlabs/php_codesniffer", |
1796 | "version": "2.8.1", | 1796 | "version": "2.9.0", |
1797 | "source": { | 1797 | "source": { |
1798 | "type": "git", | 1798 | "type": "git", |
1799 | "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", | 1799 | "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", |
1800 | "reference": "d7cf0d894e8aa4c73712ee4a331cc1eaa37cdc7d" | 1800 | "reference": "f7dfecbee89d68ab475a6c9e17d22bc9b69aed97" |
1801 | }, | 1801 | }, |
1802 | "dist": { | 1802 | "dist": { |
1803 | "type": "zip", | 1803 | "type": "zip", |
1804 | "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/d7cf0d894e8aa4c73712ee4a331cc1eaa37cdc7d", | 1804 | "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/f7dfecbee89d68ab475a6c9e17d22bc9b69aed97", |
1805 | "reference": "d7cf0d894e8aa4c73712ee4a331cc1eaa37cdc7d", | 1805 | "reference": "f7dfecbee89d68ab475a6c9e17d22bc9b69aed97", |
1806 | "shasum": "" | 1806 | "shasum": "" |
1807 | }, | 1807 | }, |
1808 | "require": { | 1808 | "require": { |
@@ -1867,20 +1867,20 @@ | |||
1867 | "phpcs", | 1867 | "phpcs", |
1868 | "standards" | 1868 | "standards" |
1869 | ], | 1869 | ], |
1870 | "time": "2017-03-01T22:17:45+00:00" | 1870 | "time": "2017-05-03T23:30:39+00:00" |
1871 | }, | 1871 | }, |
1872 | { | 1872 | { |
1873 | "name": "symfony/config", | 1873 | "name": "symfony/config", |
1874 | "version": "v3.2.6", | 1874 | "version": "v3.2.8", |
1875 | "source": { | 1875 | "source": { |
1876 | "type": "git", | 1876 | "type": "git", |
1877 | "url": "https://github.com/symfony/config.git", | 1877 | "url": "https://github.com/symfony/config.git", |
1878 | "reference": "741d6d4cd1414d67d48eb71aba6072b46ba740c2" | 1878 | "reference": "e5533fcc0b3dd377626153b2852707878f363728" |
1879 | }, | 1879 | }, |
1880 | "dist": { | 1880 | "dist": { |
1881 | "type": "zip", | 1881 | "type": "zip", |
1882 | "url": "https://api.github.com/repos/symfony/config/zipball/741d6d4cd1414d67d48eb71aba6072b46ba740c2", | 1882 | "url": "https://api.github.com/repos/symfony/config/zipball/e5533fcc0b3dd377626153b2852707878f363728", |
1883 | "reference": "741d6d4cd1414d67d48eb71aba6072b46ba740c2", | 1883 | "reference": "e5533fcc0b3dd377626153b2852707878f363728", |
1884 | "shasum": "" | 1884 | "shasum": "" |
1885 | }, | 1885 | }, |
1886 | "require": { | 1886 | "require": { |
@@ -1923,20 +1923,20 @@ | |||
1923 | ], | 1923 | ], |
1924 | "description": "Symfony Config Component", | 1924 | "description": "Symfony Config Component", |
1925 | "homepage": "https://symfony.com", | 1925 | "homepage": "https://symfony.com", |
1926 | "time": "2017-03-01T18:18:25+00:00" | 1926 | "time": "2017-04-12T14:13:17+00:00" |
1927 | }, | 1927 | }, |
1928 | { | 1928 | { |
1929 | "name": "symfony/console", | 1929 | "name": "symfony/console", |
1930 | "version": "v2.8.18", | 1930 | "version": "v2.8.20", |
1931 | "source": { | 1931 | "source": { |
1932 | "type": "git", | 1932 | "type": "git", |
1933 | "url": "https://github.com/symfony/console.git", | 1933 | "url": "https://github.com/symfony/console.git", |
1934 | "reference": "81508e6fac4476771275a3f4f53c3fee9b956bfa" | 1934 | "reference": "2cfcbced8e39e2313ed4da8896fc8c59a56c0d7e" |
1935 | }, | 1935 | }, |
1936 | "dist": { | 1936 | "dist": { |
1937 | "type": "zip", | 1937 | "type": "zip", |
1938 | "url": "https://api.github.com/repos/symfony/console/zipball/81508e6fac4476771275a3f4f53c3fee9b956bfa", | 1938 | "url": "https://api.github.com/repos/symfony/console/zipball/2cfcbced8e39e2313ed4da8896fc8c59a56c0d7e", |
1939 | "reference": "81508e6fac4476771275a3f4f53c3fee9b956bfa", | 1939 | "reference": "2cfcbced8e39e2313ed4da8896fc8c59a56c0d7e", |
1940 | "shasum": "" | 1940 | "shasum": "" |
1941 | }, | 1941 | }, |
1942 | "require": { | 1942 | "require": { |
@@ -1984,7 +1984,7 @@ | |||
1984 | ], | 1984 | ], |
1985 | "description": "Symfony Console Component", | 1985 | "description": "Symfony Console Component", |
1986 | "homepage": "https://symfony.com", | 1986 | "homepage": "https://symfony.com", |
1987 | "time": "2017-03-04T11:00:12+00:00" | 1987 | "time": "2017-04-26T01:38:53+00:00" |
1988 | }, | 1988 | }, |
1989 | { | 1989 | { |
1990 | "name": "symfony/debug", | 1990 | "name": "symfony/debug", |
@@ -2045,16 +2045,16 @@ | |||
2045 | }, | 2045 | }, |
2046 | { | 2046 | { |
2047 | "name": "symfony/dependency-injection", | 2047 | "name": "symfony/dependency-injection", |
2048 | "version": "v3.2.6", | 2048 | "version": "v3.2.8", |
2049 | "source": { | 2049 | "source": { |
2050 | "type": "git", | 2050 | "type": "git", |
2051 | "url": "https://github.com/symfony/dependency-injection.git", | 2051 | "url": "https://github.com/symfony/dependency-injection.git", |
2052 | "reference": "74e0935e414ad33d5e82074212c0eedb4681a691" | 2052 | "reference": "5e00857475b6d1fa31ff4c76f1fddf1cfa9e8d59" |
2053 | }, | 2053 | }, |
2054 | "dist": { | 2054 | "dist": { |
2055 | "type": "zip", | 2055 | "type": "zip", |
2056 | "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/74e0935e414ad33d5e82074212c0eedb4681a691", | 2056 | "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/5e00857475b6d1fa31ff4c76f1fddf1cfa9e8d59", |
2057 | "reference": "74e0935e414ad33d5e82074212c0eedb4681a691", | 2057 | "reference": "5e00857475b6d1fa31ff4c76f1fddf1cfa9e8d59", |
2058 | "shasum": "" | 2058 | "shasum": "" |
2059 | }, | 2059 | }, |
2060 | "require": { | 2060 | "require": { |
@@ -2104,20 +2104,20 @@ | |||
2104 | ], | 2104 | ], |
2105 | "description": "Symfony DependencyInjection Component", | 2105 | "description": "Symfony DependencyInjection Component", |
2106 | "homepage": "https://symfony.com", | 2106 | "homepage": "https://symfony.com", |
2107 | "time": "2017-03-05T00:06:55+00:00" | 2107 | "time": "2017-04-26T01:39:17+00:00" |
2108 | }, | 2108 | }, |
2109 | { | 2109 | { |
2110 | "name": "symfony/filesystem", | 2110 | "name": "symfony/filesystem", |
2111 | "version": "v3.2.6", | 2111 | "version": "v3.2.8", |
2112 | "source": { | 2112 | "source": { |
2113 | "type": "git", | 2113 | "type": "git", |
2114 | "url": "https://github.com/symfony/filesystem.git", | 2114 | "url": "https://github.com/symfony/filesystem.git", |
2115 | "reference": "bc0f17bed914df2cceb989972c3b996043c4da4a" | 2115 | "reference": "040651db13cf061827a460cc10f6e36a445c45b4" |
2116 | }, | 2116 | }, |
2117 | "dist": { | 2117 | "dist": { |
2118 | "type": "zip", | 2118 | "type": "zip", |
2119 | "url": "https://api.github.com/repos/symfony/filesystem/zipball/bc0f17bed914df2cceb989972c3b996043c4da4a", | 2119 | "url": "https://api.github.com/repos/symfony/filesystem/zipball/040651db13cf061827a460cc10f6e36a445c45b4", |
2120 | "reference": "bc0f17bed914df2cceb989972c3b996043c4da4a", | 2120 | "reference": "040651db13cf061827a460cc10f6e36a445c45b4", |
2121 | "shasum": "" | 2121 | "shasum": "" |
2122 | }, | 2122 | }, |
2123 | "require": { | 2123 | "require": { |
@@ -2153,20 +2153,20 @@ | |||
2153 | ], | 2153 | ], |
2154 | "description": "Symfony Filesystem Component", | 2154 | "description": "Symfony Filesystem Component", |
2155 | "homepage": "https://symfony.com", | 2155 | "homepage": "https://symfony.com", |
2156 | "time": "2017-03-06T19:30:27+00:00" | 2156 | "time": "2017-04-12T14:13:17+00:00" |
2157 | }, | 2157 | }, |
2158 | { | 2158 | { |
2159 | "name": "symfony/finder", | 2159 | "name": "symfony/finder", |
2160 | "version": "v3.2.6", | 2160 | "version": "v3.2.8", |
2161 | "source": { | 2161 | "source": { |
2162 | "type": "git", | 2162 | "type": "git", |
2163 | "url": "https://github.com/symfony/finder.git", | 2163 | "url": "https://github.com/symfony/finder.git", |
2164 | "reference": "92d7476d2df60cd851a3e13e078664b1deb8ce10" | 2164 | "reference": "9cf076f8f492f4b1ffac40aae9c2d287b4ca6930" |
2165 | }, | 2165 | }, |
2166 | "dist": { | 2166 | "dist": { |
2167 | "type": "zip", | 2167 | "type": "zip", |
2168 | "url": "https://api.github.com/repos/symfony/finder/zipball/92d7476d2df60cd851a3e13e078664b1deb8ce10", | 2168 | "url": "https://api.github.com/repos/symfony/finder/zipball/9cf076f8f492f4b1ffac40aae9c2d287b4ca6930", |
2169 | "reference": "92d7476d2df60cd851a3e13e078664b1deb8ce10", | 2169 | "reference": "9cf076f8f492f4b1ffac40aae9c2d287b4ca6930", |
2170 | "shasum": "" | 2170 | "shasum": "" |
2171 | }, | 2171 | }, |
2172 | "require": { | 2172 | "require": { |
@@ -2202,7 +2202,7 @@ | |||
2202 | ], | 2202 | ], |
2203 | "description": "Symfony Finder Component", | 2203 | "description": "Symfony Finder Component", |
2204 | "homepage": "https://symfony.com", | 2204 | "homepage": "https://symfony.com", |
2205 | "time": "2017-02-21T09:12:04+00:00" | 2205 | "time": "2017-04-12T14:13:17+00:00" |
2206 | }, | 2206 | }, |
2207 | { | 2207 | { |
2208 | "name": "symfony/polyfill-mbstring", | 2208 | "name": "symfony/polyfill-mbstring", |
@@ -2265,16 +2265,16 @@ | |||
2265 | }, | 2265 | }, |
2266 | { | 2266 | { |
2267 | "name": "symfony/yaml", | 2267 | "name": "symfony/yaml", |
2268 | "version": "v3.2.6", | 2268 | "version": "v3.2.8", |
2269 | "source": { | 2269 | "source": { |
2270 | "type": "git", | 2270 | "type": "git", |
2271 | "url": "https://github.com/symfony/yaml.git", | 2271 | "url": "https://github.com/symfony/yaml.git", |
2272 | "reference": "093e416ad096355149e265ea2e4cc1f9ee40ab1a" | 2272 | "reference": "acec26fcf7f3031e094e910b94b002fa53d4e4d6" |
2273 | }, | 2273 | }, |
2274 | "dist": { | 2274 | "dist": { |
2275 | "type": "zip", | 2275 | "type": "zip", |
2276 | "url": "https://api.github.com/repos/symfony/yaml/zipball/093e416ad096355149e265ea2e4cc1f9ee40ab1a", | 2276 | "url": "https://api.github.com/repos/symfony/yaml/zipball/acec26fcf7f3031e094e910b94b002fa53d4e4d6", |
2277 | "reference": "093e416ad096355149e265ea2e4cc1f9ee40ab1a", | 2277 | "reference": "acec26fcf7f3031e094e910b94b002fa53d4e4d6", |
2278 | "shasum": "" | 2278 | "shasum": "" |
2279 | }, | 2279 | }, |
2280 | "require": { | 2280 | "require": { |
@@ -2316,20 +2316,20 @@ | |||
2316 | ], | 2316 | ], |
2317 | "description": "Symfony Yaml Component", | 2317 | "description": "Symfony Yaml Component", |
2318 | "homepage": "https://symfony.com", | 2318 | "homepage": "https://symfony.com", |
2319 | "time": "2017-03-07T16:47:02+00:00" | 2319 | "time": "2017-05-01T14:55:58+00:00" |
2320 | }, | 2320 | }, |
2321 | { | 2321 | { |
2322 | "name": "theseer/fdomdocument", | 2322 | "name": "theseer/fdomdocument", |
2323 | "version": "1.6.1", | 2323 | "version": "1.6.5", |
2324 | "source": { | 2324 | "source": { |
2325 | "type": "git", | 2325 | "type": "git", |
2326 | "url": "https://github.com/theseer/fDOMDocument.git", | 2326 | "url": "https://github.com/theseer/fDOMDocument.git", |
2327 | "reference": "d9ad139d6c2e8edf5e313ffbe37ff13344cf0684" | 2327 | "reference": "8dcfd392135a5bd938c3c83ea71419501ad9855d" |
2328 | }, | 2328 | }, |
2329 | "dist": { | 2329 | "dist": { |
2330 | "type": "zip", | 2330 | "type": "zip", |
2331 | "url": "https://api.github.com/repos/theseer/fDOMDocument/zipball/d9ad139d6c2e8edf5e313ffbe37ff13344cf0684", | 2331 | "url": "https://api.github.com/repos/theseer/fDOMDocument/zipball/8dcfd392135a5bd938c3c83ea71419501ad9855d", |
2332 | "reference": "d9ad139d6c2e8edf5e313ffbe37ff13344cf0684", | 2332 | "reference": "8dcfd392135a5bd938c3c83ea71419501ad9855d", |
2333 | "shasum": "" | 2333 | "shasum": "" |
2334 | }, | 2334 | }, |
2335 | "require": { | 2335 | "require": { |
@@ -2356,7 +2356,7 @@ | |||
2356 | ], | 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.", | 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", | 2358 | "homepage": "https://github.com/theseer/fDOMDocument", |
2359 | "time": "2015-05-27T22:58:02+00:00" | 2359 | "time": "2017-04-21T14:50:31+00:00" |
2360 | }, | 2360 | }, |
2361 | { | 2361 | { |
2362 | "name": "webmozart/assert", | 2362 | "name": "webmozart/assert", |
diff --git a/doc/3rd-party-libraries.html b/doc/3rd-party-libraries.html index 946ca037..50aba6c0 100644 --- a/doc/3rd-party-libraries.html +++ b/doc/3rd-party-libraries.html | |||
@@ -32,6 +32,7 @@ | |||
32 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | 32 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> |
33 | <li><a href="Firefox-share.html">Firefox share</a></li> | 33 | <li><a href="Firefox-share.html">Firefox share</a></li> |
34 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | 34 | <li><a href="RSS-feeds.html">RSS feeds</a></li> |
35 | <li><a href="REST-API.html">REST API</a></li> | ||
35 | </ul></li> | 36 | </ul></li> |
36 | <li>How To | 37 | <li>How To |
37 | <ul> | 38 | <ul> |
@@ -50,6 +51,7 @@ | |||
50 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | 51 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> |
51 | <li><a href="Plugin-System.html">Plugin System</a></li> | 52 | <li><a href="Plugin-System.html">Plugin System</a></li> |
52 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | 53 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> |
54 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
53 | <li><a href="Security.html">Security</a></li> | 55 | <li><a href="Security.html">Security</a></li> |
54 | <li><a href="Static-analysis.html">Static analysis</a></li> | 56 | <li><a href="Static-analysis.html">Static analysis</a></li> |
55 | <li><a href="Theming.html">Theming</a></li> | 57 | <li><a href="Theming.html">Theming</a></li> |
diff --git a/doc/Backup,-restore,-import-and-export.html b/doc/Backup,-restore,-import-and-export.html index a4a48ad7..3c168824 100644 --- a/doc/Backup,-restore,-import-and-export.html +++ b/doc/Backup,-restore,-import-and-export.html | |||
@@ -69,6 +69,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | 69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> |
70 | <li><a href="Firefox-share.html">Firefox share</a></li> | 70 | <li><a href="Firefox-share.html">Firefox share</a></li> |
71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | 71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> |
72 | <li><a href="REST-API.html">REST API</a></li> | ||
72 | </ul></li> | 73 | </ul></li> |
73 | <li>How To | 74 | <li>How To |
74 | <ul> | 75 | <ul> |
@@ -87,6 +88,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
87 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | 88 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> |
88 | <li><a href="Plugin-System.html">Plugin System</a></li> | 89 | <li><a href="Plugin-System.html">Plugin System</a></li> |
89 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | 90 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> |
91 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
90 | <li><a href="Security.html">Security</a></li> | 92 | <li><a href="Security.html">Security</a></li> |
91 | <li><a href="Static-analysis.html">Static analysis</a></li> | 93 | <li><a href="Static-analysis.html">Static analysis</a></li> |
92 | <li><a href="Theming.html">Theming</a></li> | 94 | <li><a href="Theming.html">Theming</a></li> |
diff --git a/doc/Browsing-and-searching.html b/doc/Browsing-and-searching.html index 23001bcb..ef5b5245 100644 --- a/doc/Browsing-and-searching.html +++ b/doc/Browsing-and-searching.html | |||
@@ -32,6 +32,7 @@ | |||
32 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | 32 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> |
33 | <li><a href="Firefox-share.html">Firefox share</a></li> | 33 | <li><a href="Firefox-share.html">Firefox share</a></li> |
34 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | 34 | <li><a href="RSS-feeds.html">RSS feeds</a></li> |
35 | <li><a href="REST-API.html">REST API</a></li> | ||
35 | </ul></li> | 36 | </ul></li> |
36 | <li>How To | 37 | <li>How To |
37 | <ul> | 38 | <ul> |
@@ -50,6 +51,7 @@ | |||
50 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | 51 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> |
51 | <li><a href="Plugin-System.html">Plugin System</a></li> | 52 | <li><a href="Plugin-System.html">Plugin System</a></li> |
52 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | 53 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> |
54 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
53 | <li><a href="Security.html">Security</a></li> | 55 | <li><a href="Security.html">Security</a></li> |
54 | <li><a href="Static-analysis.html">Static analysis</a></li> | 56 | <li><a href="Static-analysis.html">Static analysis</a></li> |
55 | <li><a href="Theming.html">Theming</a></li> | 57 | <li><a href="Theming.html">Theming</a></li> |
@@ -64,7 +66,6 @@ | |||
64 | </div> | 66 | </div> |
65 | <h1 id="browsing-and-searching">Browsing and searching</h1> | 67 | <h1 id="browsing-and-searching">Browsing and searching</h1> |
66 | <h1 id="browsing-and-searching-1">Browsing and Searching</h1> | 68 | <h1 id="browsing-and-searching-1">Browsing and Searching</h1> |
67 | <p>Status: DRAFT</p> | ||
68 | <p><embed src="(http://pix.toile-libre.org/upload/original/1455571378.png).html" /></p> | 69 | <p><embed src="(http://pix.toile-libre.org/upload/original/1455571378.png).html" /></p> |
69 | <h2 id="plain-text-search">Plain text search</h2> | 70 | <h2 id="plain-text-search">Plain text search</h2> |
70 | <p>Use the <code>Search text</code> field to search in <em>any</em> of the fields of all links (Title, URL, Description...)</p> | 71 | <p>Use the <code>Search text</code> field to search in <em>any</em> of the fields of all links (Title, URL, Description...)</p> |
@@ -75,6 +76,7 @@ | |||
75 | <p>Use the <code>Filter by tags</code> field to restrict displayed links to entries tagged with one or multiple tags (use space to separate tags).</p> | 76 | <p>Use the <code>Filter by tags</code> field to restrict displayed links to entries tagged with one or multiple tags (use space to separate tags).</p> |
76 | <p><strong>Hidden tags:</strong> Tags starting with a dot <code>.</code> (example <code>.secret</code>) are private. They can only be seen and searched when logged in.</p> | 77 | <p><strong>Hidden tags:</strong> Tags starting with a dot <code>.</code> (example <code>.secret</code>) are private. They can only be seen and searched when logged in.</p> |
77 | <p>Alternatively you can use the <code>Tag cloud</code> to discover all tags and click on any of them to display related links.</p> | 78 | <p>Alternatively you can use the <code>Tag cloud</code> to discover all tags and click on any of them to display related links.</p> |
79 | <p>To search for links that are not tagged, enter <code>""</code> in the tag search field.</p> | ||
78 | <h2 id="filtering-rss-feedspicture-wall">Filtering RSS feeds/Picture wall</h2> | 80 | <h2 id="filtering-rss-feedspicture-wall">Filtering RSS feeds/Picture wall</h2> |
79 | <p>RSS feeds can also be restricted to only return items matching a text/tag search: see <a href="RSS-feeds.html">RSS feeds</a>.</p> | 81 | <p>RSS feeds can also be restricted to only return items matching a text/tag search: see <a href="RSS-feeds.html">RSS feeds</a>.</p> |
80 | </body> | 82 | </body> |
diff --git a/doc/Browsing-and-searching.md b/doc/Browsing-and-searching.md index 187fe447..854b6b60 100644 --- a/doc/Browsing-and-searching.md +++ b/doc/Browsing-and-searching.md | |||
@@ -1,8 +1,6 @@ | |||
1 | #Browsing and searching | 1 | #Browsing and searching |
2 | # Browsing and Searching | 2 | # Browsing and Searching |
3 | 3 | ||
4 | Status: DRAFT | ||
5 | |||
6 | ![(http://pix.toile-libre.org/upload/original/1455571378.png)]((http://pix.toile-libre.org/upload/original/1455571378.png).html) | 4 | ![(http://pix.toile-libre.org/upload/original/1455571378.png)]((http://pix.toile-libre.org/upload/original/1455571378.png).html) |
7 | 5 | ||
8 | ## Plain text search | 6 | ## Plain text search |
@@ -23,6 +21,8 @@ Use the `Filter by tags` field to restrict displayed links to entries tagged wit | |||
23 | 21 | ||
24 | Alternatively you can use the `Tag cloud` to discover all tags and click on any of them to display related links. | 22 | Alternatively you can use the `Tag cloud` to discover all tags and click on any of them to display related links. |
25 | 23 | ||
24 | To search for links that are not tagged, enter `""` in the tag search field. | ||
25 | |||
26 | ## Filtering RSS feeds/Picture wall | 26 | ## Filtering RSS feeds/Picture wall |
27 | 27 | ||
28 | RSS feeds can also be restricted to only return items matching a text/tag search: see [RSS feeds](RSS-feeds.html). | 28 | RSS feeds can also be restricted to only return items matching a text/tag search: see [RSS feeds](RSS-feeds.html). |
diff --git a/doc/Coding-guidelines.html b/doc/Coding-guidelines.html index 1a2a9351..8df12182 100644 --- a/doc/Coding-guidelines.html +++ b/doc/Coding-guidelines.html | |||
@@ -32,6 +32,7 @@ | |||
32 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | 32 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> |
33 | <li><a href="Firefox-share.html">Firefox share</a></li> | 33 | <li><a href="Firefox-share.html">Firefox share</a></li> |
34 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | 34 | <li><a href="RSS-feeds.html">RSS feeds</a></li> |
35 | <li><a href="REST-API.html">REST API</a></li> | ||
35 | </ul></li> | 36 | </ul></li> |
36 | <li>How To | 37 | <li>How To |
37 | <ul> | 38 | <ul> |
@@ -50,6 +51,7 @@ | |||
50 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | 51 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> |
51 | <li><a href="Plugin-System.html">Plugin System</a></li> | 52 | <li><a href="Plugin-System.html">Plugin System</a></li> |
52 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | 53 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> |
54 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
53 | <li><a href="Security.html">Security</a></li> | 55 | <li><a href="Security.html">Security</a></li> |
54 | <li><a href="Static-analysis.html">Static analysis</a></li> | 56 | <li><a href="Static-analysis.html">Static analysis</a></li> |
55 | <li><a href="Theming.html">Theming</a></li> | 57 | <li><a href="Theming.html">Theming</a></li> |
diff --git a/doc/Community-&-Related-software.html b/doc/Community-&-Related-software.html index cbc73d54..28b96185 100644 --- a/doc/Community-&-Related-software.html +++ b/doc/Community-&-Related-software.html | |||
@@ -32,6 +32,7 @@ | |||
32 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | 32 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> |
33 | <li><a href="Firefox-share.html">Firefox share</a></li> | 33 | <li><a href="Firefox-share.html">Firefox share</a></li> |
34 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | 34 | <li><a href="RSS-feeds.html">RSS feeds</a></li> |
35 | <li><a href="REST-API.html">REST API</a></li> | ||
35 | </ul></li> | 36 | </ul></li> |
36 | <li>How To | 37 | <li>How To |
37 | <ul> | 38 | <ul> |
@@ -50,6 +51,7 @@ | |||
50 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | 51 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> |
51 | <li><a href="Plugin-System.html">Plugin System</a></li> | 52 | <li><a href="Plugin-System.html">Plugin System</a></li> |
52 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | 53 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> |
54 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
53 | <li><a href="Security.html">Security</a></li> | 55 | <li><a href="Security.html">Security</a></li> |
54 | <li><a href="Static-analysis.html">Static analysis</a></li> | 56 | <li><a href="Static-analysis.html">Static analysis</a></li> |
55 | <li><a href="Theming.html">Theming</a></li> | 57 | <li><a href="Theming.html">Theming</a></li> |
@@ -77,12 +79,20 @@ | |||
77 | <li><a href="http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history">Original revisions history</a><a href=".html"></a></li> | 79 | <li><a href="http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history">Original revisions history</a><a href=".html"></a></li> |
78 | <li><a href="https://www.shaarli.fr/my.php">Shaarli.fr/my</a> - Unofficial, unsupported (old fork) hosted Shaarlis provider, courtesy of <a href="https://github.com/DMeloni">DMeloni</a><a href=".html"></a></li> | 80 | <li><a href="https://www.shaarli.fr/my.php">Shaarli.fr/my</a> - Unofficial, unsupported (old fork) hosted Shaarlis provider, courtesy of <a href="https://github.com/DMeloni">DMeloni</a><a href=".html"></a></li> |
79 | </ul> | 81 | </ul> |
82 | <h3 id="articles-and-social-media-discussions">Articles and social media discussions</h3> | ||
83 | <ul> | ||
84 | <li>2016-09-22 - Hacker News - <a href="https://news.ycombinator.com/item?id=12552176" class="uri">https://news.ycombinator.com/item?id=12552176</a></li> | ||
85 | <li>2015-08-15 - Reddit - <a href="https://www.reddit.com/r/selfhosted/comments/3h3zwh/question_about_migrating_from_wordpress_to_shaarli/">Question about migrating from WordPress to Shaarli.</a><a href=".html"></a></li> | ||
86 | <li>2015-06-22 - Hacker News - <a href="https://news.ycombinator.com/item?id=9755366" class="uri">https://news.ycombinator.com/item?id=9755366</a></li> | ||
87 | <li>2015-05-12 - Reddit - <a href="https://www.reddit.com/r/selfhosted/comments/35pkkc/shaarli_self_hosted_bookmarking_delicious_php/">shaarli - Self hosted Bookmarking / Delicious (PHP, MySQL)</a><a href=".html"></a></li> | ||
88 | </ul> | ||
80 | <h3 id="third-party-plugins">Third party plugins</h3> | 89 | <h3 id="third-party-plugins">Third party plugins</h3> |
81 | <ul> | 90 | <ul> |
82 | <li><a href="https://github.com/kalvn/shaarli-plugin-autosave">autosave</a> by <a href="https://github.com/kalvn">@kalvn</a>: Automatically saves data when editing a link to avoid any loss in case of crash or unexpected shutdown.<a href=".html"></a></li> | 91 | <li><a href="https://github.com/kalvn/shaarli-plugin-autosave">autosave</a> by <a href="https://github.com/kalvn">@kalvn</a>: Automatically saves data when editing a link to avoid any loss in case of crash or unexpected shutdown.<a href=".html"></a></li> |
83 | <li><a href="https://github.com/ArthurHoaro/code-coloration">Code Coloration</a> by <a href="https://github.com/ArthurHoaro">@ArthurHoaro</a>: client side code syntax highlighter.<a href=".html"></a></li> | 92 | <li><a href="https://github.com/ArthurHoaro/code-coloration">Code Coloration</a> by <a href="https://github.com/ArthurHoaro">@ArthurHoaro</a>: client side code syntax highlighter.<a href=".html"></a></li> |
84 | <li><a href="https://github.com/kalvn/shaarli-plugin-disqus">Disqus</a> by <a href="https://github.com/kalvn">@kalvn</a>: Adds Disqus comment system to your Shaarli.<a href=".html"></a></li> | 93 | <li><a href="https://github.com/kalvn/shaarli-plugin-disqus">Disqus</a> by <a href="https://github.com/kalvn">@kalvn</a>: Adds Disqus comment system to your Shaarli.<a href=".html"></a></li> |
85 | <li><a href="https://github.com/NerosTie/emojione">emojione</a> by <a href="https://github.com/NerosTie">@NerosTie</a>: Add colorful emojis to your Shaarli.<a href=".html"></a></li> | 94 | <li><a href="https://github.com/NerosTie/emojione">emojione</a> by <a href="https://github.com/NerosTie">@NerosTie</a>: Add colorful emojis to your Shaarli.<a href=".html"></a></li> |
95 | <li><a href="https://github.com/ericjuden/Shaarli-Google-Analytics-Plugin">google analytics</a> by <a href="http://github.com/ericjuden">@ericjuden</a>: Adds Google Analytics tracking support<a href=".html"></a></li> | ||
86 | <li><a href="https://github.com/ArthurHoaro/launch-plugin">launch</a> - Launch Plugin is a plugin designed to enhance and customize Launch Theme for Shaarli.<a href=".html"></a></li> | 96 | <li><a href="https://github.com/ArthurHoaro/launch-plugin">launch</a> - Launch Plugin is a plugin designed to enhance and customize Launch Theme for Shaarli.<a href=".html"></a></li> |
87 | <li><a href="https://github.com/alexisju/social">social</a> by <a href="https://github.com/alexisju">@alexisju</a>: share links to social networks.<a href=".html"></a></li> | 97 | <li><a href="https://github.com/alexisju/social">social</a> by <a href="https://github.com/alexisju">@alexisju</a>: share links to social networks.<a href=".html"></a></li> |
88 | <li><a href="https://github.com/ArthurHoaro/shaarli2twitter">shaarli2twitter</a> by <a href="https://github.com/ArthurHoaro">@ArthurHoaro</a> - Automatically tweet your shared links from Shaarli<a href=".html"></a></li> | 98 | <li><a href="https://github.com/ArthurHoaro/shaarli2twitter">shaarli2twitter</a> by <a href="https://github.com/ArthurHoaro">@ArthurHoaro</a> - Automatically tweet your shared links from Shaarli<a href=".html"></a></li> |
diff --git a/doc/Community-&-Related-software.md b/doc/Community-&-Related-software.md index 291bf643..52123a1e 100644 --- a/doc/Community-&-Related-software.md +++ b/doc/Community-&-Related-software.md | |||
@@ -14,6 +14,11 @@ _TODO: contact repos owners to see if they'd like to standardize their work with | |||
14 | - [Original revisions history](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history)[](.html) | 14 | - [Original revisions history](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history)[](.html) |
15 | - [Shaarli.fr/my](https://www.shaarli.fr/my.php) - Unofficial, unsupported (old fork) hosted Shaarlis provider, courtesy of [DMeloni](https://github.com/DMeloni)[](.html) | 15 | - [Shaarli.fr/my](https://www.shaarli.fr/my.php) - Unofficial, unsupported (old fork) hosted Shaarlis provider, courtesy of [DMeloni](https://github.com/DMeloni)[](.html) |
16 | 16 | ||
17 | ### Articles and social media discussions | ||
18 | - 2016-09-22 - Hacker News - https://news.ycombinator.com/item?id=12552176 | ||
19 | - 2015-08-15 - Reddit - [Question about migrating from WordPress to Shaarli.](https://www.reddit.com/r/selfhosted/comments/3h3zwh/question_about_migrating_from_wordpress_to_shaarli/)[](.html) | ||
20 | - 2015-06-22 - Hacker News - https://news.ycombinator.com/item?id=9755366 | ||
21 | - 2015-05-12 - Reddit - [shaarli - Self hosted Bookmarking / Delicious (PHP, MySQL)](https://www.reddit.com/r/selfhosted/comments/35pkkc/shaarli_self_hosted_bookmarking_delicious_php/)[](.html) | ||
17 | 22 | ||
18 | ### Third party plugins | 23 | ### Third party plugins |
19 | 24 | ||
@@ -22,6 +27,7 @@ _TODO: contact repos owners to see if they'd like to standardize their work with | |||
22 | * [Code Coloration](https://github.com/ArthurHoaro/code-coloration) by [@ArthurHoaro](https://github.com/ArthurHoaro): client side code syntax highlighter.[](.html) | 27 | * [Code Coloration](https://github.com/ArthurHoaro/code-coloration) by [@ArthurHoaro](https://github.com/ArthurHoaro): client side code syntax highlighter.[](.html) |
23 | * [Disqus](https://github.com/kalvn/shaarli-plugin-disqus) by [@kalvn](https://github.com/kalvn): Adds Disqus comment system to your Shaarli.[](.html) | 28 | * [Disqus](https://github.com/kalvn/shaarli-plugin-disqus) by [@kalvn](https://github.com/kalvn): Adds Disqus comment system to your Shaarli.[](.html) |
24 | * [emojione](https://github.com/NerosTie/emojione) by [@NerosTie](https://github.com/NerosTie): Add colorful emojis to your Shaarli.[](.html) | 29 | * [emojione](https://github.com/NerosTie/emojione) by [@NerosTie](https://github.com/NerosTie): Add colorful emojis to your Shaarli.[](.html) |
30 | * [google analytics](https://github.com/ericjuden/Shaarli-Google-Analytics-Plugin) by [@ericjuden](http://github.com/ericjuden): Adds Google Analytics tracking support[](.html) | ||
25 | * [launch](https://github.com/ArthurHoaro/launch-plugin) - Launch Plugin is a plugin designed to enhance and customize Launch Theme for Shaarli.[](.html) | 31 | * [launch](https://github.com/ArthurHoaro/launch-plugin) - Launch Plugin is a plugin designed to enhance and customize Launch Theme for Shaarli.[](.html) |
26 | * [social](https://github.com/alexisju/social) by [@alexisju](https://github.com/alexisju): share links to social networks.[](.html) | 32 | * [social](https://github.com/alexisju/social) by [@alexisju](https://github.com/alexisju): share links to social networks.[](.html) |
27 | * [shaarli2twitter](https://github.com/ArthurHoaro/shaarli2twitter) by [@ArthurHoaro](https://github.com/ArthurHoaro) - Automatically tweet your shared links from Shaarli[](.html) | 33 | * [shaarli2twitter](https://github.com/ArthurHoaro/shaarli2twitter) by [@ArthurHoaro](https://github.com/ArthurHoaro) - Automatically tweet your shared links from Shaarli[](.html) |
diff --git a/doc/Copy-an-existing-installation-over-SSH-and-serve-it-locally.html b/doc/Copy-an-existing-installation-over-SSH-and-serve-it-locally.html index 9efb1ad6..d6b76add 100644 --- a/doc/Copy-an-existing-installation-over-SSH-and-serve-it-locally.html +++ b/doc/Copy-an-existing-installation-over-SSH-and-serve-it-locally.html | |||
@@ -69,6 +69,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | 69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> |
70 | <li><a href="Firefox-share.html">Firefox share</a></li> | 70 | <li><a href="Firefox-share.html">Firefox share</a></li> |
71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | 71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> |
72 | <li><a href="REST-API.html">REST API</a></li> | ||
72 | </ul></li> | 73 | </ul></li> |
73 | <li>How To | 74 | <li>How To |
74 | <ul> | 75 | <ul> |
@@ -87,6 +88,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
87 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | 88 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> |
88 | <li><a href="Plugin-System.html">Plugin System</a></li> | 89 | <li><a href="Plugin-System.html">Plugin System</a></li> |
89 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | 90 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> |
91 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
90 | <li><a href="Security.html">Security</a></li> | 92 | <li><a href="Security.html">Security</a></li> |
91 | <li><a href="Static-analysis.html">Static analysis</a></li> | 93 | <li><a href="Static-analysis.html">Static analysis</a></li> |
92 | <li><a href="Theming.html">Theming</a></li> | 94 | <li><a href="Theming.html">Theming</a></li> |
diff --git a/doc/Create-and-serve-multiple-Shaarlis-(farm).html b/doc/Create-and-serve-multiple-Shaarlis-(farm).html index 672e4bf3..0be81d56 100644 --- a/doc/Create-and-serve-multiple-Shaarlis-(farm).html +++ b/doc/Create-and-serve-multiple-Shaarlis-(farm).html | |||
@@ -69,6 +69,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | 69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> |
70 | <li><a href="Firefox-share.html">Firefox share</a></li> | 70 | <li><a href="Firefox-share.html">Firefox share</a></li> |
71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | 71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> |
72 | <li><a href="REST-API.html">REST API</a></li> | ||
72 | </ul></li> | 73 | </ul></li> |
73 | <li>How To | 74 | <li>How To |
74 | <ul> | 75 | <ul> |
@@ -87,6 +88,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
87 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | 88 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> |
88 | <li><a href="Plugin-System.html">Plugin System</a></li> | 89 | <li><a href="Plugin-System.html">Plugin System</a></li> |
89 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | 90 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> |
91 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
90 | <li><a href="Security.html">Security</a></li> | 92 | <li><a href="Security.html">Security</a></li> |
91 | <li><a href="Static-analysis.html">Static analysis</a></li> | 93 | <li><a href="Static-analysis.html">Static analysis</a></li> |
92 | <li><a href="Theming.html">Theming</a></li> | 94 | <li><a href="Theming.html">Theming</a></li> |
diff --git a/doc/Datastore-hacks.html b/doc/Datastore-hacks.html index 15da09d4..ef3e17bb 100644 --- a/doc/Datastore-hacks.html +++ b/doc/Datastore-hacks.html | |||
@@ -69,6 +69,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | 69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> |
70 | <li><a href="Firefox-share.html">Firefox share</a></li> | 70 | <li><a href="Firefox-share.html">Firefox share</a></li> |
71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | 71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> |
72 | <li><a href="REST-API.html">REST API</a></li> | ||
72 | </ul></li> | 73 | </ul></li> |
73 | <li>How To | 74 | <li>How To |
74 | <ul> | 75 | <ul> |
@@ -87,6 +88,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
87 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | 88 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> |
88 | <li><a href="Plugin-System.html">Plugin System</a></li> | 89 | <li><a href="Plugin-System.html">Plugin System</a></li> |
89 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | 90 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> |
91 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
90 | <li><a href="Security.html">Security</a></li> | 92 | <li><a href="Security.html">Security</a></li> |
91 | <li><a href="Static-analysis.html">Static analysis</a></li> | 93 | <li><a href="Static-analysis.html">Static analysis</a></li> |
92 | <li><a href="Theming.html">Theming</a></li> | 94 | <li><a href="Theming.html">Theming</a></li> |
diff --git a/doc/Development.html b/doc/Development.html index c5776413..8a2be413 100644 --- a/doc/Development.html +++ b/doc/Development.html | |||
@@ -32,6 +32,7 @@ | |||
32 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | 32 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> |
33 | <li><a href="Firefox-share.html">Firefox share</a></li> | 33 | <li><a href="Firefox-share.html">Firefox share</a></li> |
34 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | 34 | <li><a href="RSS-feeds.html">RSS feeds</a></li> |
35 | <li><a href="REST-API.html">REST API</a></li> | ||
35 | </ul></li> | 36 | </ul></li> |
36 | <li>How To | 37 | <li>How To |
37 | <ul> | 38 | <ul> |
@@ -50,6 +51,7 @@ | |||
50 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | 51 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> |
51 | <li><a href="Plugin-System.html">Plugin System</a></li> | 52 | <li><a href="Plugin-System.html">Plugin System</a></li> |
52 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | 53 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> |
54 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
53 | <li><a href="Security.html">Security</a></li> | 55 | <li><a href="Security.html">Security</a></li> |
54 | <li><a href="Static-analysis.html">Static analysis</a></li> | 56 | <li><a href="Static-analysis.html">Static analysis</a></li> |
55 | <li><a href="Theming.html">Theming</a></li> | 57 | <li><a href="Theming.html">Theming</a></li> |
diff --git a/doc/Directory-structure.html b/doc/Directory-structure.html index 404ff7c8..3f75db8e 100644 --- a/doc/Directory-structure.html +++ b/doc/Directory-structure.html | |||
@@ -69,6 +69,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | 69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> |
70 | <li><a href="Firefox-share.html">Firefox share</a></li> | 70 | <li><a href="Firefox-share.html">Firefox share</a></li> |
71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | 71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> |
72 | <li><a href="REST-API.html">REST API</a></li> | ||
72 | </ul></li> | 73 | </ul></li> |
73 | <li>How To | 74 | <li>How To |
74 | <ul> | 75 | <ul> |
@@ -87,6 +88,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
87 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | 88 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> |
88 | <li><a href="Plugin-System.html">Plugin System</a></li> | 89 | <li><a href="Plugin-System.html">Plugin System</a></li> |
89 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | 90 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> |
91 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
90 | <li><a href="Security.html">Security</a></li> | 92 | <li><a href="Security.html">Security</a></li> |
91 | <li><a href="Static-analysis.html">Static analysis</a></li> | 93 | <li><a href="Static-analysis.html">Static analysis</a></li> |
92 | <li><a href="Theming.html">Theming</a></li> | 94 | <li><a href="Theming.html">Theming</a></li> |
@@ -101,33 +103,33 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
101 | </div> | 103 | </div> |
102 | <h1 id="directory-structure">Directory structure</h1> | 104 | <h1 id="directory-structure">Directory structure</h1> |
103 | <p>Here is the directory structure of Shaarli and the purpose of the different files:</p> | 105 | <p>Here is the directory structure of Shaarli and the purpose of the different files:</p> |
104 | <div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"> <span class="ex">index.php</span> <span class="co"># Main program</span> | 106 | <div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"> <span class="ex">index.php</span> # Main program |
105 | <span class="ex">application/</span> <span class="co"># Shaarli classes</span> | 107 | <span class="ex">application/</span> # Shaarli classes |
106 | ├── <span class="ex">LinkDB.php</span> | 108 | ├── <span class="ex">LinkDB.php</span> |
107 | └── <span class="ex">Utils.php</span> | 109 | └── <span class="ex">Utils.php</span> |
108 | <span class="ex">tests/</span> <span class="co"># Shaarli unitary & functional tests</span> | 110 | <span class="ex">tests/</span> # Shaarli unitary <span class="kw">&</span> <span class="ex">functional</span> tests |
109 | ├── <span class="ex">LinkDBTest.php</span> | 111 | ├── <span class="ex">LinkDBTest.php</span> |
110 | ├── <span class="ex">utils</span> <span class="co"># utilities to ease testing</span> | 112 | ├── <span class="ex">utils</span> # utilities to ease testing |
111 | │ └── <span class="ex">ReferenceLinkDB.php</span> | 113 | │ └── <span class="ex">ReferenceLinkDB.php</span> |
112 | └── <span class="ex">UtilsTest.php</span> | 114 | └── <span class="ex">UtilsTest.php</span> |
113 | <span class="ex">COPYING</span> <span class="co"># Shaarli license</span> | 115 | <span class="ex">COPYING</span> # Shaarli license |
114 | <span class="ex">inc/</span> <span class="co"># static assets and 3rd party libraries</span> | 116 | <span class="ex">inc/</span> # static assets and 3rd party libraries |
115 | ├── <span class="ex">awesomplete.*</span> <span class="co"># tags autocompletion library</span> | 117 | ├── <span class="ex">awesomplete.*</span> # tags autocompletion library |
116 | ├── <span class="ex">blazy.*</span> <span class="co"># picture wall lazy image loading library</span> | 118 | ├── <span class="ex">blazy.*</span> # picture wall lazy image loading library |
117 | ├── <span class="ex">shaarli.css</span>, reset.css <span class="co"># Shaarli stylesheet.</span> | 119 | ├── <span class="ex">shaarli.css</span>, reset.css <span class="co"># Shaarli stylesheet.</span> |
118 | ├── <span class="ex">qr.*</span> <span class="co"># qr code generation library</span> | 120 | ├── <span class="ex">qr.*</span> # qr code generation library |
119 | └──<span class="ex">rain.tpl.class.php</span> <span class="co"># RainTPL templating library</span> | 121 | └──<span class="ex">rain.tpl.class.php</span> # RainTPL templating library |
120 | <span class="ex">tpl/</span> <span class="co"># RainTPL templates for Shaarli. They are used to build the pages.</span> | 122 | <span class="ex">tpl/</span> # RainTPL templates for Shaarli. They are used to build the pages. |
121 | <span class="ex">images/</span> <span class="co"># Images and icons used in Shaarli</span> | 123 | <span class="ex">images/</span> # Images and icons used in Shaarli |
122 | <span class="ex">data/</span> <span class="co"># data storage: bookmark database, configuration, logs, banlist…</span> | 124 | <span class="ex">data/</span> # data storage: bookmark database, configuration, logs, banlist… |
123 | ├── <span class="ex">config.php</span> <span class="co"># Shaarli configuration (login, password, timezone, title…)</span> | 125 | ├── <span class="ex">config.php</span> # Shaarli configuration (login, password, timezone, title…) |
124 | ├── <span class="ex">datastore.php</span> <span class="co"># Your link database (compressed).</span> | 126 | ├── <span class="ex">datastore.php</span> # Your link database (compressed)<span class="ex">.</span> |
125 | ├── <span class="ex">ipban.php</span> <span class="co"># IP address ban system data</span> | 127 | ├── <span class="ex">ipban.php</span> # IP address ban system data |
126 | ├── <span class="ex">lastupdatecheck.txt</span> <span class="co"># Update check timestamp file</span> | 128 | ├── <span class="ex">lastupdatecheck.txt</span> # Update check timestamp file |
127 | └──<span class="ex">log.txt</span> <span class="co"># login/IPban log.</span> | 129 | └──<span class="ex">log.txt</span> # login/IPban log. |
128 | <span class="ex">cache/</span> <span class="co"># thumbnails cache</span> | 130 | <span class="ex">cache/</span> # thumbnails cache |
129 | <span class="co"># This directory is automatically created. You can erase it anytime you want.</span> | 131 | <span class="co"># This directory is automatically created. You can erase it anytime you want.</span> |
130 | <span class="ex">tmp/</span> <span class="co"># Temporary directory for compiled RainTPL templates.</span> | 132 | <span class="ex">tmp/</span> # Temporary directory for compiled RainTPL templates. |
131 | <span class="co"># This directory is automatically created. You can erase it anytime you want.</span></code></pre></div> | 133 | <span class="co"># This directory is automatically created. You can erase it anytime you want.</span></code></pre></div> |
132 | </body> | 134 | </body> |
133 | </html> | 135 | </html> |
diff --git a/doc/Docker.html b/doc/Docker.html index e89c90fb..fd0dec4b 100644 --- a/doc/Docker.html +++ b/doc/Docker.html | |||
@@ -69,6 +69,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | 69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> |
70 | <li><a href="Firefox-share.html">Firefox share</a></li> | 70 | <li><a href="Firefox-share.html">Firefox share</a></li> |
71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | 71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> |
72 | <li><a href="REST-API.html">REST API</a></li> | ||
72 | </ul></li> | 73 | </ul></li> |
73 | <li>How To | 74 | <li>How To |
74 | <ul> | 75 | <ul> |
@@ -87,6 +88,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
87 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | 88 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> |
88 | <li><a href="Plugin-System.html">Plugin System</a></li> | 89 | <li><a href="Plugin-System.html">Plugin System</a></li> |
89 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | 90 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> |
91 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
90 | <li><a href="Security.html">Security</a></li> | 92 | <li><a href="Security.html">Security</a></li> |
91 | <li><a href="Static-analysis.html">Static analysis</a></li> | 93 | <li><a href="Static-analysis.html">Static analysis</a></li> |
92 | <li><a href="Theming.html">Theming</a></li> | 94 | <li><a href="Theming.html">Theming</a></li> |
@@ -200,7 +202,7 @@ $ <span class="ex">docker</span> ps | |||
200 | <span class="ex">CONTAINER</span> ID IMAGE COMMAND CREATED STATUS PORTS NAMES | 202 | <span class="ex">CONTAINER</span> ID IMAGE COMMAND CREATED STATUS PORTS NAMES |
201 | <span class="ex">d40b7af693d6</span> shaarli/shaarli /usr/bin/supervisor 15 seconds ago Up 4 seconds 0.0.0.0:8000-<span class="op">></span>80/tcp backstabbing_galileo</code></pre></div> | 203 | <span class="ex">d40b7af693d6</span> shaarli/shaarli /usr/bin/supervisor 15 seconds ago Up 4 seconds 0.0.0.0:8000-<span class="op">></span>80/tcp backstabbing_galileo</code></pre></div> |
202 | <h3 id="stop-and-destroy-a-container">Stop and destroy a container</h3> | 204 | <h3 id="stop-and-destroy-a-container">Stop and destroy a container</h3> |
203 | <div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="ex">docker</span> stop backstabbing_galileo <span class="co"># those docker guys are really rude to physicists!</span> | 205 | <div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="ex">docker</span> stop backstabbing_galileo # those docker guys are really rude to physicists! |
204 | <span class="ex">backstabbing_galileo</span> | 206 | <span class="ex">backstabbing_galileo</span> |
205 | 207 | ||
206 | <span class="co"># check the container is stopped</span> | 208 | <span class="co"># check the container is stopped</span> |
@@ -213,14 +215,15 @@ $ <span class="ex">docker</span> ps -a | |||
213 | <span class="ex">d40b7af693d6</span> shaarli/shaarli /usr/bin/supervisor 5 minutes ago Exited (0) <span class="ex">48</span> seconds ago backstabbing_galileo | 215 | <span class="ex">d40b7af693d6</span> shaarli/shaarli /usr/bin/supervisor 5 minutes ago Exited (0) <span class="ex">48</span> seconds ago backstabbing_galileo |
214 | 216 | ||
215 | <span class="co"># destroy the container</span> | 217 | <span class="co"># destroy the container</span> |
216 | $ <span class="ex">docker</span> rm backstabbing_galileo <span class="co"># let's put an end to these barbarian practices</span> | 218 | $ <span class="ex">docker</span> rm backstabbing_galileo # let<span class="st">'s put an end to these barbarian practices</span> |
217 | <span class="ex">backstabbing_galileo</span> | 219 | <span class="st">backstabbing_galileo</span> |
218 | 220 | ||
219 | $ <span class="ex">docker</span> ps -a | 221 | <span class="st">$ docker ps -a</span> |
220 | <span class="ex">CONTAINER</span> ID IMAGE COMMAND CREATED STATUS PORTS NAMES</code></pre></div> | 222 | <span class="st">CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES</span></code></pre></div> |
221 | <h2 id="resources">Resources</h2> | 223 | <h2 id="resources">Resources</h2> |
222 | <h3 id="docker-1">Docker</h3> | 224 | <h3 id="docker-1">Docker</h3> |
223 | <ul> | 225 | <ul> |
226 | <li><a href="https://www.katacoda.com/courses/docker/">Interactive Docker training portal</a> on <a href="https://www.katacoda.com/">Katakoda</a><a href=".html"></a></li> | ||
224 | <li><a href="http://blog.thoward37.me/articles/where-are-docker-images-stored/">Where are Docker images stored?</a><a href=".html"></a></li> | 227 | <li><a href="http://blog.thoward37.me/articles/where-are-docker-images-stored/">Where are Docker images stored?</a><a href=".html"></a></li> |
225 | <li><a href="https://docs.docker.com/reference/builder/">Dockerfile reference</a><a href=".html"></a></li> | 228 | <li><a href="https://docs.docker.com/reference/builder/">Dockerfile reference</a><a href=".html"></a></li> |
226 | <li><a href="https://docs.docker.com/articles/dockerfile_best-practices/">Dockerfile best practices</a><a href=".html"></a></li> | 229 | <li><a href="https://docs.docker.com/articles/dockerfile_best-practices/">Dockerfile best practices</a><a href=".html"></a></li> |
diff --git a/doc/Docker.md b/doc/Docker.md index 1faa7904..a7d2efb5 100644 --- a/doc/Docker.md +++ b/doc/Docker.md | |||
@@ -141,6 +141,7 @@ CONTAINER ID IMAGE COMMAND CREATED STATUS | |||
141 | 141 | ||
142 | ## Resources | 142 | ## Resources |
143 | ### Docker | 143 | ### Docker |
144 | - [Interactive Docker training portal](https://www.katacoda.com/courses/docker/) on [Katakoda](https://www.katacoda.com/)[](.html) | ||
144 | - [Where are Docker images stored?](http://blog.thoward37.me/articles/where-are-docker-images-stored/)[](.html) | 145 | - [Where are Docker images stored?](http://blog.thoward37.me/articles/where-are-docker-images-stored/)[](.html) |
145 | - [Dockerfile reference](https://docs.docker.com/reference/builder/)[](.html) | 146 | - [Dockerfile reference](https://docs.docker.com/reference/builder/)[](.html) |
146 | - [Dockerfile best practices](https://docs.docker.com/articles/dockerfile_best-practices/)[](.html) | 147 | - [Dockerfile best practices](https://docs.docker.com/articles/dockerfile_best-practices/)[](.html) |
diff --git a/doc/Download-CSS-styles-from-an-OPML-list.html b/doc/Download-CSS-styles-from-an-OPML-list.html index a4f68ac6..18cc5d9a 100644 --- a/doc/Download-CSS-styles-from-an-OPML-list.html +++ b/doc/Download-CSS-styles-from-an-OPML-list.html | |||
@@ -69,6 +69,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | 69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> |
70 | <li><a href="Firefox-share.html">Firefox share</a></li> | 70 | <li><a href="Firefox-share.html">Firefox share</a></li> |
71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | 71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> |
72 | <li><a href="REST-API.html">REST API</a></li> | ||
72 | </ul></li> | 73 | </ul></li> |
73 | <li>How To | 74 | <li>How To |
74 | <ul> | 75 | <ul> |
@@ -87,6 +88,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
87 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | 88 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> |
88 | <li><a href="Plugin-System.html">Plugin System</a></li> | 89 | <li><a href="Plugin-System.html">Plugin System</a></li> |
89 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | 90 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> |
91 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
90 | <li><a href="Security.html">Security</a></li> | 92 | <li><a href="Security.html">Security</a></li> |
91 | <li><a href="Static-analysis.html">Static analysis</a></li> | 93 | <li><a href="Static-analysis.html">Static analysis</a></li> |
92 | <li><a href="Theming.html">Theming</a></li> | 94 | <li><a href="Theming.html">Theming</a></li> |
@@ -155,7 +157,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
155 | <span class="kw">function</span> copyUserStyleFrom<span class="ot">(</span><span class="kw">$url</span><span class="ot">,</span> <span class="kw">$name</span><span class="ot">,</span> <span class="kw">$knownStyles</span><span class="ot">)</span> { | 157 | <span class="kw">function</span> copyUserStyleFrom<span class="ot">(</span><span class="kw">$url</span><span class="ot">,</span> <span class="kw">$name</span><span class="ot">,</span> <span class="kw">$knownStyles</span><span class="ot">)</span> { |
156 | <span class="kw">$userStyle</span> = <span class="kw">$url</span>.<span class="st">"inc/user.css"</span><span class="ot">;</span> | 158 | <span class="kw">$userStyle</span> = <span class="kw">$url</span>.<span class="st">"inc/user.css"</span><span class="ot">;</span> |
157 | <span class="kw">if</span><span class="ot">(</span><span class="fu">in_array</span><span class="ot">(</span><span class="kw">$url</span><span class="ot">,</span> <span class="kw">$knownStyles</span><span class="ot">))</span> { | 159 | <span class="kw">if</span><span class="ot">(</span><span class="fu">in_array</span><span class="ot">(</span><span class="kw">$url</span><span class="ot">,</span> <span class="kw">$knownStyles</span><span class="ot">))</span> { |
158 | <span class="co">// TODO add log message</span> | 160 | <span class="co">// </span><span class="al">TODO</span><span class="co"> add log message</span> |
159 | } <span class="kw">else</span> { | 161 | } <span class="kw">else</span> { |
160 | <span class="kw">$statusCode</span> = get_http_response_code<span class="ot">(</span><span class="kw">$userStyle</span><span class="ot">);</span> | 162 | <span class="kw">$statusCode</span> = get_http_response_code<span class="ot">(</span><span class="kw">$userStyle</span><span class="ot">);</span> |
161 | <span class="kw">if</span><span class="ot">(</span><span class="fu">intval</span><span class="ot">(</span><span class="kw">$statusCode</span><span class="ot">)</span><<span class="dv">300</span><span class="ot">)</span> { | 163 | <span class="kw">if</span><span class="ot">(</span><span class="fu">intval</span><span class="ot">(</span><span class="kw">$statusCode</span><span class="ot">)</span><<span class="dv">300</span><span class="ot">)</span> { |
diff --git a/doc/Download-and-Installation.html b/doc/Download-and-Installation.html index b9cac360..2c5b3be2 100644 --- a/doc/Download-and-Installation.html +++ b/doc/Download-and-Installation.html | |||
@@ -69,6 +69,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | 69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> |
70 | <li><a href="Firefox-share.html">Firefox share</a></li> | 70 | <li><a href="Firefox-share.html">Firefox share</a></li> |
71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | 71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> |
72 | <li><a href="REST-API.html">REST API</a></li> | ||
72 | </ul></li> | 73 | </ul></li> |
73 | <li>How To | 74 | <li>How To |
74 | <ul> | 75 | <ul> |
@@ -87,6 +88,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
87 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | 88 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> |
88 | <li><a href="Plugin-System.html">Plugin System</a></li> | 89 | <li><a href="Plugin-System.html">Plugin System</a></li> |
89 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | 90 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> |
91 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
90 | <li><a href="Security.html">Security</a></li> | 92 | <li><a href="Security.html">Security</a></li> |
91 | <li><a href="Static-analysis.html">Static analysis</a></li> | 93 | <li><a href="Static-analysis.html">Static analysis</a></li> |
92 | <li><a href="Theming.html">Theming</a></li> | 94 | <li><a href="Theming.html">Theming</a></li> |
@@ -108,10 +110,10 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
108 | <h3 id="download-as-an-archive">Download as an archive</h3> | 110 | <h3 id="download-as-an-archive">Download as an archive</h3> |
109 | <p>Get the latest released version from the <a href="https://github.com/shaarli/Shaarli/releases">releases</a> page.<a href=".html"></a></p> | 111 | <p>Get the latest released version from the <a href="https://github.com/shaarli/Shaarli/releases">releases</a> page.<a href=".html"></a></p> |
110 | <p><strong>Download our <em>shaarli-full</em> archive</strong> to include dependencies.</p> | 112 | <p><strong>Download our <em>shaarli-full</em> archive</strong> to include dependencies.</p> |
111 | <p>The current latest released version is <code>v0.8.0</code></p> | 113 | <p>The current latest released version is <code>v0.8.4</code></p> |
112 | <p>Or in command lines:</p> | 114 | <p>Or in command lines:</p> |
113 | <div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="fu">wget</span> https://github.com/shaarli/Shaarli/releases/download/v0.8.0/shaarli-v0.8.0-full.zip | 115 | <div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="fu">wget</span> https://github.com/shaarli/Shaarli/releases/download/v0.8.4/shaarli-v0.8.4-full.zip |
114 | $ <span class="fu">unzip</span> shaarli-v0.8.0-full.zip | 116 | $ <span class="fu">unzip</span> shaarli-v0.8.4-full.zip |
115 | $ <span class="fu">mv</span> Shaarli /path/to/shaarli/</code></pre></div> | 117 | $ <span class="fu">mv</span> Shaarli /path/to/shaarli/</code></pre></div> |
116 | <table style="width:46%;"> | 118 | <table style="width:46%;"> |
117 | <colgroup> | 119 | <colgroup> |
@@ -129,8 +131,8 @@ $ <span class="fu">mv</span> Shaarli /path/to/shaarli/</code></pre></div> | |||
129 | </table> | 131 | </table> |
130 | <h3 id="using-git">Using git</h3> | 132 | <h3 id="using-git">Using git</h3> |
131 | <pre><code>mkdir -p /path/to/shaarli && cd /path/to/shaarli/ | 133 | <pre><code>mkdir -p /path/to/shaarli && cd /path/to/shaarli/ |
132 | git clone -b v0.8.0 https://github.com/shaarli/Shaarli.git . | 134 | git clone -b v0.8 https://github.com/shaarli/Shaarli.git . |
133 | composer update --no-dev</code></pre> | 135 | composer install --no-dev</code></pre> |
134 | <hr /> | 136 | <hr /> |
135 | <h2 id="stable-version">Stable version</h2> | 137 | <h2 id="stable-version">Stable version</h2> |
136 | <p>The stable version has been experienced by Shaarli users, and will receive security updates.</p> | 138 | <p>The stable version has been experienced by Shaarli users, and will receive security updates.</p> |
@@ -148,16 +150,16 @@ $ <span class="fu">mv</span> Shaarli-stable /path/to/shaarli/</code></pre></div> | |||
148 | <div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="fu">git</span> clone https://github.com/shaarli/Shaarli.git -b stable /path/to/shaarli/ | 150 | <div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="fu">git</span> clone https://github.com/shaarli/Shaarli.git -b stable /path/to/shaarli/ |
149 | <span class="co"># install/update third-party dependencies</span> | 151 | <span class="co"># install/update third-party dependencies</span> |
150 | $ <span class="bu">cd</span> /path/to/shaarli/ | 152 | $ <span class="bu">cd</span> /path/to/shaarli/ |
151 | $ <span class="ex">composer</span> update --no-dev</code></pre></div> | 153 | $ <span class="ex">composer</span> install --no-dev</code></pre></div> |
152 | <hr /> | 154 | <hr /> |
153 | <h2 id="development-version-mainline">Development version (mainline)</h2> | 155 | <h2 id="development-version-mainline">Development version (mainline)</h2> |
154 | <p><em>Use at your own risk!</em></p> | 156 | <p><em>Use at your own risk!</em></p> |
155 | <p>To get the latest changes from the <code>master</code> branch:</p> | 157 | <p>To get the latest changes from the <code>master</code> branch:</p> |
156 | <div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="co"># clone the repository </span> | 158 | <div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="co"># clone the repository </span> |
157 | $ <span class="fu">git</span> clone https://github.com/shaarli/Shaarli.git master /path/to/shaarli/ | 159 | $ <span class="fu">git</span> clone https://github.com/shaarli/Shaarli.git -b master /path/to/shaarli/ |
158 | <span class="co"># install/update third-party dependencies</span> | 160 | <span class="co"># install/update third-party dependencies</span> |
159 | $ <span class="bu">cd</span> /path/to/shaarli | 161 | $ <span class="bu">cd</span> /path/to/shaarli |
160 | $ <span class="ex">composer</span> update --no-dev</code></pre></div> | 162 | $ <span class="ex">composer</span> install --no-dev</code></pre></div> |
161 | <hr /> | 163 | <hr /> |
162 | <h2 id="finish-installation">Finish Installation</h2> | 164 | <h2 id="finish-installation">Finish Installation</h2> |
163 | <p>Once Shaarli is downloaded and files have been placed at the correct location, open it this location your favorite browser.</p> | 165 | <p>Once Shaarli is downloaded and files have been placed at the correct location, open it this location your favorite browser.</p> |
diff --git a/doc/Download-and-Installation.md b/doc/Download-and-Installation.md index 32df8984..970144a5 100644 --- a/doc/Download-and-Installation.md +++ b/doc/Download-and-Installation.md | |||
@@ -13,13 +13,13 @@ Get the latest released version from the [releases](https://github.com/shaarli/S | |||
13 | 13 | ||
14 | **Download our *shaarli-full* archive** to include dependencies. | 14 | **Download our *shaarli-full* archive** to include dependencies. |
15 | 15 | ||
16 | The current latest released version is `v0.8.0` | 16 | The current latest released version is `v0.8.4` |
17 | 17 | ||
18 | Or in command lines: | 18 | Or in command lines: |
19 | 19 | ||
20 | ```bash | 20 | ```bash |
21 | $ wget https://github.com/shaarli/Shaarli/releases/download/v0.8.0/shaarli-v0.8.0-full.zip | 21 | $ wget https://github.com/shaarli/Shaarli/releases/download/v0.8.4/shaarli-v0.8.4-full.zip |
22 | $ unzip shaarli-v0.8.0-full.zip | 22 | $ unzip shaarli-v0.8.4-full.zip |
23 | $ mv Shaarli /path/to/shaarli/ | 23 | $ mv Shaarli /path/to/shaarli/ |
24 | ``` | 24 | ``` |
25 | 25 | ||
@@ -30,8 +30,8 @@ $ mv Shaarli /path/to/shaarli/ | |||
30 | 30 | ||
31 | ``` | 31 | ``` |
32 | mkdir -p /path/to/shaarli && cd /path/to/shaarli/ | 32 | mkdir -p /path/to/shaarli && cd /path/to/shaarli/ |
33 | git clone -b v0.8.0 https://github.com/shaarli/Shaarli.git . | 33 | git clone -b v0.8 https://github.com/shaarli/Shaarli.git . |
34 | composer update --no-dev | 34 | composer install --no-dev |
35 | ``` | 35 | ``` |
36 | 36 | ||
37 | -------------------------------------------------------- | 37 | -------------------------------------------------------- |
@@ -66,7 +66,7 @@ $ mv Shaarli-stable /path/to/shaarli/ | |||
66 | $ git clone https://github.com/shaarli/Shaarli.git -b stable /path/to/shaarli/ | 66 | $ git clone https://github.com/shaarli/Shaarli.git -b stable /path/to/shaarli/ |
67 | # install/update third-party dependencies | 67 | # install/update third-party dependencies |
68 | $ cd /path/to/shaarli/ | 68 | $ cd /path/to/shaarli/ |
69 | $ composer update --no-dev | 69 | $ composer install --no-dev |
70 | ``` | 70 | ``` |
71 | 71 | ||
72 | -------------------------------------------------------- | 72 | -------------------------------------------------------- |
@@ -79,10 +79,10 @@ To get the latest changes from the `master` branch: | |||
79 | 79 | ||
80 | ```bash | 80 | ```bash |
81 | # clone the repository | 81 | # clone the repository |
82 | $ git clone https://github.com/shaarli/Shaarli.git master /path/to/shaarli/ | 82 | $ git clone https://github.com/shaarli/Shaarli.git -b master /path/to/shaarli/ |
83 | # install/update third-party dependencies | 83 | # install/update third-party dependencies |
84 | $ cd /path/to/shaarli | 84 | $ cd /path/to/shaarli |
85 | $ composer update --no-dev | 85 | $ composer install --no-dev |
86 | ``` | 86 | ``` |
87 | 87 | ||
88 | -------------------------------------------------------- | 88 | -------------------------------------------------------- |
diff --git a/doc/Example-patch---add-new-via-field-for-links.html b/doc/Example-patch---add-new-via-field-for-links.html index 133224e2..49036a74 100644 --- a/doc/Example-patch---add-new-via-field-for-links.html +++ b/doc/Example-patch---add-new-via-field-for-links.html | |||
@@ -32,6 +32,7 @@ | |||
32 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | 32 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> |
33 | <li><a href="Firefox-share.html">Firefox share</a></li> | 33 | <li><a href="Firefox-share.html">Firefox share</a></li> |
34 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | 34 | <li><a href="RSS-feeds.html">RSS feeds</a></li> |
35 | <li><a href="REST-API.html">REST API</a></li> | ||
35 | </ul></li> | 36 | </ul></li> |
36 | <li>How To | 37 | <li>How To |
37 | <ul> | 38 | <ul> |
@@ -50,6 +51,7 @@ | |||
50 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | 51 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> |
51 | <li><a href="Plugin-System.html">Plugin System</a></li> | 52 | <li><a href="Plugin-System.html">Plugin System</a></li> |
52 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | 53 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> |
54 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
53 | <li><a href="Security.html">Security</a></li> | 55 | <li><a href="Security.html">Security</a></li> |
54 | <li><a href="Static-analysis.html">Static analysis</a></li> | 56 | <li><a href="Static-analysis.html">Static analysis</a></li> |
55 | <li><a href="Theming.html">Theming</a></li> | 57 | <li><a href="Theming.html">Theming</a></li> |
diff --git a/doc/FAQ.html b/doc/FAQ.html index 61f3475f..25584f22 100644 --- a/doc/FAQ.html +++ b/doc/FAQ.html | |||
@@ -32,6 +32,7 @@ | |||
32 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | 32 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> |
33 | <li><a href="Firefox-share.html">Firefox share</a></li> | 33 | <li><a href="Firefox-share.html">Firefox share</a></li> |
34 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | 34 | <li><a href="RSS-feeds.html">RSS feeds</a></li> |
35 | <li><a href="REST-API.html">REST API</a></li> | ||
35 | </ul></li> | 36 | </ul></li> |
36 | <li>How To | 37 | <li>How To |
37 | <ul> | 38 | <ul> |
@@ -50,6 +51,7 @@ | |||
50 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | 51 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> |
51 | <li><a href="Plugin-System.html">Plugin System</a></li> | 52 | <li><a href="Plugin-System.html">Plugin System</a></li> |
52 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | 53 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> |
54 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
53 | <li><a href="Security.html">Security</a></li> | 55 | <li><a href="Security.html">Security</a></li> |
54 | <li><a href="Static-analysis.html">Static analysis</a></li> | 56 | <li><a href="Static-analysis.html">Static analysis</a></li> |
55 | <li><a href="Theming.html">Theming</a></li> | 57 | <li><a href="Theming.html">Theming</a></li> |
diff --git a/doc/Firefox-share.html b/doc/Firefox-share.html index d7dcc282..707119a6 100644 --- a/doc/Firefox-share.html +++ b/doc/Firefox-share.html | |||
@@ -32,6 +32,7 @@ | |||
32 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | 32 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> |
33 | <li><a href="Firefox-share.html">Firefox share</a></li> | 33 | <li><a href="Firefox-share.html">Firefox share</a></li> |
34 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | 34 | <li><a href="RSS-feeds.html">RSS feeds</a></li> |
35 | <li><a href="REST-API.html">REST API</a></li> | ||
35 | </ul></li> | 36 | </ul></li> |
36 | <li>How To | 37 | <li>How To |
37 | <ul> | 38 | <ul> |
@@ -50,6 +51,7 @@ | |||
50 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | 51 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> |
51 | <li><a href="Plugin-System.html">Plugin System</a></li> | 52 | <li><a href="Plugin-System.html">Plugin System</a></li> |
52 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | 53 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> |
54 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
53 | <li><a href="Security.html">Security</a></li> | 55 | <li><a href="Security.html">Security</a></li> |
54 | <li><a href="Static-analysis.html">Static analysis</a></li> | 56 | <li><a href="Static-analysis.html">Static analysis</a></li> |
55 | <li><a href="Theming.html">Theming</a></li> | 57 | <li><a href="Theming.html">Theming</a></li> |
diff --git a/doc/GnuPG-signature.html b/doc/GnuPG-signature.html index 50b904d5..182a71d4 100644 --- a/doc/GnuPG-signature.html +++ b/doc/GnuPG-signature.html | |||
@@ -69,6 +69,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | 69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> |
70 | <li><a href="Firefox-share.html">Firefox share</a></li> | 70 | <li><a href="Firefox-share.html">Firefox share</a></li> |
71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | 71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> |
72 | <li><a href="REST-API.html">REST API</a></li> | ||
72 | </ul></li> | 73 | </ul></li> |
73 | <li>How To | 74 | <li>How To |
74 | <ul> | 75 | <ul> |
@@ -87,6 +88,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
87 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | 88 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> |
88 | <li><a href="Plugin-System.html">Plugin System</a></li> | 89 | <li><a href="Plugin-System.html">Plugin System</a></li> |
89 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | 90 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> |
91 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
90 | <li><a href="Security.html">Security</a></li> | 92 | <li><a href="Security.html">Security</a></li> |
91 | <li><a href="Static-analysis.html">Static analysis</a></li> | 93 | <li><a href="Static-analysis.html">Static analysis</a></li> |
92 | <li><a href="Theming.html">Theming</a></li> | 94 | <li><a href="Theming.html">Theming</a></li> |
diff --git a/doc/Home.html b/doc/Home.html index 970f547e..7f51b93b 100644 --- a/doc/Home.html +++ b/doc/Home.html | |||
@@ -32,6 +32,7 @@ | |||
32 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | 32 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> |
33 | <li><a href="Firefox-share.html">Firefox share</a></li> | 33 | <li><a href="Firefox-share.html">Firefox share</a></li> |
34 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | 34 | <li><a href="RSS-feeds.html">RSS feeds</a></li> |
35 | <li><a href="REST-API.html">REST API</a></li> | ||
35 | </ul></li> | 36 | </ul></li> |
36 | <li>How To | 37 | <li>How To |
37 | <ul> | 38 | <ul> |
@@ -50,6 +51,7 @@ | |||
50 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | 51 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> |
51 | <li><a href="Plugin-System.html">Plugin System</a></li> | 52 | <li><a href="Plugin-System.html">Plugin System</a></li> |
52 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | 53 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> |
54 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
53 | <li><a href="Security.html">Security</a></li> | 55 | <li><a href="Security.html">Security</a></li> |
54 | <li><a href="Static-analysis.html">Static analysis</a></li> | 56 | <li><a href="Static-analysis.html">Static analysis</a></li> |
55 | <li><a href="Theming.html">Theming</a></li> | 57 | <li><a href="Theming.html">Theming</a></li> |
diff --git a/doc/Plugin-System.html b/doc/Plugin-System.html index 655536c6..123bf106 100644 --- a/doc/Plugin-System.html +++ b/doc/Plugin-System.html | |||
@@ -69,6 +69,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | 69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> |
70 | <li><a href="Firefox-share.html">Firefox share</a></li> | 70 | <li><a href="Firefox-share.html">Firefox share</a></li> |
71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | 71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> |
72 | <li><a href="REST-API.html">REST API</a></li> | ||
72 | </ul></li> | 73 | </ul></li> |
73 | <li>How To | 74 | <li>How To |
74 | <ul> | 75 | <ul> |
@@ -87,6 +88,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
87 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | 88 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> |
88 | <li><a href="Plugin-System.html">Plugin System</a></li> | 89 | <li><a href="Plugin-System.html">Plugin System</a></li> |
89 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | 90 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> |
91 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
90 | <li><a href="Security.html">Security</a></li> | 92 | <li><a href="Security.html">Security</a></li> |
91 | <li><a href="Static-analysis.html">Static analysis</a></li> | 93 | <li><a href="Static-analysis.html">Static analysis</a></li> |
92 | <li><a href="Theming.html">Theming</a></li> | 94 | <li><a href="Theming.html">Theming</a></li> |
@@ -100,9 +102,6 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
100 | </ul> | 102 | </ul> |
101 | </div> | 103 | </div> |
102 | <h1 id="plugin-system">Plugin System</h1> | 104 | <h1 id="plugin-system">Plugin System</h1> |
103 | <blockquote> | ||
104 | <p>Note: Plugin current status - in development (not merged into master).</p> | ||
105 | </blockquote> | ||
106 | <p><a href="#developer-api"><strong>I am a developer.</strong> Developer API.</a><a href=".html"></a></p> | 105 | <p><a href="#developer-api"><strong>I am a developer.</strong> Developer API.</a><a href=".html"></a></p> |
107 | <p><a href="#guide-for-template-designer"><strong>I am a template designer.</strong> Guide for template designer.</a><a href=".html"></a></p> | 106 | <p><a href="#guide-for-template-designer"><strong>I am a template designer.</strong> Guide for template designer.</a><a href=".html"></a></p> |
108 | <h2 id="developer-api">Developer API</h2> | 107 | <h2 id="developer-api">Developer API</h2> |
@@ -121,12 +120,21 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
121 | | plugins/ | 120 | | plugins/ |
122 | |---| demo_plugin/ | 121 | |---| demo_plugin/ |
123 | | |---| demo_plugin.php</code></pre> | 122 | | |---| demo_plugin.php</code></pre> |
123 | <h3 id="plugin-initialization">Plugin initialization</h3> | ||
124 | <p>At the beginning of Shaarli execution, all enabled plugins are loaded. At this point, the plugin system looks for an <code>init()</code> function to execute and run it if it exists. This function must be named this way, and takes the <code>ConfigManager</code> as parameter.</p> | ||
125 | <pre><code><plugin_name>_init($conf)</code></pre> | ||
126 | <p>This function can be used to create initial data, load default settings, etc. But also to set <em>plugin errors</em>. If the initialization function returns an array of strings, they will be understand as errors, and displayed in the header to logged in users.</p> | ||
124 | <h3 id="understanding-hooks">Understanding hooks</h3> | 127 | <h3 id="understanding-hooks">Understanding hooks</h3> |
125 | <p>A plugin is a set of functions. Each function will be triggered by the plugin system at certain point in Shaarli execution.</p> | 128 | <p>A plugin is a set of functions. Each function will be triggered by the plugin system at certain point in Shaarli execution.</p> |
126 | <p>These functions need to be named with this pattern:</p> | 129 | <p>These functions need to be named with this pattern:</p> |
127 | <pre><code>hook_<plugin_name>_<hook_name></code></pre> | 130 | <pre><code>hook_<plugin_name>_<hook_name>($data, $conf)</code></pre> |
131 | <p>Parameters:</p> | ||
132 | <ul> | ||
133 | <li>data: see <a href="https://github.com/shaarli/Shaarli/wiki/Plugin-System#plugins-data">$data section</a><a href=".html"></a></li> | ||
134 | <li>conf: the <code>ConfigManager</code> instance.</li> | ||
135 | </ul> | ||
128 | <p>For exemple, if my plugin want to add data to the header, this function is needed:</p> | 136 | <p>For exemple, if my plugin want to add data to the header, this function is needed:</p> |
129 | <pre><code>hook_demo_plugin_render_header()</code></pre> | 137 | <pre><code>hook_demo_plugin_render_header</code></pre> |
130 | <p>If this function is declared, and the plugin enabled, it will be called every time Shaarli is rendering the header.</p> | 138 | <p>If this function is declared, and the plugin enabled, it will be called every time Shaarli is rendering the header.</p> |
131 | <h3 id="plugins-data">Plugin's data</h3> | 139 | <h3 id="plugins-data">Plugin's data</h3> |
132 | <h4 id="parameters">Parameters</h4> | 140 | <h4 id="parameters">Parameters</h4> |
@@ -159,6 +167,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
159 | <ul> | 167 | <ul> |
160 | <li><code>description</code>: plugin description</li> | 168 | <li><code>description</code>: plugin description</li> |
161 | <li><code>parameters</code>: user parameter names, separated by a <code>;</code>.</li> | 169 | <li><code>parameters</code>: user parameter names, separated by a <code>;</code>.</li> |
170 | <li><code>parameter.<PARAMETER_NAME></code>: add a text description the specified parameter.</li> | ||
162 | </ul> | 171 | </ul> |
163 | <blockquote> | 172 | <blockquote> |
164 | <p>Note: In PHP, <code>parse_ini_file()</code> seems to want strings to be between by quotes <code>"</code> in the ini file.</p> | 173 | <p>Note: In PHP, <code>parse_ini_file()</code> seems to want strings to be between by quotes <code>"</code> in the ini file.</p> |
@@ -209,16 +218,28 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
209 | </tr> | 218 | </tr> |
210 | <tr class="even"> | 219 | <tr class="even"> |
211 | <td><a href="#render_tagcloud">render_tagcloud</a></td> | 220 | <td><a href="#render_tagcloud">render_tagcloud</a></td> |
212 | <td style="text-align: center;">Allow to add content at the top and bottom of the page.</td> | 221 | <td style="text-align: center;">Allow to add content at the top and bottom of the page, and after all tags.</td> |
213 | </tr> | 222 | </tr> |
214 | <tr class="odd"> | 223 | <tr class="odd"> |
224 | <td><a href="#render_taglist">render_taglist</a></td> | ||
225 | <td style="text-align: center;">Allow to add content at the top and bottom of the page, and after all tags.</td> | ||
226 | </tr> | ||
227 | <tr class="even"> | ||
215 | <td><a href="#render_daily">render_daily</a></td> | 228 | <td><a href="#render_daily">render_daily</a></td> |
216 | <td style="text-align: center;">Allow to add content at the top and bottom of the page, the bottom of each link and to alter data.</td> | 229 | <td style="text-align: center;">Allow to add content at the top and bottom of the page, the bottom of each link and to alter data.</td> |
217 | </tr> | 230 | </tr> |
231 | <tr class="odd"> | ||
232 | <td><a href="#render_feed">render_feed</a></td> | ||
233 | <td style="text-align: center;">Allow to do add tags in RSS and ATOM feeds.</td> | ||
234 | </tr> | ||
218 | <tr class="even"> | 235 | <tr class="even"> |
219 | <td><a href="#savelink">savelink</a></td> | 236 | <td><a href="#save_link">save_link</a></td> |
220 | <td style="text-align: center;">Allow to alter the link being saved in the datastore.</td> | 237 | <td style="text-align: center;">Allow to alter the link being saved in the datastore.</td> |
221 | </tr> | 238 | </tr> |
239 | <tr class="odd"> | ||
240 | <td><a href="#delete_link">delete_link</a></td> | ||
241 | <td style="text-align: center;">Allow to do an action before a link is deleted from the datastore.</td> | ||
242 | </tr> | ||
222 | </tbody> | 243 | </tbody> |
223 | </table> | 244 | </table> |
224 | <h4 id="render_header">render_header</h4> | 245 | <h4 id="render_header">render_header</h4> |
@@ -376,17 +397,41 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
376 | <li><p><code>plugin_start_zone</code>: before displaying the template content.</p></li> | 397 | <li><p><code>plugin_start_zone</code>: before displaying the template content.</p></li> |
377 | <li><p><code>plugin_end_zone</code>: after displaying the template content.</p></li> | 398 | <li><p><code>plugin_end_zone</code>: after displaying the template content.</p></li> |
378 | </ul> | 399 | </ul> |
400 | <p>For each tag, the following placeholder can be used:</p> | ||
401 | <ul> | ||
402 | <li><code>tag_plugin</code>: after each tag</li> | ||
403 | </ul> | ||
379 | <p><img src="http://i.imgur.com/vHmyT3a.png" alt="plugin_start_end_zone_example" /><a href=".html"></a></p> | 404 | <p><img src="http://i.imgur.com/vHmyT3a.png" alt="plugin_start_end_zone_example" /><a href=".html"></a></p> |
405 | <h4 id="render_taglist">render_taglist</h4> | ||
406 | <p>Triggered when taglist is displayed.</p> | ||
407 | <p>Allow to add content at the top and bottom of the page.</p> | ||
408 | <h5 id="data-8">Data</h5> | ||
409 | <p><code>$data</code> is an array containing:</p> | ||
410 | <ul> | ||
411 | <li><code>_LOGGEDIN_</code>: true if user is logged in, false otherwise.</li> | ||
412 | <li>All templates data.</li> | ||
413 | </ul> | ||
414 | <h5 id="template-placeholders-8">Template placeholders</h5> | ||
415 | <p>Items can be displayed in templates by adding an entry in <code>$data['<placeholder>']</code> array.<a href=".html"></a></p> | ||
416 | <p>List of placeholders:</p> | ||
417 | <ul> | ||
418 | <li><p><code>plugin_start_zone</code>: before displaying the template content.</p></li> | ||
419 | <li><p><code>plugin_end_zone</code>: after displaying the template content.</p></li> | ||
420 | </ul> | ||
421 | <p>For each tag, the following placeholder can be used:</p> | ||
422 | <ul> | ||
423 | <li><code>tag_plugin</code>: after each tag</li> | ||
424 | </ul> | ||
380 | <h4 id="render_daily">render_daily</h4> | 425 | <h4 id="render_daily">render_daily</h4> |
381 | <p>Triggered when tagcloud is displayed.</p> | 426 | <p>Triggered when tagcloud is displayed.</p> |
382 | <p>Allow to add content at the top and bottom of the page, the bottom of each link and to alter data.</p> | 427 | <p>Allow to add content at the top and bottom of the page, the bottom of each link and to alter data.</p> |
383 | <h5 id="data-8">Data</h5> | 428 | <h5 id="data-9">Data</h5> |
384 | <p><code>$data</code> is an array containing:</p> | 429 | <p><code>$data</code> is an array containing:</p> |
385 | <ul> | 430 | <ul> |
386 | <li><code>_LOGGEDIN_</code>: true if user is logged in, false otherwise.</li> | 431 | <li><code>_LOGGEDIN_</code>: true if user is logged in, false otherwise.</li> |
387 | <li>All templates data, including links.</li> | 432 | <li>All templates data, including links.</li> |
388 | </ul> | 433 | </ul> |
389 | <h5 id="template-placeholders-8">Template placeholders</h5> | 434 | <h5 id="template-placeholders-9">Template placeholders</h5> |
390 | <p>Items can be displayed in templates by adding an entry in <code>$data['<placeholder>']</code> array.<a href=".html"></a></p> | 435 | <p>Items can be displayed in templates by adding an entry in <code>$data['<placeholder>']</code> array.<a href=".html"></a></p> |
391 | <p>List of placeholders:</p> | 436 | <p>List of placeholders:</p> |
392 | <ul> | 437 | <ul> |
@@ -397,18 +442,57 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
397 | <li><p><code>plugin_start_zone</code>: before displaying the template content.</p></li> | 442 | <li><p><code>plugin_start_zone</code>: before displaying the template content.</p></li> |
398 | <li><p><code>plugin_end_zone</code>: after displaying the template content.</p></li> | 443 | <li><p><code>plugin_end_zone</code>: after displaying the template content.</p></li> |
399 | </ul> | 444 | </ul> |
400 | <h4 id="savelink">savelink</h4> | 445 | <h4 id="render_feed">render_feed</h4> |
446 | <p>Triggered when the ATOM or RSS feed is displayed.</p> | ||
447 | <p>Allow to add tags in the feed, either in the header or for each items. Items (links) can also be altered before being rendered.</p> | ||
448 | <h5 id="data-10">Data</h5> | ||
449 | <p><code>$data</code> is an array containing:</p> | ||
450 | <ul> | ||
451 | <li><code>_LOGGEDIN_</code>: true if user is logged in, false otherwise.</li> | ||
452 | <li><code>_PAGE_</code>: containing either <code>rss</code> or <code>atom</code>.</li> | ||
453 | <li>All templates data, including links.</li> | ||
454 | </ul> | ||
455 | <h5 id="template-placeholders-10">Template placeholders</h5> | ||
456 | <p>Tags can be added in feeds by adding an entry in <code>$data['<placeholder>']</code> array.<a href=".html"></a></p> | ||
457 | <p>List of placeholders:</p> | ||
458 | <ul> | ||
459 | <li><code>feed_plugins_header</code>: used as a header tag in the feed.</li> | ||
460 | </ul> | ||
461 | <p>For each links:</p> | ||
462 | <ul> | ||
463 | <li><code>feed_plugins</code>: additional tag for every link entry.</li> | ||
464 | </ul> | ||
465 | <h4 id="save_link">save_link</h4> | ||
401 | <p>Triggered when a link is save (new link or edit).</p> | 466 | <p>Triggered when a link is save (new link or edit).</p> |
402 | <p>Allow to alter the link being saved in the datastore.</p> | 467 | <p>Allow to alter the link being saved in the datastore.</p> |
403 | <h5 id="data-9">Data</h5> | 468 | <h5 id="data-11">Data</h5> |
404 | <p><code>$data</code> is an array containing the link being saved:</p> | 469 | <p><code>$data</code> is an array containing the link being saved:</p> |
405 | <ul> | 470 | <ul> |
471 | <li>id</li> | ||
406 | <li>title</li> | 472 | <li>title</li> |
407 | <li>url</li> | 473 | <li>url</li> |
474 | <li>shorturl</li> | ||
408 | <li>description</li> | 475 | <li>description</li> |
409 | <li>linkdate</li> | ||
410 | <li>private</li> | 476 | <li>private</li> |
411 | <li>tags</li> | 477 | <li>tags</li> |
478 | <li>created</li> | ||
479 | <li>updated</li> | ||
480 | </ul> | ||
481 | <h4 id="delete_link">delete_link</h4> | ||
482 | <p>Triggered when a link is deleted.</p> | ||
483 | <p>Allow to execute any action before the link is actually removed from the datastore</p> | ||
484 | <h5 id="data-12">Data</h5> | ||
485 | <p><code>$data</code> is an array containing the link being saved:</p> | ||
486 | <ul> | ||
487 | <li>id</li> | ||
488 | <li>title</li> | ||
489 | <li>url</li> | ||
490 | <li>shorturl</li> | ||
491 | <li>description</li> | ||
492 | <li>private</li> | ||
493 | <li>tags</li> | ||
494 | <li>created</li> | ||
495 | <li>updated</li> | ||
412 | </ul> | 496 | </ul> |
413 | <h2 id="guide-for-template-designer">Guide for template designer</h2> | 497 | <h2 id="guide-for-template-designer">Guide for template designer</h2> |
414 | <h3 id="plugin-administration">Plugin administration</h3> | 498 | <h3 id="plugin-administration">Plugin administration</h3> |
@@ -537,5 +621,14 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
537 | {$value} | 621 | {$value} |
538 | {/loop} | 622 | {/loop} |
539 | <span class="kw"></div></span></code></pre></div> | 623 | <span class="kw"></div></span></code></pre></div> |
624 | <p><strong>feed.atom.xml</strong> and <strong>feed.rss.xml</strong>:</p> | ||
625 | <p>In headers tags section:</p> | ||
626 | <div class="sourceCode"><pre class="sourceCode xml"><code class="sourceCode xml">{loop="$feed_plugins_header"} | ||
627 | {$value} | ||
628 | {/loop}</code></pre></div> | ||
629 | <p>After each entry:</p> | ||
630 | <div class="sourceCode"><pre class="sourceCode xml"><code class="sourceCode xml">{loop="$value.feed_plugins"} | ||
631 | {$value} | ||
632 | {/loop}</code></pre></div> | ||
540 | </body> | 633 | </body> |
541 | </html> | 634 | </html> |
diff --git a/doc/Plugin-System.md b/doc/Plugin-System.md index 623627dd..addd792d 100644 --- a/doc/Plugin-System.md +++ b/doc/Plugin-System.md | |||
@@ -1,6 +1,4 @@ | |||
1 | #Plugin System | 1 | #Plugin System |
2 | > Note: Plugin current status - in development (not merged into master). | ||
3 | |||
4 | [**I am a developer.** Developer API.](#developer-api)[](.html) | 2 | [**I am a developer.** Developer API.](#developer-api)[](.html) |
5 | 3 | ||
6 | [**I am a template designer.** Guide for template designer.](#guide-for-template-designer)[](.html) | 4 | [**I am a template designer.** Guide for template designer.](#guide-for-template-designer)[](.html) |
@@ -30,6 +28,14 @@ You should have the following tree view: | |||
30 | | |---| demo_plugin.php | 28 | | |---| demo_plugin.php |
31 | ``` | 29 | ``` |
32 | 30 | ||
31 | ### Plugin initialization | ||
32 | |||
33 | At the beginning of Shaarli execution, all enabled plugins are loaded. At this point, the plugin system looks for an `init()` function to execute and run it if it exists. This function must be named this way, and takes the `ConfigManager` as parameter. | ||
34 | |||
35 | <plugin_name>_init($conf) | ||
36 | |||
37 | This function can be used to create initial data, load default settings, etc. But also to set *plugin errors*. If the initialization function returns an array of strings, they will be understand as errors, and displayed in the header to logged in users. | ||
38 | |||
33 | ### Understanding hooks | 39 | ### Understanding hooks |
34 | 40 | ||
35 | A plugin is a set of functions. Each function will be triggered by the plugin system at certain point in Shaarli execution. | 41 | A plugin is a set of functions. Each function will be triggered by the plugin system at certain point in Shaarli execution. |
@@ -37,12 +43,17 @@ A plugin is a set of functions. Each function will be triggered by the plugin sy | |||
37 | These functions need to be named with this pattern: | 43 | These functions need to be named with this pattern: |
38 | 44 | ||
39 | ``` | 45 | ``` |
40 | hook_<plugin_name>_<hook_name> | 46 | hook_<plugin_name>_<hook_name>($data, $conf) |
41 | ``` | 47 | ``` |
42 | 48 | ||
49 | Parameters: | ||
50 | |||
51 | - data: see [$data section](https://github.com/shaarli/Shaarli/wiki/Plugin-System#plugins-data)[](.html) | ||
52 | - conf: the `ConfigManager` instance. | ||
53 | |||
43 | For exemple, if my plugin want to add data to the header, this function is needed: | 54 | For exemple, if my plugin want to add data to the header, this function is needed: |
44 | 55 | ||
45 | hook_demo_plugin_render_header() | 56 | hook_demo_plugin_render_header |
46 | 57 | ||
47 | If this function is declared, and the plugin enabled, it will be called every time Shaarli is rendering the header. | 58 | If this function is declared, and the plugin enabled, it will be called every time Shaarli is rendering the header. |
48 | 59 | ||
@@ -98,6 +109,7 @@ Each file contain two keys: | |||
98 | 109 | ||
99 | * `description`: plugin description | 110 | * `description`: plugin description |
100 | * `parameters`: user parameter names, separated by a `;`. | 111 | * `parameters`: user parameter names, separated by a `;`. |
112 | * `parameter.<PARAMETER_NAME>`: add a text description the specified parameter. | ||
101 | 113 | ||
102 | > Note: In PHP, `parse_ini_file()` seems to want strings to be between by quotes `"` in the ini file. | 114 | > Note: In PHP, `parse_ini_file()` seems to want strings to be between by quotes `"` in the ini file. |
103 | 115 | ||
@@ -118,9 +130,13 @@ If it's still not working, please [open an issue](https://github.com/shaarli/Sha | |||
118 | | [render_editlink](#render_editlink) | Allow to add fields in the form, or display elements. |[](.html) | 130 | | [render_editlink](#render_editlink) | Allow to add fields in the form, or display elements. |[](.html) |
119 | | [render_tools](#render_tools) | Allow to add content at the end of the page. |[](.html) | 131 | | [render_tools](#render_tools) | Allow to add content at the end of the page. |[](.html) |
120 | | [render_picwall](#render_picwall) | Allow to add content at the top and bottom of the page. |[](.html) | 132 | | [render_picwall](#render_picwall) | Allow to add content at the top and bottom of the page. |[](.html) |
121 | | [render_tagcloud](#render_tagcloud) | Allow to add content at the top and bottom of the page. |[](.html) | 133 | | [render_tagcloud](#render_tagcloud) | Allow to add content at the top and bottom of the page, and after all tags. |[](.html) |
134 | | [render_taglist](#render_taglist) | Allow to add content at the top and bottom of the page, and after all tags. |[](.html) | ||
122 | | [render_daily](#render_daily) | Allow to add content at the top and bottom of the page, the bottom of each link and to alter data. |[](.html) | 135 | | [render_daily](#render_daily) | Allow to add content at the top and bottom of the page, the bottom of each link and to alter data. |[](.html) |
123 | | [savelink](#savelink) | Allow to alter the link being saved in the datastore. |[](.html) | 136 | | [render_feed](#render_feed) | Allow to do add tags in RSS and ATOM feeds. |[](.html) |
137 | | [save_link](#save_link) | Allow to alter the link being saved in the datastore. |[](.html) | ||
138 | | [delete_link](#delete_link) | Allow to do an action before a link is deleted from the datastore. |[](.html) | ||
139 | |||
124 | 140 | ||
125 | 141 | ||
126 | #### render_header | 142 | #### render_header |
@@ -330,8 +346,40 @@ List of placeholders: | |||
330 | 346 | ||
331 | * `plugin_end_zone`: after displaying the template content. | 347 | * `plugin_end_zone`: after displaying the template content. |
332 | 348 | ||
349 | For each tag, the following placeholder can be used: | ||
350 | |||
351 | * `tag_plugin`: after each tag | ||
352 | |||
333 | ![plugin_start_end_zone_example](http://i.imgur.com/vHmyT3a.png)[](.html) | 353 | ![plugin_start_end_zone_example](http://i.imgur.com/vHmyT3a.png)[](.html) |
334 | 354 | ||
355 | |||
356 | #### render_taglist | ||
357 | |||
358 | Triggered when taglist is displayed. | ||
359 | |||
360 | Allow to add content at the top and bottom of the page. | ||
361 | |||
362 | ##### Data | ||
363 | |||
364 | `$data` is an array containing: | ||
365 | |||
366 | * `_LOGGEDIN_`: true if user is logged in, false otherwise. | ||
367 | * All templates data. | ||
368 | |||
369 | ##### Template placeholders | ||
370 | |||
371 | Items can be displayed in templates by adding an entry in `$data['<placeholder>']` array.[](.html) | ||
372 | |||
373 | List of placeholders: | ||
374 | |||
375 | * `plugin_start_zone`: before displaying the template content. | ||
376 | |||
377 | * `plugin_end_zone`: after displaying the template content. | ||
378 | |||
379 | For each tag, the following placeholder can be used: | ||
380 | |||
381 | * `tag_plugin`: after each tag | ||
382 | |||
335 | #### render_daily | 383 | #### render_daily |
336 | 384 | ||
337 | Triggered when tagcloud is displayed. | 385 | Triggered when tagcloud is displayed. |
@@ -359,7 +407,33 @@ List of placeholders: | |||
359 | 407 | ||
360 | * `plugin_end_zone`: after displaying the template content. | 408 | * `plugin_end_zone`: after displaying the template content. |
361 | 409 | ||
362 | #### savelink | 410 | #### render_feed |
411 | |||
412 | Triggered when the ATOM or RSS feed is displayed. | ||
413 | |||
414 | Allow to add tags in the feed, either in the header or for each items. Items (links) can also be altered before being rendered. | ||
415 | |||
416 | ##### Data | ||
417 | |||
418 | `$data` is an array containing: | ||
419 | |||
420 | * `_LOGGEDIN_`: true if user is logged in, false otherwise. | ||
421 | * `_PAGE_`: containing either `rss` or `atom`. | ||
422 | * All templates data, including links. | ||
423 | |||
424 | ##### Template placeholders | ||
425 | |||
426 | Tags can be added in feeds by adding an entry in `$data['<placeholder>']` array.[](.html) | ||
427 | |||
428 | List of placeholders: | ||
429 | |||
430 | * `feed_plugins_header`: used as a header tag in the feed. | ||
431 | |||
432 | For each links: | ||
433 | |||
434 | * `feed_plugins`: additional tag for every link entry. | ||
435 | |||
436 | #### save_link | ||
363 | 437 | ||
364 | Triggered when a link is save (new link or edit). | 438 | Triggered when a link is save (new link or edit). |
365 | 439 | ||
@@ -369,12 +443,36 @@ Allow to alter the link being saved in the datastore. | |||
369 | 443 | ||
370 | `$data` is an array containing the link being saved: | 444 | `$data` is an array containing the link being saved: |
371 | 445 | ||
446 | * id | ||
447 | * title | ||
448 | * url | ||
449 | * shorturl | ||
450 | * description | ||
451 | * private | ||
452 | * tags | ||
453 | * created | ||
454 | * updated | ||
455 | |||
456 | |||
457 | #### delete_link | ||
458 | |||
459 | Triggered when a link is deleted. | ||
460 | |||
461 | Allow to execute any action before the link is actually removed from the datastore | ||
462 | |||
463 | ##### Data | ||
464 | |||
465 | `$data` is an array containing the link being saved: | ||
466 | |||
467 | * id | ||
372 | * title | 468 | * title |
373 | * url | 469 | * url |
470 | * shorturl | ||
374 | * description | 471 | * description |
375 | * linkdate | ||
376 | * private | 472 | * private |
377 | * tags | 473 | * tags |
474 | * created | ||
475 | * updated | ||
378 | 476 | ||
379 | ## Guide for template designer | 477 | ## Guide for template designer |
380 | 478 | ||
@@ -595,3 +693,19 @@ Bottom: | |||
595 | {/loop} | 693 | {/loop} |
596 | </div> | 694 | </div> |
597 | ``` | 695 | ``` |
696 | |||
697 | **feed.atom.xml** and **feed.rss.xml**: | ||
698 | |||
699 | In headers tags section: | ||
700 | ```xml | ||
701 | {loop="$feed_plugins_header"} | ||
702 | {$value} | ||
703 | {/loop} | ||
704 | ``` | ||
705 | |||
706 | After each entry: | ||
707 | ```xml | ||
708 | {loop="$value.feed_plugins"} | ||
709 | {$value} | ||
710 | {/loop} | ||
711 | ``` | ||
diff --git a/doc/Plugins.html b/doc/Plugins.html index 435a836f..08ce8a86 100644 --- a/doc/Plugins.html +++ b/doc/Plugins.html | |||
@@ -69,6 +69,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | 69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> |
70 | <li><a href="Firefox-share.html">Firefox share</a></li> | 70 | <li><a href="Firefox-share.html">Firefox share</a></li> |
71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | 71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> |
72 | <li><a href="REST-API.html">REST API</a></li> | ||
72 | </ul></li> | 73 | </ul></li> |
73 | <li>How To | 74 | <li>How To |
74 | <ul> | 75 | <ul> |
@@ -87,6 +88,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
87 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | 88 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> |
88 | <li><a href="Plugin-System.html">Plugin System</a></li> | 89 | <li><a href="Plugin-System.html">Plugin System</a></li> |
89 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | 90 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> |
91 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
90 | <li><a href="Security.html">Security</a></li> | 92 | <li><a href="Security.html">Security</a></li> |
91 | <li><a href="Static-analysis.html">Static analysis</a></li> | 93 | <li><a href="Static-analysis.html">Static analysis</a></li> |
92 | <li><a href="Theming.html">Theming</a></li> | 94 | <li><a href="Theming.html">Theming</a></li> |
@@ -145,7 +147,6 @@ Example:</p> | |||
145 | <li><a href="https://github.com/shaarli/Shaarli/blob/master/plugins/markdown/README.md"><code>markdown</code></a>: Render shaare description with Markdown syntax.<a href=".html"></a></li> | 147 | <li><a href="https://github.com/shaarli/Shaarli/blob/master/plugins/markdown/README.md"><code>markdown</code></a>: Render shaare description with Markdown syntax.<a href=".html"></a></li> |
146 | <li><a href="https://github.com/shaarli/Shaarli/blob/master/plugins/playvideos/README.md"><code>playvideos</code></a>: Add a button in the toolbar allowing to watch all videos.<a href=".html"></a></li> | 148 | <li><a href="https://github.com/shaarli/Shaarli/blob/master/plugins/playvideos/README.md"><code>playvideos</code></a>: Add a button in the toolbar allowing to watch all videos.<a href=".html"></a></li> |
147 | <li><code>qrcode</code>: For each link, add a QRCode icon.</li> | 149 | <li><code>qrcode</code>: For each link, add a QRCode icon.</li> |
148 | <li><code>readityourself</code>: For each link, add a ReadItYourself icon to save the shaared URL</li> | ||
149 | <li><a href="https://github.com/shaarli/Shaarli/blob/master/plugins/wallabag/README.md"><code>wallabag</code></a>: For each link, add a Wallabag icon to save it in your instance.<a href=".html"></a></li> | 150 | <li><a href="https://github.com/shaarli/Shaarli/blob/master/plugins/wallabag/README.md"><code>wallabag</code></a>: For each link, add a Wallabag icon to save it in your instance.<a href=".html"></a></li> |
150 | </ul> | 151 | </ul> |
151 | <h4 id="third-party-plugins">Third party plugins</h4> | 152 | <h4 id="third-party-plugins">Third party plugins</h4> |
diff --git a/doc/Plugins.md b/doc/Plugins.md index 81167fcf..e3192a60 100644 --- a/doc/Plugins.md +++ b/doc/Plugins.md | |||
@@ -67,7 +67,6 @@ Usage of each plugin is documented in it's README file: | |||
67 | * [`markdown`](https://github.com/shaarli/Shaarli/blob/master/plugins/markdown/README.md): Render shaare description with Markdown syntax.[](.html) | 67 | * [`markdown`](https://github.com/shaarli/Shaarli/blob/master/plugins/markdown/README.md): Render shaare description with Markdown syntax.[](.html) |
68 | * [`playvideos`](https://github.com/shaarli/Shaarli/blob/master/plugins/playvideos/README.md): Add a button in the toolbar allowing to watch all videos.[](.html) | 68 | * [`playvideos`](https://github.com/shaarli/Shaarli/blob/master/plugins/playvideos/README.md): Add a button in the toolbar allowing to watch all videos.[](.html) |
69 | * `qrcode`: For each link, add a QRCode icon. | 69 | * `qrcode`: For each link, add a QRCode icon. |
70 | * `readityourself`: For each link, add a ReadItYourself icon to save the shaared URL | ||
71 | * [`wallabag`](https://github.com/shaarli/Shaarli/blob/master/plugins/wallabag/README.md): For each link, add a Wallabag icon to save it in your instance.[](.html) | 70 | * [`wallabag`](https://github.com/shaarli/Shaarli/blob/master/plugins/wallabag/README.md): For each link, add a Wallabag icon to save it in your instance.[](.html) |
72 | 71 | ||
73 | 72 | ||
diff --git a/doc/REST-API.html b/doc/REST-API.html new file mode 100644 index 00000000..d14c98c9 --- /dev/null +++ b/doc/REST-API.html | |||
@@ -0,0 +1,169 @@ | |||
1 | <!DOCTYPE html> | ||
2 | <html> | ||
3 | <head> | ||
4 | <meta charset="utf-8"> | ||
5 | <meta name="generator" content="pandoc"> | ||
6 | <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes"> | ||
7 | <title>Shaarli – REST API</title> | ||
8 | <style type="text/css">code{white-space: pre;}</style> | ||
9 | <style type="text/css"> | ||
10 | div.sourceCode { overflow-x: auto; } | ||
11 | table.sourceCode, tr.sourceCode, td.lineNumbers, td.sourceCode { | ||
12 | margin: 0; padding: 0; vertical-align: baseline; border: none; } | ||
13 | table.sourceCode { width: 100%; line-height: 100%; } | ||
14 | td.lineNumbers { text-align: right; padding-right: 4px; padding-left: 4px; color: #aaaaaa; border-right: 1px solid #aaaaaa; } | ||
15 | td.sourceCode { padding-left: 5px; } | ||
16 | code > span.kw { color: #007020; font-weight: bold; } /* Keyword */ | ||
17 | code > span.dt { color: #902000; } /* DataType */ | ||
18 | code > span.dv { color: #40a070; } /* DecVal */ | ||
19 | code > span.bn { color: #40a070; } /* BaseN */ | ||
20 | code > span.fl { color: #40a070; } /* Float */ | ||
21 | code > span.ch { color: #4070a0; } /* Char */ | ||
22 | code > span.st { color: #4070a0; } /* String */ | ||
23 | code > span.co { color: #60a0b0; font-style: italic; } /* Comment */ | ||
24 | code > span.ot { color: #007020; } /* Other */ | ||
25 | code > span.al { color: #ff0000; font-weight: bold; } /* Alert */ | ||
26 | code > span.fu { color: #06287e; } /* Function */ | ||
27 | code > span.er { color: #ff0000; font-weight: bold; } /* Error */ | ||
28 | code > span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */ | ||
29 | code > span.cn { color: #880000; } /* Constant */ | ||
30 | code > span.sc { color: #4070a0; } /* SpecialChar */ | ||
31 | code > span.vs { color: #4070a0; } /* VerbatimString */ | ||
32 | code > span.ss { color: #bb6688; } /* SpecialString */ | ||
33 | code > span.im { } /* Import */ | ||
34 | code > span.va { color: #19177c; } /* Variable */ | ||
35 | code > span.cf { color: #007020; font-weight: bold; } /* ControlFlow */ | ||
36 | code > span.op { color: #666666; } /* Operator */ | ||
37 | code > span.bu { } /* BuiltIn */ | ||
38 | code > span.ex { } /* Extension */ | ||
39 | code > span.pp { color: #bc7a00; } /* Preprocessor */ | ||
40 | code > span.at { color: #7d9029; } /* Attribute */ | ||
41 | code > span.do { color: #ba2121; font-style: italic; } /* Documentation */ | ||
42 | code > span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */ | ||
43 | code > span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */ | ||
44 | code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */ | ||
45 | </style> | ||
46 | <link rel="stylesheet" href="github-markdown.css"> | ||
47 | <!--[if lt IE 9]> | ||
48 | <script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script> | ||
49 | <![endif]--> | ||
50 | </head> | ||
51 | <body> | ||
52 | <div id="local-sidebar"> | ||
53 | <ul> | ||
54 | <li><a href="Home.html">Home</a></li> | ||
55 | <li>Setup | ||
56 | <ul> | ||
57 | <li><a href="Download-and-Installation.html">Download and Installation</a></li> | ||
58 | <li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li> | ||
59 | <li><a href="Server-requirements.html">Server requirements</a></li> | ||
60 | <li><a href="Server-configuration.html">Server configuration</a></li> | ||
61 | <li><a href="Server-security.html">Server security</a></li> | ||
62 | <li><a href="Shaarli-configuration.html">Shaarli configuration</a></li> | ||
63 | <li><a href="Plugins.html">Plugins</a></li> | ||
64 | </ul></li> | ||
65 | <li><a href="Docker.html">Docker</a></li> | ||
66 | <li><a href="Usage.html">Usage</a> | ||
67 | <ul> | ||
68 | <li><a href="Sharing-button.html">Sharing button</a> (bookmarklet)</li> | ||
69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | ||
70 | <li><a href="Firefox-share.html">Firefox share</a></li> | ||
71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | ||
72 | <li><a href="REST-API.html">REST API</a></li> | ||
73 | </ul></li> | ||
74 | <li>How To | ||
75 | <ul> | ||
76 | <li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li> | ||
77 | <li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li> | ||
78 | <li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li> | ||
79 | <li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> | ||
80 | <li><a href="Datastore-hacks.html">Datastore hacks</a></li> | ||
81 | </ul></li> | ||
82 | <li><a href="Troubleshooting.html">Troubleshooting</a></li> | ||
83 | <li><a href="Development.html">Development</a> | ||
84 | <ul> | ||
85 | <li><a href="GnuPG-signature.html">GnuPG signature</a></li> | ||
86 | <li><a href="Coding-guidelines.html">Coding guidelines</a></li> | ||
87 | <li><a href="Directory-structure.html">Directory structure</a></li> | ||
88 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | ||
89 | <li><a href="Plugin-System.html">Plugin System</a></li> | ||
90 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | ||
91 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
92 | <li><a href="Security.html">Security</a></li> | ||
93 | <li><a href="Static-analysis.html">Static analysis</a></li> | ||
94 | <li><a href="Theming.html">Theming</a></li> | ||
95 | <li><a href="Unit-tests.html">Unit tests</a></li> | ||
96 | </ul></li> | ||
97 | <li>About | ||
98 | <ul> | ||
99 | <li><a href="FAQ.html">FAQ</a></li> | ||
100 | <li><a href="Community-&-Related-software.html">Community & Related software</a></li> | ||
101 | </ul></li> | ||
102 | </ul> | ||
103 | </div> | ||
104 | <h1 id="rest-api">REST API</h1> | ||
105 | <h2 id="usage">Usage</h2> | ||
106 | <p>See the <a href="http://shaarli.github.io/api-documentation/">REST API documentation</a>.<a href=".html"></a></p> | ||
107 | <h2 id="authentication">Authentication</h2> | ||
108 | <p>All requests to Shaarli's API must include a JWT token to verify their authenticity.</p> | ||
109 | <p>This token has to be included as an HTTP header called <code>Authentication: Bearer <jwt token></code>.</p> | ||
110 | <p>JWT resources :</p> | ||
111 | <ul> | ||
112 | <li><a href="https://jwt.io">jwt.io</a> (including a list of client per language).<a href=".html"></a></li> | ||
113 | <li>RFC : <a href="https://tools.ietf.org/html/rfc7519" class="uri">https://tools.ietf.org/html/rfc7519</a></li> | ||
114 | <li><a href="https://float-middle.com/json-web-tokens-jwt-vs-sessions/" class="uri">https://float-middle.com/json-web-tokens-jwt-vs-sessions/</a></li> | ||
115 | <li>HackerNews thread: <a href="https://news.ycombinator.com/item?id=11929267" class="uri">https://news.ycombinator.com/item?id=11929267</a></li> | ||
116 | </ul> | ||
117 | <h3 id="shaarli-jwt-token">Shaarli JWT Token</h3> | ||
118 | <p>JWT tokens are composed by three parts, separated by a dot <code>.</code> and encoded in base64:</p> | ||
119 | <pre><code>[header].[payload].[signature][](.html)</code></pre> | ||
120 | <h4 id="header">Header</h4> | ||
121 | <p>Shaarli only allow one hash algorithm, so the header will always be the same:</p> | ||
122 | <div class="sourceCode"><pre class="sourceCode json"><code class="sourceCode json"><span class="fu">{</span> | ||
123 | <span class="dt">"typ"</span><span class="fu">:</span> <span class="st">"JWT"</span><span class="fu">,</span> | ||
124 | <span class="dt">"alg"</span><span class="fu">:</span> <span class="st">"HS512"</span> | ||
125 | <span class="fu">}</span></code></pre></div> | ||
126 | <p>Encoded in base64, it gives:</p> | ||
127 | <pre><code>ewogICAgICAgICJ0eXAiOiAiSldUIiwKICAgICAgICAiYWxnIjogIkhTNTEyIgogICAgfQ==</code></pre> | ||
128 | <h4 id="payload">Payload</h4> | ||
129 | <p><strong>Validity duration</strong></p> | ||
130 | <p>To avoid infinite token validity, JWT tokens must include their creation date in UNIX timestamp format (timezone independant - UTC) under the key <code>iat</code> (issued at). This token will be accepted during 9 minutes.</p> | ||
131 | <div class="sourceCode"><pre class="sourceCode json"><code class="sourceCode json"><span class="fu">{</span> | ||
132 | <span class="dt">"iat"</span><span class="fu">:</span> <span class="dv">1468663519</span> | ||
133 | <span class="fu">}</span></code></pre></div> | ||
134 | <p>See <a href="https://tools.ietf.org/html/rfc7519#section-4.1.6">RFC reference</a>.<a href=".html"></a></p> | ||
135 | <h4 id="signature">Signature</h4> | ||
136 | <p>The signature authenticate the token validity. It contains the base64 of the header and the body, separated by a dot <code>.</code>, hashed in SHA512 with the API secret available in Shaarli administration page.</p> | ||
137 | <p>Signature example with PHP:</p> | ||
138 | <div class="sourceCode"><pre class="sourceCode php"><code class="sourceCode php"><span class="kw">$content</span> = <span class="fu">base64_encode</span><span class="ot">(</span><span class="kw">$header</span><span class="ot">)</span> . <span class="st">'.'</span> . <span class="fu">base64_encode</span><span class="ot">(</span><span class="kw">$payload</span><span class="ot">);</span> | ||
139 | <span class="kw">$signature</span> = <span class="fu">hash_hmac</span><span class="ot">(</span><span class="st">'sha512'</span><span class="ot">,</span> <span class="kw">$content</span><span class="ot">,</span> <span class="kw">$secret</span><span class="ot">);</span></code></pre></div> | ||
140 | <h3 id="complete-example">Complete example</h3> | ||
141 | <h4 id="php">PHP</h4> | ||
142 | <div class="sourceCode"><pre class="sourceCode php"><code class="sourceCode php"><span class="kw">function</span> generateToken<span class="ot">(</span><span class="kw">$secret</span><span class="ot">)</span> { | ||
143 | <span class="kw">$header</span> = <span class="fu">base64_encode</span><span class="ot">(</span><span class="st">'{</span> | ||
144 | <span class="st"> "typ": "JWT",</span> | ||
145 | <span class="st"> "alg": "HS512"</span> | ||
146 | <span class="st"> }'</span><span class="ot">);</span> | ||
147 | <span class="kw">$payload</span> = <span class="fu">base64_encode</span><span class="ot">(</span><span class="st">'{</span> | ||
148 | <span class="st"> "iat": '</span>. <span class="fu">time</span><span class="ot">()</span> .<span class="st">'</span> | ||
149 | <span class="st"> }'</span><span class="ot">);</span> | ||
150 | <span class="kw">$signature</span> = <span class="fu">hash_hmac</span><span class="ot">(</span><span class="st">'sha512'</span><span class="ot">,</span> <span class="kw">$header</span> .<span class="st">'.'</span>. <span class="kw">$payload</span> <span class="ot">,</span> <span class="kw">$secret</span><span class="ot">);</span> | ||
151 | <span class="kw">return</span> <span class="kw">$header</span> .<span class="st">'.'</span>. <span class="kw">$payload</span> .<span class="st">'.'</span>. <span class="kw">$signature</span><span class="ot">;</span> | ||
152 | } | ||
153 | |||
154 | <span class="kw">$secret</span> = <span class="st">'mysecret'</span><span class="ot">;</span> | ||
155 | <span class="kw">$token</span> = generateToken<span class="ot">(</span><span class="kw">$secret</span><span class="ot">);</span> | ||
156 | <span class="fu">echo</span> <span class="kw">$token</span><span class="ot">;</span></code></pre></div> | ||
157 | <blockquote> | ||
158 | <p><code>ewogICAgICAgICJ0eXAiOiAiSldUIiwKICAgICAgICAiYWxnIjogIkhTNTEyIgogICAgfQ==.ewogICAgICAgICJpYXQiOiAxNDY4NjY3MDQ3CiAgICB9.1d2c54fa947daf594fdbf7591796195652c8bc63bffad7f6a6db2a41c313f495a542cbfb595acade79e83f3810d709b4251d7b940bbc10b531a6e6134af63a68</code></p> | ||
159 | </blockquote> | ||
160 | <div class="sourceCode"><pre class="sourceCode php"><code class="sourceCode php"><span class="kw">$options</span> = <span class="ot">[[](</span>.html<span class="ot">)</span> | ||
161 | <span class="st">'http'</span> => <span class="ot">[[](</span>.html<span class="ot">)</span> | ||
162 | <span class="st">'method'</span> => <span class="st">'GET'</span><span class="ot">,</span> | ||
163 | <span class="st">'jwt'</span> => <span class="kw">$token</span><span class="ot">,</span> | ||
164 | <span class="ot">],</span> | ||
165 | <span class="ot">];</span> | ||
166 | <span class="kw">$context</span> = <span class="fu">stream_context_create</span><span class="ot">(</span><span class="kw">$options</span><span class="ot">);</span> | ||
167 | <span class="fu">file_get_contents</span><span class="ot">(</span><span class="kw">$apiEndpoint</span><span class="ot">,</span> <span class="kw">false</span><span class="ot">,</span> <span class="kw">$context</span><span class="ot">);</span></code></pre></div> | ||
168 | </body> | ||
169 | </html> | ||
diff --git a/doc/REST-API.md b/doc/REST-API.md new file mode 100644 index 00000000..d7909978 --- /dev/null +++ b/doc/REST-API.md | |||
@@ -0,0 +1,105 @@ | |||
1 | #REST API | ||
2 | ## Usage | ||
3 | |||
4 | See the [REST API documentation](http://shaarli.github.io/api-documentation/).[](.html) | ||
5 | |||
6 | ## Authentication | ||
7 | |||
8 | All requests to Shaarli's API must include a JWT token to verify their authenticity. | ||
9 | |||
10 | This token has to be included as an HTTP header called `Authentication: Bearer <jwt token>`. | ||
11 | |||
12 | JWT resources : | ||
13 | |||
14 | * [jwt.io](https://jwt.io) (including a list of client per language).[](.html) | ||
15 | * RFC : https://tools.ietf.org/html/rfc7519 | ||
16 | * https://float-middle.com/json-web-tokens-jwt-vs-sessions/ | ||
17 | * HackerNews thread: https://news.ycombinator.com/item?id=11929267 | ||
18 | |||
19 | |||
20 | ### Shaarli JWT Token | ||
21 | |||
22 | JWT tokens are composed by three parts, separated by a dot `.` and encoded in base64: | ||
23 | |||
24 | ``` | ||
25 | [header].[payload].[signature][](.html) | ||
26 | ``` | ||
27 | |||
28 | #### Header | ||
29 | |||
30 | Shaarli only allow one hash algorithm, so the header will always be the same: | ||
31 | |||
32 | ```json | ||
33 | { | ||
34 | "typ": "JWT", | ||
35 | "alg": "HS512" | ||
36 | } | ||
37 | ``` | ||
38 | |||
39 | Encoded in base64, it gives: | ||
40 | |||
41 | ``` | ||
42 | ewogICAgICAgICJ0eXAiOiAiSldUIiwKICAgICAgICAiYWxnIjogIkhTNTEyIgogICAgfQ== | ||
43 | ``` | ||
44 | |||
45 | #### Payload | ||
46 | |||
47 | **Validity duration** | ||
48 | |||
49 | To avoid infinite token validity, JWT tokens must include their creation date in UNIX timestamp format (timezone independant - UTC) under the key `iat` (issued at). This token will be accepted during 9 minutes. | ||
50 | |||
51 | ```json | ||
52 | { | ||
53 | "iat": 1468663519 | ||
54 | } | ||
55 | ``` | ||
56 | |||
57 | See [RFC reference](https://tools.ietf.org/html/rfc7519#section-4.1.6).[](.html) | ||
58 | |||
59 | |||
60 | #### Signature | ||
61 | |||
62 | The signature authenticate the token validity. It contains the base64 of the header and the body, separated by a dot `.`, hashed in SHA512 with the API secret available in Shaarli administration page. | ||
63 | |||
64 | Signature example with PHP: | ||
65 | |||
66 | ```php | ||
67 | $content = base64_encode($header) . '.' . base64_encode($payload); | ||
68 | $signature = hash_hmac('sha512', $content, $secret); | ||
69 | ``` | ||
70 | |||
71 | |||
72 | ### Complete example | ||
73 | |||
74 | #### PHP | ||
75 | |||
76 | ```php | ||
77 | function generateToken($secret) { | ||
78 | $header = base64_encode('{ | ||
79 | "typ": "JWT", | ||
80 | "alg": "HS512" | ||
81 | }'); | ||
82 | $payload = base64_encode('{ | ||
83 | "iat": '. time() .' | ||
84 | }'); | ||
85 | $signature = hash_hmac('sha512', $header .'.'. $payload , $secret); | ||
86 | return $header .'.'. $payload .'.'. $signature; | ||
87 | } | ||
88 | |||
89 | $secret = 'mysecret'; | ||
90 | $token = generateToken($secret); | ||
91 | echo $token; | ||
92 | ``` | ||
93 | |||
94 | > `ewogICAgICAgICJ0eXAiOiAiSldUIiwKICAgICAgICAiYWxnIjogIkhTNTEyIgogICAgfQ==.ewogICAgICAgICJpYXQiOiAxNDY4NjY3MDQ3CiAgICB9.1d2c54fa947daf594fdbf7591796195652c8bc63bffad7f6a6db2a41c313f495a542cbfb595acade79e83f3810d709b4251d7b940bbc10b531a6e6134af63a68` | ||
95 | |||
96 | ```php | ||
97 | $options = [[](.html) | ||
98 | 'http' => [[](.html) | ||
99 | 'method' => 'GET', | ||
100 | 'jwt' => $token, | ||
101 | ], | ||
102 | ]; | ||
103 | $context = stream_context_create($options); | ||
104 | file_get_contents($apiEndpoint, false, $context); | ||
105 | ``` | ||
diff --git a/doc/RSS-feeds.html b/doc/RSS-feeds.html index 0f332b3d..0ebfecc6 100644 --- a/doc/RSS-feeds.html +++ b/doc/RSS-feeds.html | |||
@@ -32,6 +32,7 @@ | |||
32 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | 32 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> |
33 | <li><a href="Firefox-share.html">Firefox share</a></li> | 33 | <li><a href="Firefox-share.html">Firefox share</a></li> |
34 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | 34 | <li><a href="RSS-feeds.html">RSS feeds</a></li> |
35 | <li><a href="REST-API.html">REST API</a></li> | ||
35 | </ul></li> | 36 | </ul></li> |
36 | <li>How To | 37 | <li>How To |
37 | <ul> | 38 | <ul> |
@@ -50,6 +51,7 @@ | |||
50 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | 51 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> |
51 | <li><a href="Plugin-System.html">Plugin System</a></li> | 52 | <li><a href="Plugin-System.html">Plugin System</a></li> |
52 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | 53 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> |
54 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
53 | <li><a href="Security.html">Security</a></li> | 55 | <li><a href="Security.html">Security</a></li> |
54 | <li><a href="Static-analysis.html">Static analysis</a></li> | 56 | <li><a href="Static-analysis.html">Static analysis</a></li> |
55 | <li><a href="Theming.html">Theming</a></li> | 57 | <li><a href="Theming.html">Theming</a></li> |
diff --git a/doc/Release-Shaarli.html b/doc/Release-Shaarli.html index 0d9fa3e1..fa690c7c 100644 --- a/doc/Release-Shaarli.html +++ b/doc/Release-Shaarli.html | |||
@@ -69,6 +69,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | 69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> |
70 | <li><a href="Firefox-share.html">Firefox share</a></li> | 70 | <li><a href="Firefox-share.html">Firefox share</a></li> |
71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | 71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> |
72 | <li><a href="REST-API.html">REST API</a></li> | ||
72 | </ul></li> | 73 | </ul></li> |
73 | <li>How To | 74 | <li>How To |
74 | <ul> | 75 | <ul> |
@@ -87,6 +88,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
87 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | 88 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> |
88 | <li><a href="Plugin-System.html">Plugin System</a></li> | 89 | <li><a href="Plugin-System.html">Plugin System</a></li> |
89 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | 90 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> |
91 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
90 | <li><a href="Security.html">Security</a></li> | 92 | <li><a href="Security.html">Security</a></li> |
91 | <li><a href="Static-analysis.html">Static analysis</a></li> | 93 | <li><a href="Static-analysis.html">Static analysis</a></li> |
92 | <li><a href="Theming.html">Theming</a></li> | 94 | <li><a href="Theming.html">Theming</a></li> |
@@ -192,6 +194,8 @@ $ <span class="fu">git</span> verify-tag f7762cf803f03f5caf4b8078359a63783d0090c | |||
192 | <span class="ex">gpg</span>: Signature made Thu 30 Jul 2015 11:46:34 CEST using RSA key ID 4100DF6F | 194 | <span class="ex">gpg</span>: Signature made Thu 30 Jul 2015 11:46:34 CEST using RSA key ID 4100DF6F |
193 | <span class="ex">gpg</span>: Good signature from <span class="st">"VirtualTam <virtualtam@flibidi.net>"</span> [ultimate][](.html)</code></pre></div> | 195 | <span class="ex">gpg</span>: Good signature from <span class="st">"VirtualTam <virtualtam@flibidi.net>"</span> [ultimate][](.html)</code></pre></div> |
194 | <h2 id="publish-the-github-release">Publish the GitHub release</h2> | 196 | <h2 id="publish-the-github-release">Publish the GitHub release</h2> |
197 | <h3 id="update-release-badges">Update release badges</h3> | ||
198 | <p>Update <code>README.md</code> so version badges display and point to the newly released Shaarli version(s).</p> | ||
195 | <h3 id="create-a-github-release-from-a-git-tag">Create a GitHub release from a Git tag</h3> | 199 | <h3 id="create-a-github-release-from-a-git-tag">Create a GitHub release from a Git tag</h3> |
196 | <p>From the previously drafted release:</p> | 200 | <p>From the previously drafted release:</p> |
197 | <ul> | 201 | <ul> |
diff --git a/doc/Release-Shaarli.md b/doc/Release-Shaarli.md index 556a96ee..ced58853 100644 --- a/doc/Release-Shaarli.md +++ b/doc/Release-Shaarli.md | |||
@@ -103,6 +103,9 @@ gpg: Good signature from "VirtualTam <virtualtam@flibidi.net>" [ultimate][](.htm | |||
103 | ``` | 103 | ``` |
104 | 104 | ||
105 | ## Publish the GitHub release | 105 | ## Publish the GitHub release |
106 | ### Update release badges | ||
107 | Update `README.md` so version badges display and point to the newly released Shaarli version(s). | ||
108 | |||
106 | ### Create a GitHub release from a Git tag | 109 | ### Create a GitHub release from a Git tag |
107 | From the previously drafted release: | 110 | From the previously drafted release: |
108 | - edit the release notes (if needed) | 111 | - edit the release notes (if needed) |
diff --git a/doc/Security.html b/doc/Security.html index cec20590..12b46fa9 100644 --- a/doc/Security.html +++ b/doc/Security.html | |||
@@ -69,6 +69,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | 69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> |
70 | <li><a href="Firefox-share.html">Firefox share</a></li> | 70 | <li><a href="Firefox-share.html">Firefox share</a></li> |
71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | 71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> |
72 | <li><a href="REST-API.html">REST API</a></li> | ||
72 | </ul></li> | 73 | </ul></li> |
73 | <li>How To | 74 | <li>How To |
74 | <ul> | 75 | <ul> |
@@ -87,6 +88,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
87 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | 88 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> |
88 | <li><a href="Plugin-System.html">Plugin System</a></li> | 89 | <li><a href="Plugin-System.html">Plugin System</a></li> |
89 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | 90 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> |
91 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
90 | <li><a href="Security.html">Security</a></li> | 92 | <li><a href="Security.html">Security</a></li> |
91 | <li><a href="Static-analysis.html">Static analysis</a></li> | 93 | <li><a href="Static-analysis.html">Static analysis</a></li> |
92 | <li><a href="Theming.html">Theming</a></li> | 94 | <li><a href="Theming.html">Theming</a></li> |
diff --git a/doc/Server-configuration.html b/doc/Server-configuration.html index 2f1c25b5..0e6b220a 100644 --- a/doc/Server-configuration.html +++ b/doc/Server-configuration.html | |||
@@ -69,6 +69,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | 69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> |
70 | <li><a href="Firefox-share.html">Firefox share</a></li> | 70 | <li><a href="Firefox-share.html">Firefox share</a></li> |
71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | 71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> |
72 | <li><a href="REST-API.html">REST API</a></li> | ||
72 | </ul></li> | 73 | </ul></li> |
73 | <li>How To | 74 | <li>How To |
74 | <ul> | 75 | <ul> |
@@ -87,6 +88,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
87 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | 88 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> |
88 | <li><a href="Plugin-System.html">Plugin System</a></li> | 89 | <li><a href="Plugin-System.html">Plugin System</a></li> |
89 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | 90 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> |
91 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
90 | <li><a href="Security.html">Security</a></li> | 92 | <li><a href="Security.html">Security</a></li> |
91 | <li><a href="Static-analysis.html">Static analysis</a></li> | 93 | <li><a href="Static-analysis.html">Static analysis</a></li> |
92 | <li><a href="Theming.html">Theming</a></li> | 94 | <li><a href="Theming.html">Theming</a></li> |
@@ -196,6 +198,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
196 | <h3 id="htaccess">.htaccess</h3> | 198 | <h3 id="htaccess">.htaccess</h3> |
197 | <p>Shaarli use <code>.htaccess</code> Apache files to deny access to files that shouldn't be directly accessed (datastore, config, etc.). You need the directive <code>AllowOverride All</code> in your virtual host configuration for them to work.</p> | 199 | <p>Shaarli use <code>.htaccess</code> Apache files to deny access to files that shouldn't be directly accessed (datastore, config, etc.). You need the directive <code>AllowOverride All</code> in your virtual host configuration for them to work.</p> |
198 | <p><strong>Warning</strong>: If you use Apache 2.2 or lower, you need <a href="https://httpd.apache.org/docs/current/mod/mod_version.html">mod_version</a> to be installed and enabled.<a href=".html"></a></p> | 200 | <p><strong>Warning</strong>: If you use Apache 2.2 or lower, you need <a href="https://httpd.apache.org/docs/current/mod/mod_version.html">mod_version</a> to be installed and enabled.<a href=".html"></a></p> |
201 | <p>Apache module <code>mod_rewrite</code> <strong>must</strong> be enabled to use the REST API. URL rewriting rules for the Slim microframework are stated in the root <code>.htaccess</code> file.</p> | ||
199 | <h2 id="lighthttpd">LightHttpd</h2> | 202 | <h2 id="lighthttpd">LightHttpd</h2> |
200 | <h2 id="nginx">Nginx</h2> | 203 | <h2 id="nginx">Nginx</h2> |
201 | <h3 id="foreword">Foreword</h3> | 204 | <h3 id="foreword">Foreword</h3> |
@@ -296,11 +299,14 @@ http { | |||
296 | error_log /var/log/nginx/error.log; | 299 | error_log /var/log/nginx/error.log; |
297 | 300 | ||
298 | location /shaarli/ { | 301 | location /shaarli/ { |
302 | try_files $uri /shaarli/index.php$is_args$args; | ||
299 | access_log /var/log/nginx/shaarli.access.log; | 303 | access_log /var/log/nginx/shaarli.access.log; |
300 | error_log /var/log/nginx/shaarli.error.log; | 304 | error_log /var/log/nginx/shaarli.error.log; |
301 | } | 305 | } |
302 | 306 | ||
303 | location ~ (index)\.php$ { | 307 | location ~ (index)\.php$ { |
308 | try_files $uri =404; | ||
309 | fastcgi_split_path_info ^(.+\.php)(/.+)$; | ||
304 | fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock; | 310 | fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock; |
305 | fastcgi_index index.php; | 311 | fastcgi_index index.php; |
306 | include fastcgi.conf; | 312 | include fastcgi.conf; |
@@ -335,6 +341,10 @@ location ~ ~$ { | |||
335 | }</code></pre> | 341 | }</code></pre> |
336 | <pre class="nginx"><code># /etc/nginx/php.conf | 342 | <pre class="nginx"><code># /etc/nginx/php.conf |
337 | location ~ (index)\.php$ { | 343 | location ~ (index)\.php$ { |
344 | # Slim - split URL path into (script_filename, path_info) | ||
345 | try_files $uri =404; | ||
346 | fastcgi_split_path_info ^(.+\.php)(/.+)$; | ||
347 | |||
338 | # filter and proxy PHP requests to PHP-FPM | 348 | # filter and proxy PHP requests to PHP-FPM |
339 | fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock; | 349 | fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock; |
340 | fastcgi_index index.php; | 350 | fastcgi_index index.php; |
@@ -367,6 +377,9 @@ http { | |||
367 | server_name my.first.domain.org; | 377 | server_name my.first.domain.org; |
368 | 378 | ||
369 | location /shaarli/ { | 379 | location /shaarli/ { |
380 | # Slim - rewrite URLs | ||
381 | try_files $uri /shaarli/index.php$is_args$args; | ||
382 | |||
370 | access_log /var/log/nginx/shaarli.access.log; | 383 | access_log /var/log/nginx/shaarli.access.log; |
371 | error_log /var/log/nginx/shaarli.error.log; | 384 | error_log /var/log/nginx/shaarli.error.log; |
372 | } | 385 | } |
@@ -425,6 +438,9 @@ http { | |||
425 | ssl_certificate_key /home/john/ssl/localhost.key; | 438 | ssl_certificate_key /home/john/ssl/localhost.key; |
426 | 439 | ||
427 | location /shaarli/ { | 440 | location /shaarli/ { |
441 | # Slim - rewrite URLs | ||
442 | try_files $uri /index.php$is_args$args; | ||
443 | |||
428 | access_log /var/log/nginx/shaarli.access.log; | 444 | access_log /var/log/nginx/shaarli.access.log; |
429 | error_log /var/log/nginx/shaarli.error.log; | 445 | error_log /var/log/nginx/shaarli.error.log; |
430 | } | 446 | } |
diff --git a/doc/Server-configuration.md b/doc/Server-configuration.md index df10feb2..81cc1a72 100644 --- a/doc/Server-configuration.md +++ b/doc/Server-configuration.md | |||
@@ -107,6 +107,8 @@ See [Server-side TLS](https://wiki.mozilla.org/Security/Server_Side_TLS#Apache) | |||
107 | Shaarli use `.htaccess` Apache files to deny access to files that shouldn't be directly accessed (datastore, config, etc.). You need the directive `AllowOverride All` in your virtual host configuration for them to work. | 107 | Shaarli use `.htaccess` Apache files to deny access to files that shouldn't be directly accessed (datastore, config, etc.). You need the directive `AllowOverride All` in your virtual host configuration for them to work. |
108 | 108 | ||
109 | **Warning**: If you use Apache 2.2 or lower, you need [mod_version](https://httpd.apache.org/docs/current/mod/mod_version.html) to be installed and enabled.[](.html) | 109 | **Warning**: If you use Apache 2.2 or lower, you need [mod_version](https://httpd.apache.org/docs/current/mod/mod_version.html) to be installed and enabled.[](.html) |
110 | |||
111 | Apache module `mod_rewrite` **must** be enabled to use the REST API. URL rewriting rules for the Slim microframework are stated in the root `.htaccess` file. | ||
110 | 112 | ||
111 | ## LightHttpd | 113 | ## LightHttpd |
112 | 114 | ||
@@ -218,11 +220,14 @@ http { | |||
218 | error_log /var/log/nginx/error.log; | 220 | error_log /var/log/nginx/error.log; |
219 | 221 | ||
220 | location /shaarli/ { | 222 | location /shaarli/ { |
223 | try_files $uri /shaarli/index.php$is_args$args; | ||
221 | access_log /var/log/nginx/shaarli.access.log; | 224 | access_log /var/log/nginx/shaarli.access.log; |
222 | error_log /var/log/nginx/shaarli.error.log; | 225 | error_log /var/log/nginx/shaarli.error.log; |
223 | } | 226 | } |
224 | 227 | ||
225 | location ~ (index)\.php$ { | 228 | location ~ (index)\.php$ { |
229 | try_files $uri =404; | ||
230 | fastcgi_split_path_info ^(.+\.php)(/.+)$; | ||
226 | fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock; | 231 | fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock; |
227 | fastcgi_index index.php; | 232 | fastcgi_index index.php; |
228 | include fastcgi.conf; | 233 | include fastcgi.conf; |
@@ -261,6 +266,10 @@ location ~ ~$ { | |||
261 | ```nginx | 266 | ```nginx |
262 | # /etc/nginx/php.conf | 267 | # /etc/nginx/php.conf |
263 | location ~ (index)\.php$ { | 268 | location ~ (index)\.php$ { |
269 | # Slim - split URL path into (script_filename, path_info) | ||
270 | try_files $uri =404; | ||
271 | fastcgi_split_path_info ^(.+\.php)(/.+)$; | ||
272 | |||
264 | # filter and proxy PHP requests to PHP-FPM | 273 | # filter and proxy PHP requests to PHP-FPM |
265 | fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock; | 274 | fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock; |
266 | fastcgi_index index.php; | 275 | fastcgi_index index.php; |
@@ -299,6 +308,9 @@ http { | |||
299 | server_name my.first.domain.org; | 308 | server_name my.first.domain.org; |
300 | 309 | ||
301 | location /shaarli/ { | 310 | location /shaarli/ { |
311 | # Slim - rewrite URLs | ||
312 | try_files $uri /shaarli/index.php$is_args$args; | ||
313 | |||
302 | access_log /var/log/nginx/shaarli.access.log; | 314 | access_log /var/log/nginx/shaarli.access.log; |
303 | error_log /var/log/nginx/shaarli.error.log; | 315 | error_log /var/log/nginx/shaarli.error.log; |
304 | } | 316 | } |
@@ -361,6 +373,9 @@ http { | |||
361 | ssl_certificate_key /home/john/ssl/localhost.key; | 373 | ssl_certificate_key /home/john/ssl/localhost.key; |
362 | 374 | ||
363 | location /shaarli/ { | 375 | location /shaarli/ { |
376 | # Slim - rewrite URLs | ||
377 | try_files $uri /index.php$is_args$args; | ||
378 | |||
364 | access_log /var/log/nginx/shaarli.access.log; | 379 | access_log /var/log/nginx/shaarli.access.log; |
365 | error_log /var/log/nginx/shaarli.error.log; | 380 | error_log /var/log/nginx/shaarli.error.log; |
366 | } | 381 | } |
diff --git a/doc/Server-requirements.html b/doc/Server-requirements.html index 2c2545bb..79d74118 100644 --- a/doc/Server-requirements.html +++ b/doc/Server-requirements.html | |||
@@ -32,6 +32,7 @@ | |||
32 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | 32 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> |
33 | <li><a href="Firefox-share.html">Firefox share</a></li> | 33 | <li><a href="Firefox-share.html">Firefox share</a></li> |
34 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | 34 | <li><a href="RSS-feeds.html">RSS feeds</a></li> |
35 | <li><a href="REST-API.html">REST API</a></li> | ||
35 | </ul></li> | 36 | </ul></li> |
36 | <li>How To | 37 | <li>How To |
37 | <ul> | 38 | <ul> |
@@ -50,6 +51,7 @@ | |||
50 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | 51 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> |
51 | <li><a href="Plugin-System.html">Plugin System</a></li> | 52 | <li><a href="Plugin-System.html">Plugin System</a></li> |
52 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | 53 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> |
54 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
53 | <li><a href="Security.html">Security</a></li> | 55 | <li><a href="Security.html">Security</a></li> |
54 | <li><a href="Static-analysis.html">Static analysis</a></li> | 56 | <li><a href="Static-analysis.html">Static analysis</a></li> |
55 | <li><a href="Theming.html">Theming</a></li> | 57 | <li><a href="Theming.html">Theming</a></li> |
@@ -83,26 +85,31 @@ | |||
83 | </thead> | 85 | </thead> |
84 | <tbody> | 86 | <tbody> |
85 | <tr class="odd"> | 87 | <tr class="odd"> |
88 | <td style="text-align: center;">7.1</td> | ||
89 | <td style="text-align: center;">Supported (v0.9.x)</td> | ||
90 | <td style="text-align: center;">✅</td> | ||
91 | </tr> | ||
92 | <tr class="even"> | ||
86 | <td style="text-align: center;">7.0</td> | 93 | <td style="text-align: center;">7.0</td> |
87 | <td style="text-align: center;">Supported</td> | 94 | <td style="text-align: center;">Supported</td> |
88 | <td style="text-align: center;">✅</td> | 95 | <td style="text-align: center;">✅</td> |
89 | </tr> | 96 | </tr> |
90 | <tr class="even"> | 97 | <tr class="odd"> |
91 | <td style="text-align: center;">5.6</td> | 98 | <td style="text-align: center;">5.6</td> |
92 | <td style="text-align: center;">Supported</td> | 99 | <td style="text-align: center;">Supported</td> |
93 | <td style="text-align: center;">✅</td> | 100 | <td style="text-align: center;">✅</td> |
94 | </tr> | 101 | </tr> |
95 | <tr class="odd"> | 102 | <tr class="even"> |
96 | <td style="text-align: center;">5.5</td> | 103 | <td style="text-align: center;">5.5</td> |
97 | <td style="text-align: center;">EOL: 2016-07-10</td> | 104 | <td style="text-align: center;">EOL: 2016-07-10</td> |
98 | <td style="text-align: center;">✅</td> | 105 | <td style="text-align: center;">✅</td> |
99 | </tr> | 106 | </tr> |
100 | <tr class="even"> | 107 | <tr class="odd"> |
101 | <td style="text-align: center;">5.4</td> | 108 | <td style="text-align: center;">5.4</td> |
102 | <td style="text-align: center;">EOL: 2015-09-14</td> | 109 | <td style="text-align: center;">EOL: 2015-09-14</td> |
103 | <td style="text-align: center;">✅ (up to Shaarli 0.8.x)</td> | 110 | <td style="text-align: center;">✅ (up to Shaarli 0.8.x)</td> |
104 | </tr> | 111 | </tr> |
105 | <tr class="odd"> | 112 | <tr class="even"> |
106 | <td style="text-align: center;">5.3</td> | 113 | <td style="text-align: center;">5.3</td> |
107 | <td style="text-align: center;">EOL: 2014-08-14</td> | 114 | <td style="text-align: center;">EOL: 2014-08-14</td> |
108 | <td style="text-align: center;">✅ (up to Shaarli 0.8.x)</td> | 115 | <td style="text-align: center;">✅ (up to Shaarli 0.8.x)</td> |
@@ -130,6 +137,16 @@ download and install third-party PHP dependencies.</p> | |||
130 | <td style="text-align: center;">All</td> | 137 | <td style="text-align: center;">All</td> |
131 | <td>Import bookmarks from Netscape files<a href=".html"></a></td> | 138 | <td>Import bookmarks from Netscape files<a href=".html"></a></td> |
132 | </tr> | 139 | </tr> |
140 | <tr class="even"> | ||
141 | <td><a href="https://packagist.org/packages/erusev/parsedown"><code>erusev/parsedown</code></a></td> | ||
142 | <td style="text-align: center;">All</td> | ||
143 | <td>Parse MarkDown syntax for the MarkDown plugin<a href=".html"></a></td> | ||
144 | </tr> | ||
145 | <tr class="odd"> | ||
146 | <td><a href="https://packagist.org/packages/slim/slim"><code>slim/slim</code></a></td> | ||
147 | <td style="text-align: center;">All</td> | ||
148 | <td>Handle routes and middleware for the REST API<a href=".html"></a></td> | ||
149 | </tr> | ||
133 | </tbody> | 150 | </tbody> |
134 | </table> | 151 | </table> |
135 | <h3 id="extensions">Extensions</h3> | 152 | <h3 id="extensions">Extensions</h3> |
diff --git a/doc/Server-requirements.md b/doc/Server-requirements.md index 4962193e..07e70ab3 100644 --- a/doc/Server-requirements.md +++ b/doc/Server-requirements.md | |||
@@ -10,6 +10,7 @@ | |||
10 | ### Supported versions | 10 | ### Supported versions |
11 | Version | Status | Shaarli compatibility | 11 | Version | Status | Shaarli compatibility |
12 | :---:|:---:|:---: | 12 | :---:|:---:|:---: |
13 | 7.1 | Supported (v0.9.x) | :white_check_mark: | ||
13 | 7.0 | Supported | :white_check_mark: | 14 | 7.0 | Supported | :white_check_mark: |
14 | 5.6 | Supported | :white_check_mark: | 15 | 5.6 | Supported | :white_check_mark: |
15 | 5.5 | EOL: 2016-07-10 | :white_check_mark: | 16 | 5.5 | EOL: 2016-07-10 | :white_check_mark: |
@@ -26,6 +27,8 @@ download and install third-party PHP dependencies. | |||
26 | Library | Required? | Usage | 27 | Library | Required? | Usage |
27 | ---|:---:|--- | 28 | ---|:---:|--- |
28 | [`shaarli/netscape-bookmark-parser`](https://packagist.org/packages/shaarli/netscape-bookmark-parser) | All | Import bookmarks from Netscape files[](.html) | 29 | [`shaarli/netscape-bookmark-parser`](https://packagist.org/packages/shaarli/netscape-bookmark-parser) | All | Import bookmarks from Netscape files[](.html) |
30 | [`erusev/parsedown`](https://packagist.org/packages/erusev/parsedown) | All | Parse MarkDown syntax for the MarkDown plugin[](.html) | ||
31 | [`slim/slim`](https://packagist.org/packages/slim/slim) | All | Handle routes and middleware for the REST API[](.html) | ||
29 | 32 | ||
30 | ### Extensions | 33 | ### Extensions |
31 | Extension | Required? | Usage | 34 | Extension | Required? | Usage |
diff --git a/doc/Server-security.html b/doc/Server-security.html index 3551deff..4f7ff468 100644 --- a/doc/Server-security.html +++ b/doc/Server-security.html | |||
@@ -69,6 +69,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | 69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> |
70 | <li><a href="Firefox-share.html">Firefox share</a></li> | 70 | <li><a href="Firefox-share.html">Firefox share</a></li> |
71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | 71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> |
72 | <li><a href="REST-API.html">REST API</a></li> | ||
72 | </ul></li> | 73 | </ul></li> |
73 | <li>How To | 74 | <li>How To |
74 | <ul> | 75 | <ul> |
@@ -87,6 +88,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
87 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | 88 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> |
88 | <li><a href="Plugin-System.html">Plugin System</a></li> | 89 | <li><a href="Plugin-System.html">Plugin System</a></li> |
89 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | 90 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> |
91 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
90 | <li><a href="Security.html">Security</a></li> | 92 | <li><a href="Security.html">Security</a></li> |
91 | <li><a href="Static-analysis.html">Static analysis</a></li> | 93 | <li><a href="Static-analysis.html">Static analysis</a></li> |
92 | <li><a href="Theming.html">Theming</a></li> | 94 | <li><a href="Theming.html">Theming</a></li> |
diff --git a/doc/Shaarli-configuration.html b/doc/Shaarli-configuration.html index 6d717c65..c696c97c 100644 --- a/doc/Shaarli-configuration.html +++ b/doc/Shaarli-configuration.html | |||
@@ -69,6 +69,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | 69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> |
70 | <li><a href="Firefox-share.html">Firefox share</a></li> | 70 | <li><a href="Firefox-share.html">Firefox share</a></li> |
71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | 71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> |
72 | <li><a href="REST-API.html">REST API</a></li> | ||
72 | </ul></li> | 73 | </ul></li> |
73 | <li>How To | 74 | <li>How To |
74 | <ul> | 75 | <ul> |
@@ -87,6 +88,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
87 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | 88 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> |
88 | <li><a href="Plugin-System.html">Plugin System</a></li> | 89 | <li><a href="Plugin-System.html">Plugin System</a></li> |
89 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | 90 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> |
91 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
90 | <li><a href="Security.html">Security</a></li> | 92 | <li><a href="Security.html">Security</a></li> |
91 | <li><a href="Static-analysis.html">Static analysis</a></li> | 93 | <li><a href="Static-analysis.html">Static analysis</a></li> |
92 | <li><a href="Theming.html">Theming</a></li> | 94 | <li><a href="Theming.html">Theming</a></li> |
@@ -169,6 +171,7 @@ It might be useful if your IP adress often changes.<br /> | |||
169 | <h3 id="resources">Resources</h3> | 171 | <h3 id="resources">Resources</h3> |
170 | <p><strong>data_dir</strong>: Data directory.<br /> | 172 | <p><strong>data_dir</strong>: Data directory.<br /> |
171 | <strong>datastore</strong>: Shaarli's links database file path.<br /> | 173 | <strong>datastore</strong>: Shaarli's links database file path.<br /> |
174 | <strong>history</strong>: Shaarli's operation history file path.<br /> | ||
172 | <strong>updates</strong>: File path for the ran updates file.<br /> | 175 | <strong>updates</strong>: File path for the ran updates file.<br /> |
173 | <strong>log</strong>: Log file path.<br /> | 176 | <strong>log</strong>: Log file path.<br /> |
174 | <strong>update_check</strong>: Last update check file path.<br /> | 177 | <strong>update_check</strong>: Last update check file path.<br /> |
diff --git a/doc/Shaarli-configuration.md b/doc/Shaarli-configuration.md index 4a783c0e..25647cb7 100644 --- a/doc/Shaarli-configuration.md +++ b/doc/Shaarli-configuration.md | |||
@@ -70,6 +70,7 @@ It might be useful if your IP adress often changes. | |||
70 | 70 | ||
71 | **data_dir**: Data directory. | 71 | **data_dir**: Data directory. |
72 | **datastore**: Shaarli's links database file path. | 72 | **datastore**: Shaarli's links database file path. |
73 | **history**: Shaarli's operation history file path. | ||
73 | **updates**: File path for the ran updates file. | 74 | **updates**: File path for the ran updates file. |
74 | **log**: Log file path. | 75 | **log**: Log file path. |
75 | **update_check**: Last update check file path. | 76 | **update_check**: Last update check file path. |
diff --git a/doc/Sharing-button.html b/doc/Sharing-button.html index 93710efe..f3682f8c 100644 --- a/doc/Sharing-button.html +++ b/doc/Sharing-button.html | |||
@@ -32,6 +32,7 @@ | |||
32 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | 32 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> |
33 | <li><a href="Firefox-share.html">Firefox share</a></li> | 33 | <li><a href="Firefox-share.html">Firefox share</a></li> |
34 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | 34 | <li><a href="RSS-feeds.html">RSS feeds</a></li> |
35 | <li><a href="REST-API.html">REST API</a></li> | ||
35 | </ul></li> | 36 | </ul></li> |
36 | <li>How To | 37 | <li>How To |
37 | <ul> | 38 | <ul> |
@@ -50,6 +51,7 @@ | |||
50 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | 51 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> |
51 | <li><a href="Plugin-System.html">Plugin System</a></li> | 52 | <li><a href="Plugin-System.html">Plugin System</a></li> |
52 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | 53 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> |
54 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
53 | <li><a href="Security.html">Security</a></li> | 55 | <li><a href="Security.html">Security</a></li> |
54 | <li><a href="Static-analysis.html">Static analysis</a></li> | 56 | <li><a href="Static-analysis.html">Static analysis</a></li> |
55 | <li><a href="Theming.html">Theming</a></li> | 57 | <li><a href="Theming.html">Theming</a></li> |
diff --git a/doc/Static-analysis.html b/doc/Static-analysis.html index d964e917..a95d195e 100644 --- a/doc/Static-analysis.html +++ b/doc/Static-analysis.html | |||
@@ -32,6 +32,7 @@ | |||
32 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | 32 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> |
33 | <li><a href="Firefox-share.html">Firefox share</a></li> | 33 | <li><a href="Firefox-share.html">Firefox share</a></li> |
34 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | 34 | <li><a href="RSS-feeds.html">RSS feeds</a></li> |
35 | <li><a href="REST-API.html">REST API</a></li> | ||
35 | </ul></li> | 36 | </ul></li> |
36 | <li>How To | 37 | <li>How To |
37 | <ul> | 38 | <ul> |
@@ -50,6 +51,7 @@ | |||
50 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | 51 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> |
51 | <li><a href="Plugin-System.html">Plugin System</a></li> | 52 | <li><a href="Plugin-System.html">Plugin System</a></li> |
52 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | 53 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> |
54 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
53 | <li><a href="Security.html">Security</a></li> | 55 | <li><a href="Security.html">Security</a></li> |
54 | <li><a href="Static-analysis.html">Static analysis</a></li> | 56 | <li><a href="Static-analysis.html">Static analysis</a></li> |
55 | <li><a href="Theming.html">Theming</a></li> | 57 | <li><a href="Theming.html">Theming</a></li> |
diff --git a/doc/Theming.html b/doc/Theming.html index 7cbf7aef..6b5dac35 100644 --- a/doc/Theming.html +++ b/doc/Theming.html | |||
@@ -69,6 +69,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | 69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> |
70 | <li><a href="Firefox-share.html">Firefox share</a></li> | 70 | <li><a href="Firefox-share.html">Firefox share</a></li> |
71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | 71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> |
72 | <li><a href="REST-API.html">REST API</a></li> | ||
72 | </ul></li> | 73 | </ul></li> |
73 | <li>How To | 74 | <li>How To |
74 | <ul> | 75 | <ul> |
@@ -87,6 +88,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
87 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | 88 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> |
88 | <li><a href="Plugin-System.html">Plugin System</a></li> | 89 | <li><a href="Plugin-System.html">Plugin System</a></li> |
89 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | 90 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> |
91 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
90 | <li><a href="Security.html">Security</a></li> | 92 | <li><a href="Security.html">Security</a></li> |
91 | <li><a href="Static-analysis.html">Static analysis</a></li> | 93 | <li><a href="Static-analysis.html">Static analysis</a></li> |
92 | <li><a href="Theming.html">Theming</a></li> | 94 | <li><a href="Theming.html">Theming</a></li> |
@@ -100,42 +102,56 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
100 | </ul> | 102 | </ul> |
101 | </div> | 103 | </div> |
102 | <h1 id="theming">Theming</h1> | 104 | <h1 id="theming">Theming</h1> |
103 | <h2 id="user-css">User CSS</h2> | 105 | <h2 id="foreword">Foreword</h2> |
106 | <p>There are two ways of customizing how Shaarli looks:</p> | ||
107 | <ol> | ||
108 | <li>by using a custom CSS to override Shaarli's CSS</li> | ||
109 | <li>by using a full theme that provides its own RainTPL templates, CSS and Javascript resources</li> | ||
110 | </ol> | ||
111 | <h2 id="custom-css">Custom CSS</h2> | ||
112 | <p>Shaarli's appearance can be modified by adding CSS rules to:</p> | ||
104 | <ul> | 113 | <ul> |
105 | <li>Shaarli's apparence can be modified by editing CSS rules in <code>inc/user.css</code>. This file allows to override rules defined in the main <code>inc/shaarli.css</code> (only add changed rules), or define a whole new theme.</li> | 114 | <li>Shaarli < <code>v0.9.0</code>: <code>inc/user.css</code></li> |
106 | <li>Do not edit <code>inc/shaarli.css</code>! Your changes would be overriden when updating Shaarli.</li> | 115 | <li>Shaarli >= <code>v0.9.0</code>: <code>data/user.css</code></li> |
107 | <li>Some themes are available at <a href="https://github.com/shaarli/shaarli-themes" class="uri">https://github.com/shaarli/shaarli-themes</a>.</li> | ||
108 | </ul> | 116 | </ul> |
109 | <p>See also:</p> | 117 | <p>This file allows overriding rules defined in the template CSS files (only add changed rules), or define a whole new theme.</p> |
110 | <ul> | 118 | <p><strong>Note</strong>: Do not edit <code>tpl/default/css/shaarli.css</code>! Your changes would be overridden when updating Shaarli.</p> |
111 | <li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> | 119 | <p>See also <a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></p> |
112 | </ul> | 120 | <h2 id="themes">Themes</h2> |
113 | <h2 id="raintpl-template">RainTPL template</h2> | ||
114 | <p><em>WARNING - This feature is currently being worked on and will be improved in the next releases. Experimental.</em></p> | 121 | <p><em>WARNING - This feature is currently being worked on and will be improved in the next releases. Experimental.</em></p> |
122 | <p>Installation:</p> | ||
115 | <ul> | 123 | <ul> |
116 | <li>Find the template you'd like to install (see the list of <a href="available-templates%7CTheming#community-themes--templates.html">available templates|Theming#community-themes--templates</a>)</li> | 124 | <li>find a theme you'd like to install</li> |
117 | <li>Find it's git clone URL or download the zip archive for the template.</li> | 125 | <li>copy or clone the theme folder under <code>tpl/<a_sweet_theme></code></li> |
118 | <li>In your Shaarli <code>tpl/</code> directory, run <code>git clone https://url/of/my-template/</code> or unpack the zip archive. | 126 | <li>enable the theme: |
119 | <ul> | 127 | <ul> |
120 | <li>There should now be a <code>my-template/</code> directory under the <code>tpl/</code> dir, containing directly all the template files.</li> | 128 | <li>Shaarli < <code>v0.9.0</code>: edit <code>data/config.json.php</code> and set the value of <code>raintpl_tpl</code> to the new theme name:<br /> |
129 | <code>"raintpl_tpl": "tpl\/my-template\/"</code></li> | ||
130 | <li>Shaarli >= <code>v0.9.0</code>: select the theme through the <em>Tools</em> page</li> | ||
121 | </ul></li> | 131 | </ul></li> |
122 | <li><p>Edit <code>data/config.json.php</code> to have Shaarli use this template, in <code>"resource"</code> e.g.</p> | ||
123 | <div class="sourceCode"><pre class="sourceCode json"><code class="sourceCode json"><span class="er">"raintpl_tpl":</span> <span class="er">"tpl\/my-template\/",</span></code></pre></div></li> | ||
124 | </ul> | 132 | </ul> |
125 | <h2 id="community-themes-templates">Community themes & templates</h2> | 133 | <h2 id="community-css-themes">Community CSS & themes</h2> |
134 | <h3 id="custom-css-1">Custom CSS</h3> | ||
135 | <ul> | ||
136 | <li><a href="https://github.com/mrjovanovic/serious-theme-shaarli">mrjovanovic/serious-theme-shaarli</a> - A serious theme for Shaarli<a href=".html"></a></li> | ||
137 | <li><a href="https://github.com/shaarli/shaarli-themes">shaarli/shaarli-themes</a><a href=".html"></a></li> | ||
138 | </ul> | ||
139 | <h3 id="themes-1">Themes</h3> | ||
126 | <ul> | 140 | <ul> |
127 | <li><a href="https://github.com/AkibaTech/Shaarli---SuperHero-Theme">AkibaTech/Shaarli Superhero Theme</a> - A template/theme for Shaarli<a href=".html"></a></li> | 141 | <li><a href="https://github.com/AkibaTech/Shaarli---SuperHero-Theme">AkibaTech/Shaarli Superhero Theme</a> - A template/theme for Shaarli<a href=".html"></a></li> |
128 | <li><a href="https://github.com/alexisju/albinomouse-template">alexisju/albinomouse-template</a> - A full template for Shaarli<a href=".html"></a></li> | 142 | <li><a href="https://github.com/alexisju/albinomouse-template">alexisju/albinomouse-template</a> - A full template for Shaarli<a href=".html"></a></li> |
129 | <li><a href="https://github.com/ArthurHoaro/shaarli-launch">ArthurHoaro/shaarli-launch</a> - Customizable Shaarli theme.<a href=".html"></a></li> | 143 | <li><a href="https://github.com/ArthurHoaro/shaarli-launch">ArthurHoaro/shaarli-launch</a> - Customizable Shaarli theme<a href=".html"></a></li> |
130 | <li><a href="https://github.com/dhoko/ShaarliTemplate">dhoko/ShaarliTemplate</a> - A template/theme for Shaarli<a href=".html"></a></li> | 144 | <li><a href="https://github.com/dhoko/ShaarliTemplate">dhoko/ShaarliTemplate</a> - A template/theme for Shaarli<a href=".html"></a></li> |
131 | <li><a href="https://github.com/kalvn/shaarli-blocks">kalvn/shaarli-blocks</a> - A template/theme for Shaarli<a href=".html"></a></li> | 145 | <li><a href="https://github.com/kalvn/shaarli-blocks">kalvn/shaarli-blocks</a> - A template/theme for Shaarli<a href=".html"></a></li> |
132 | <li><a href="https://github.com/kalvn/Shaarli-Material">kalvn/Shaarli-Material</a> - A theme (template) based on Google's Material Design for Shaarli, the superfast delicious clone.<a href=".html"></a></li> | 146 | <li><a href="https://github.com/kalvn/Shaarli-Material">kalvn/Shaarli-Material</a> - A theme (template) based on Google's Material Design for Shaarli, the superfast delicious clone<a href=".html"></a></li> |
133 | <li><a href="https://github.com/ManufacturaInd/shaarli-2004licious-theme">ManufacturaInd/shaarli-2004licious-theme</a> - A template/theme as a humble homage to the early looks of the del.icio.us site.<a href=".html"></a></li> | 147 | <li><a href="https://github.com/ManufacturaInd/shaarli-2004licious-theme">ManufacturaInd/shaarli-2004licious-theme</a> - A template/theme as a humble homage to the early looks of the del.icio.us site<a href=".html"></a></li> |
148 | </ul> | ||
149 | <h3 id="shaarli-forks">Shaarli forks</h3> | ||
150 | <ul> | ||
134 | <li><a href="https://github.com/misterair/limonade">misterair/Limonade</a> - A fork of (legacy) Shaarli with a new template<a href=".html"></a></li> | 151 | <li><a href="https://github.com/misterair/limonade">misterair/Limonade</a> - A fork of (legacy) Shaarli with a new template<a href=".html"></a></li> |
135 | <li><a href="https://github.com/mrjovanovic/serious-theme-shaarli">mrjovanovic/serious-theme-shaarli</a> - A serious theme for SHaarli.<a href=".html"></a></li> | ||
136 | <li><a href="https://github.com/vivienhaese/shaarlitheme">vivienhaese/shaarlitheme</a> - A Shaarli fork meant to be run in an openshift instance<a href=".html"></a></li> | 152 | <li><a href="https://github.com/vivienhaese/shaarlitheme">vivienhaese/shaarlitheme</a> - A Shaarli fork meant to be run in an openshift instance<a href=".html"></a></li> |
137 | </ul> | 153 | </ul> |
138 | <h3 id="example-installation-albinomouse-template">Example installation: AlbinoMouse template</h3> | 154 | <h2 id="example-installation-albinomouse-theme">Example installation: AlbinoMouse theme</h2> |
139 | <p>With the following configuration:</p> | 155 | <p>With the following configuration:</p> |
140 | <ul> | 156 | <ul> |
141 | <li>Apache 2 / PHP 5.6</li> | 157 | <li>Apache 2 / PHP 5.6</li> |
diff --git a/doc/Theming.md b/doc/Theming.md index a21899c2..23877e5d 100644 --- a/doc/Theming.md +++ b/doc/Theming.md | |||
@@ -1,39 +1,51 @@ | |||
1 | #Theming | 1 | #Theming |
2 | ## User CSS | 2 | ## Foreword |
3 | There are two ways of customizing how Shaarli looks: | ||
3 | 4 | ||
4 | - Shaarli's apparence can be modified by editing CSS rules in `inc/user.css`. This file allows to override rules defined in the main `inc/shaarli.css` (only add changed rules), or define a whole new theme. | 5 | 1. by using a custom CSS to override Shaarli's CSS |
5 | - Do not edit `inc/shaarli.css`! Your changes would be overriden when updating Shaarli. | 6 | 2. by using a full theme that provides its own RainTPL templates, CSS and Javascript resources |
6 | - Some themes are available at https://github.com/shaarli/shaarli-themes. | ||
7 | 7 | ||
8 | See also: | 8 | ## Custom CSS |
9 | - [Download CSS styles from an OPML list](Download-CSS-styles-from-an-OPML-list.html) | 9 | Shaarli's appearance can be modified by adding CSS rules to: |
10 | - Shaarli < `v0.9.0`: `inc/user.css` | ||
11 | - Shaarli >= `v0.9.0`: `data/user.css` | ||
10 | 12 | ||
11 | ## RainTPL template | 13 | This file allows overriding rules defined in the template CSS files (only add changed rules), or define a whole new theme. |
12 | 14 | ||
15 | **Note**: Do not edit `tpl/default/css/shaarli.css`! Your changes would be overridden when updating Shaarli. | ||
16 | |||
17 | See also [Download CSS styles from an OPML list](Download-CSS-styles-from-an-OPML-list.html) | ||
18 | |||
19 | ## Themes | ||
13 | _WARNING - This feature is currently being worked on and will be improved in the next releases. Experimental._ | 20 | _WARNING - This feature is currently being worked on and will be improved in the next releases. Experimental._ |
14 | 21 | ||
15 | - Find the template you'd like to install (see the list of [available templates|Theming#community-themes--templates](available-templates|Theming#community-themes--templates.html)) | 22 | Installation: |
16 | - Find it's git clone URL or download the zip archive for the template. | 23 | - find a theme you'd like to install |
17 | - In your Shaarli `tpl/` directory, run `git clone https://url/of/my-template/` or unpack the zip archive. | 24 | - copy or clone the theme folder under `tpl/<a_sweet_theme>` |
18 | - There should now be a `my-template/` directory under the `tpl/` dir, containing directly all the template files. | 25 | - enable the theme: |
19 | - Edit `data/config.json.php` to have Shaarli use this template, in `"resource"` e.g. | 26 | - Shaarli < `v0.9.0`: edit `data/config.json.php` and set the value of `raintpl_tpl` to the new theme name: |
20 | ```json | 27 | `"raintpl_tpl": "tpl\/my-template\/"` |
21 | "raintpl_tpl": "tpl\/my-template\/", | 28 | - Shaarli >= `v0.9.0`: select the theme through the _Tools_ page |
22 | ``` | ||
23 | 29 | ||
24 | ## Community themes & templates | 30 | ## Community CSS & themes |
31 | ### Custom CSS | ||
32 | - [mrjovanovic/serious-theme-shaarli](https://github.com/mrjovanovic/serious-theme-shaarli) - A serious theme for Shaarli[](.html) | ||
33 | - [shaarli/shaarli-themes](https://github.com/shaarli/shaarli-themes)[](.html) | ||
34 | |||
35 | ### Themes | ||
25 | - [AkibaTech/Shaarli Superhero Theme](https://github.com/AkibaTech/Shaarli---SuperHero-Theme) - A template/theme for Shaarli[](.html) | 36 | - [AkibaTech/Shaarli Superhero Theme](https://github.com/AkibaTech/Shaarli---SuperHero-Theme) - A template/theme for Shaarli[](.html) |
26 | - [alexisju/albinomouse-template](https://github.com/alexisju/albinomouse-template) - A full template for Shaarli[](.html) | 37 | - [alexisju/albinomouse-template](https://github.com/alexisju/albinomouse-template) - A full template for Shaarli[](.html) |
27 | - [ArthurHoaro/shaarli-launch](https://github.com/ArthurHoaro/shaarli-launch) - Customizable Shaarli theme.[](.html) | 38 | - [ArthurHoaro/shaarli-launch](https://github.com/ArthurHoaro/shaarli-launch) - Customizable Shaarli theme[](.html) |
28 | - [dhoko/ShaarliTemplate](https://github.com/dhoko/ShaarliTemplate) - A template/theme for Shaarli[](.html) | 39 | - [dhoko/ShaarliTemplate](https://github.com/dhoko/ShaarliTemplate) - A template/theme for Shaarli[](.html) |
29 | - [kalvn/shaarli-blocks](https://github.com/kalvn/shaarli-blocks) - A template/theme for Shaarli[](.html) | 40 | - [kalvn/shaarli-blocks](https://github.com/kalvn/shaarli-blocks) - A template/theme for Shaarli[](.html) |
30 | - [kalvn/Shaarli-Material](https://github.com/kalvn/Shaarli-Material) - A theme (template) based on Google's Material Design for Shaarli, the superfast delicious clone.[](.html) | 41 | - [kalvn/Shaarli-Material](https://github.com/kalvn/Shaarli-Material) - A theme (template) based on Google's Material Design for Shaarli, the superfast delicious clone[](.html) |
31 | - [ManufacturaInd/shaarli-2004licious-theme](https://github.com/ManufacturaInd/shaarli-2004licious-theme) - A template/theme as a humble homage to the early looks of the del.icio.us site.[](.html) | 42 | - [ManufacturaInd/shaarli-2004licious-theme](https://github.com/ManufacturaInd/shaarli-2004licious-theme) - A template/theme as a humble homage to the early looks of the del.icio.us site[](.html) |
43 | |||
44 | ### Shaarli forks | ||
32 | - [misterair/Limonade](https://github.com/misterair/limonade) - A fork of (legacy) Shaarli with a new template[](.html) | 45 | - [misterair/Limonade](https://github.com/misterair/limonade) - A fork of (legacy) Shaarli with a new template[](.html) |
33 | - [mrjovanovic/serious-theme-shaarli](https://github.com/mrjovanovic/serious-theme-shaarli) - A serious theme for SHaarli.[](.html) | ||
34 | - [vivienhaese/shaarlitheme](https://github.com/vivienhaese/shaarlitheme) - A Shaarli fork meant to be run in an openshift instance[](.html) | 46 | - [vivienhaese/shaarlitheme](https://github.com/vivienhaese/shaarlitheme) - A Shaarli fork meant to be run in an openshift instance[](.html) |
35 | 47 | ||
36 | ### Example installation: AlbinoMouse template | 48 | ## Example installation: AlbinoMouse theme |
37 | With the following configuration: | 49 | With the following configuration: |
38 | - Apache 2 / PHP 5.6 | 50 | - Apache 2 / PHP 5.6 |
39 | - user sites are enabled, e.g. `/home/user/public_html/somedir` is served as `http://localhost/~user/somedir` | 51 | - user sites are enabled, e.g. `/home/user/public_html/somedir` is served as `http://localhost/~user/somedir` |
diff --git a/doc/Troubleshooting.html b/doc/Troubleshooting.html index ed1c6f09..f43e6ed3 100644 --- a/doc/Troubleshooting.html +++ b/doc/Troubleshooting.html | |||
@@ -69,6 +69,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | 69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> |
70 | <li><a href="Firefox-share.html">Firefox share</a></li> | 70 | <li><a href="Firefox-share.html">Firefox share</a></li> |
71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | 71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> |
72 | <li><a href="REST-API.html">REST API</a></li> | ||
72 | </ul></li> | 73 | </ul></li> |
73 | <li>How To | 74 | <li>How To |
74 | <ul> | 75 | <ul> |
@@ -87,6 +88,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
87 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | 88 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> |
88 | <li><a href="Plugin-System.html">Plugin System</a></li> | 89 | <li><a href="Plugin-System.html">Plugin System</a></li> |
89 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | 90 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> |
91 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
90 | <li><a href="Security.html">Security</a></li> | 92 | <li><a href="Security.html">Security</a></li> |
91 | <li><a href="Static-analysis.html">Static analysis</a></li> | 93 | <li><a href="Static-analysis.html">Static analysis</a></li> |
92 | <li><a href="Theming.html">Theming</a></li> | 94 | <li><a href="Theming.html">Theming</a></li> |
@@ -172,7 +174,7 @@ Search for <code>failed</code> in this file to look for unauthorized login attem | |||
172 | <li>If you have the error <code>Warning: file_get_contents() [function.file-get-contents]: URL file-access is disabled in the server configuration in /…/index.php on line xxx</code>, it means that your host has disabled the ability to fetch a file by HTTP in the php config (Typically in 1and1 hosting). Bad host. Change host. Or comment the following lines:<a href=".html"></a></li> | 174 | <li>If you have the error <code>Warning: file_get_contents() [function.file-get-contents]: URL file-access is disabled in the server configuration in /…/index.php on line xxx</code>, it means that your host has disabled the ability to fetch a file by HTTP in the php config (Typically in 1and1 hosting). Bad host. Change host. Or comment the following lines:<a href=".html"></a></li> |
173 | </ul> | 175 | </ul> |
174 | <div class="sourceCode"><pre class="sourceCode php"><code class="sourceCode php"><span class="co">//list($status,$headers,$data) = getHTTP($url,4); // Short timeout to keep the application responsive.</span> | 176 | <div class="sourceCode"><pre class="sourceCode php"><code class="sourceCode php"><span class="co">//list($status,$headers,$data) = getHTTP($url,4); // Short timeout to keep the application responsive.</span> |
175 | <span class="co">// FIXME: Decode charset according to charset specified in either 1) HTTP response headers or 2) <head> in html </span> | 177 | <span class="co">// </span><span class="al">FIXME</span><span class="co">: Decode charset according to charset specified in either 1) HTTP response headers or 2) <head> in html </span> |
176 | <span class="co">//if (strpos($status,'200 OK')) $title=html_extract_title($data);</span></code></pre></div> | 178 | <span class="co">//if (strpos($status,'200 OK')) $title=html_extract_title($data);</span></code></pre></div> |
177 | <ul> | 179 | <ul> |
178 | <li>On hosts which forbid outgoing HTTP requests (such as free.fr), some thumbnails will not work.</li> | 180 | <li>On hosts which forbid outgoing HTTP requests (such as free.fr), some thumbnails will not work.</li> |
diff --git a/doc/Unit-tests.html b/doc/Unit-tests.html index 266fd33a..09611463 100644 --- a/doc/Unit-tests.html +++ b/doc/Unit-tests.html | |||
@@ -69,6 +69,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | 69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> |
70 | <li><a href="Firefox-share.html">Firefox share</a></li> | 70 | <li><a href="Firefox-share.html">Firefox share</a></li> |
71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | 71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> |
72 | <li><a href="REST-API.html">REST API</a></li> | ||
72 | </ul></li> | 73 | </ul></li> |
73 | <li>How To | 74 | <li>How To |
74 | <ul> | 75 | <ul> |
@@ -87,6 +88,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
87 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | 88 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> |
88 | <li><a href="Plugin-System.html">Plugin System</a></li> | 89 | <li><a href="Plugin-System.html">Plugin System</a></li> |
89 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | 90 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> |
91 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
90 | <li><a href="Security.html">Security</a></li> | 92 | <li><a href="Security.html">Security</a></li> |
91 | <li><a href="Static-analysis.html">Static analysis</a></li> | 93 | <li><a href="Static-analysis.html">Static analysis</a></li> |
92 | <li><a href="Theming.html">Theming</a></li> | 94 | <li><a href="Theming.html">Theming</a></li> |
@@ -208,5 +210,17 @@ DBTest.php on line 79 and defined | |||
208 | <li>a detailed HTML report with metrics for tested code</li> | 210 | <li>a detailed HTML report with metrics for tested code</li> |
209 | <li>to open it in a web browser: <code>firefox coverage/index.html &</code></li> | 211 | <li>to open it in a web browser: <code>firefox coverage/index.html &</code></li> |
210 | </ul> | 212 | </ul> |
213 | <h3 id="executing-specific-tests">Executing specific tests</h3> | ||
214 | <p>Add a <a href="https://phpunit.de/manual/current/en/appendixes.annotations.html#appendixes.annotations.group"><code>@group</code></a> annotation in a test class or method comment:<a href=".html"></a></p> | ||
215 | <div class="sourceCode"><pre class="sourceCode php"><code class="sourceCode php"><span class="co">/**</span> | ||
216 | <span class="co"> * Netscape bookmark import</span> | ||
217 | <span class="co"> * </span><span class="an">@group</span><span class="co"> WIP</span> | ||
218 | <span class="co"> */</span> | ||
219 | <span class="kw">class</span> BookmarkImportTest <span class="kw">extends</span> PHPUnit_Framework_TestCase | ||
220 | { | ||
221 | <span class="ot">[</span><span class="st">...</span><span class="ot">][](</span>.html<span class="ot">)</span> | ||
222 | }</code></pre></div> | ||
223 | <p>To run all tests annotated with <code>@group WIP</code>:</p> | ||
224 | <div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="ex">vendor/bin/phpunit</span> --group WIP tests/</code></pre></div> | ||
211 | </body> | 225 | </body> |
212 | </html> | 226 | </html> |
diff --git a/doc/Unit-tests.md b/doc/Unit-tests.md index f2888780..0942ad38 100644 --- a/doc/Unit-tests.md +++ b/doc/Unit-tests.md | |||
@@ -126,3 +126,22 @@ If Xdebug has been installed and activated, two coverage reports will be generat | |||
126 | * a summary in the console | 126 | * a summary in the console |
127 | * a detailed HTML report with metrics for tested code | 127 | * a detailed HTML report with metrics for tested code |
128 | * to open it in a web browser: `firefox coverage/index.html &` | 128 | * to open it in a web browser: `firefox coverage/index.html &` |
129 | |||
130 | ### Executing specific tests | ||
131 | Add a [`@group`](https://phpunit.de/manual/current/en/appendixes.annotations.html#appendixes.annotations.group) annotation in a test class or method comment:[](.html) | ||
132 | |||
133 | ```php | ||
134 | /** | ||
135 | * Netscape bookmark import | ||
136 | * @group WIP | ||
137 | */ | ||
138 | class BookmarkImportTest extends PHPUnit_Framework_TestCase | ||
139 | { | ||
140 | [...][](.html) | ||
141 | } | ||
142 | ``` | ||
143 | |||
144 | To run all tests annotated with `@group WIP`: | ||
145 | ```bash | ||
146 | $ vendor/bin/phpunit --group WIP tests/ | ||
147 | ``` | ||
diff --git a/doc/Upgrade-and-migration.html b/doc/Upgrade-and-migration.html index a5b041d5..667215ab 100644 --- a/doc/Upgrade-and-migration.html +++ b/doc/Upgrade-and-migration.html | |||
@@ -69,6 +69,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | 69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> |
70 | <li><a href="Firefox-share.html">Firefox share</a></li> | 70 | <li><a href="Firefox-share.html">Firefox share</a></li> |
71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | 71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> |
72 | <li><a href="REST-API.html">REST API</a></li> | ||
72 | </ul></li> | 73 | </ul></li> |
73 | <li>How To | 74 | <li>How To |
74 | <ul> | 75 | <ul> |
@@ -87,6 +88,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
87 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | 88 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> |
88 | <li><a href="Plugin-System.html">Plugin System</a></li> | 89 | <li><a href="Plugin-System.html">Plugin System</a></li> |
89 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | 90 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> |
91 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
90 | <li><a href="Security.html">Security</a></li> | 92 | <li><a href="Security.html">Security</a></li> |
91 | <li><a href="Static-analysis.html">Static analysis</a></li> | 93 | <li><a href="Static-analysis.html">Static analysis</a></li> |
92 | <li><a href="Theming.html">Theming</a></li> | 94 | <li><a href="Theming.html">Theming</a></li> |
@@ -101,12 +103,16 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
101 | </div> | 103 | </div> |
102 | <h1 id="upgrade-and-migration">Upgrade and migration</h1> | 104 | <h1 id="upgrade-and-migration">Upgrade and migration</h1> |
103 | <h2 id="preparation">Preparation</h2> | 105 | <h2 id="preparation">Preparation</h2> |
106 | <h3 id="note-your-current-version">Note your current version</h3> | ||
107 | <p>If anything goes wrong, it's important for us to know which version you're upgrading from.<br /> | ||
108 | The current version is present in the <code>version.php</code> file.</p> | ||
104 | <h3 id="backup-your-data">Backup your data</h3> | 109 | <h3 id="backup-your-data">Backup your data</h3> |
105 | <p>Shaarli stores all user data under the <code>data</code> directory:</p> | 110 | <p>Shaarli stores all user data under the <code>data</code> directory:</p> |
106 | <ul> | 111 | <ul> |
107 | <li><code>data/config.php</code> - main configuration file</li> | 112 | <li><code>data/config.php</code> - main configuration file</li> |
108 | <li><code>data/datastore.php</code> - bookmarked links</li> | 113 | <li><code>data/datastore.php</code> - bookmarked links</li> |
109 | <li><code>data/ipbans.php</code> - banned IP addresses</li> | 114 | <li><code>data/ipbans.php</code> - banned IP addresses</li> |
115 | <li><code>data/updates.txt</code> - contains all automatic update to the configuration and datastore files already run</li> | ||
110 | </ul> | 116 | </ul> |
111 | <p>See <a href="Shaarli-configuration.html">Shaarli configuration</a> for more information about Shaarli resources.</p> | 117 | <p>See <a href="Shaarli-configuration.html">Shaarli configuration</a> for more information about Shaarli resources.</p> |
112 | <p>It is recommended to backup this repository <em>before</em> starting updating/upgrading Shaarli:</p> | 118 | <p>It is recommended to backup this repository <em>before</em> starting updating/upgrading Shaarli:</p> |
@@ -125,15 +131,11 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf | |||
125 | </ul></li> | 131 | </ul></li> |
126 | <li>check or restore the <code>data</code> directory</li> | 132 | <li>check or restore the <code>data</code> directory</li> |
127 | </ul> | 133 | </ul> |
128 | <h2 id="upgrading-from-release-archives">Upgrading from release archives</h2> | 134 | <h2 id="recommended-upgrading-from-release-archives">Recommended : Upgrading from release archives</h2> |
129 | <p>All tagged revisions can be downloaded as tarballs or ZIP archives from the <a href="https://github.com/shaarli/Shaarli/releases">releases</a> page.<a href=".html"></a></p> | 135 | <p>All tagged revisions can be downloaded as tarballs or ZIP archives from the <a href="https://github.com/shaarli/Shaarli/releases">releases</a> page.<a href=".html"></a></p> |
130 | <p>We <em>recommend</em> using the releases from the <code>stable</code> branch, which are available as:</p> | 136 | <p>We recommend that you use the latest release tarball with the <code>-full</code> suffix. It contains the dependencies, please read <a href="Download-and-installation.html">Download and installation</a> for <code>git</code> complete instructions.</p> |
131 | <ul> | 137 | <p>Once downloaded, extract the archive locally and update your remote installation (e.g. via FTP) -be sure you keep the content of the <code>data</code> directory!</p> |
132 | <li>gzipped tarball - <a href="https://github.com/shaarli/Shaarli/archive/stable.tar.gz" class="uri">https://github.com/shaarli/Shaarli/archive/stable.tar.gz</a></li> | 138 | <p>After upgrading, access your fresh Shaarli installation from a web browser; the configuration and data store will then be automatically updated, and new settings added to <code>data/config.json.php</code> (see <a href="Shaarli-configuration.html">Shaarli configuration</a> for more details).</p> |
133 | <li>ZIP archive - <a href="https://github.com/shaarli/Shaarli/archive/stable.zip" class="uri">https://github.com/shaarli/Shaarli/archive/stable.zip</a></li> | ||
134 | </ul> | ||
135 | <p>Once downloaded, extract the archive locally and update your remote installation (e.g. via FTP) -be sure you keep the contents of the <code>data</code> directory!</p> | ||
136 | <p>After upgrading, access your fresh Shaarli installation from a web browser; the configuration will then be automatically updated, and new settings added to <code>data/config.php</code> (see <a href="Shaarli-configuration.html">Shaarli configuration</a> for more details).</p> | ||
137 | <h2 id="upgrading-with-git">Upgrading with Git</h2> | 139 | <h2 id="upgrading-with-git">Upgrading with Git</h2> |
138 | <h3 id="updating-a-community-shaarli">Updating a community Shaarli</h3> | 140 | <h3 id="updating-a-community-shaarli">Updating a community Shaarli</h3> |
139 | <p>If you have installed Shaarli from the <a href="Download#clone-with-git-recommended">community Git repository</a>, simply <a href="https://www.git-scm.com/docs/git-pull">pull new changes</a> from your local clone:<a href=".html"></a></p> | 141 | <p>If you have installed Shaarli from the <a href="Download#clone-with-git-recommended">community Git repository</a>, simply <a href="https://www.git-scm.com/docs/git-pull">pull new changes</a> from your local clone:<a href=".html"></a></p> |
@@ -149,7 +151,7 @@ $ <span class="fu">git</span> pull | |||
149 | <span class="ex">tests/Url/UrlTest.php</span> <span class="kw">|</span> <span class="ex">1</span> + | 151 | <span class="ex">tests/Url/UrlTest.php</span> <span class="kw">|</span> <span class="ex">1</span> + |
150 | <span class="ex">3</span> files changed, 3 insertions(+), <span class="ex">1</span> deletion(-)</code></pre></div> | 152 | <span class="ex">3</span> files changed, 3 insertions(+), <span class="ex">1</span> deletion(-)</code></pre></div> |
151 | <p>Shaarli >= <code>v0.8.x</code>: install/update third-party PHP dependencies using <a href="https://getcomposer.org/">Composer</a>:<a href=".html"></a></p> | 153 | <p>Shaarli >= <code>v0.8.x</code>: install/update third-party PHP dependencies using <a href="https://getcomposer.org/">Composer</a>:<a href=".html"></a></p> |
152 | <div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="ex">composer</span> update --no-dev | 154 | <div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="ex">composer</span> install --no-dev |
153 | 155 | ||
154 | <span class="ex">Loading</span> composer repositories with package information | 156 | <span class="ex">Loading</span> composer repositories with package information |
155 | <span class="ex">Updating</span> dependencies | 157 | <span class="ex">Updating</span> dependencies |
@@ -214,7 +216,7 @@ $ <span class="fu">git</span> branch -vv | |||
214 | <span class="ex">master</span> 029f75f [sebsauvage/master] Update README.md[](.html) | 216 | <span class="ex">master</span> 029f75f [sebsauvage/master] Update README.md[](.html) |
215 | <span class="ex">*</span> stable 890afc3 [origin/stable] Merge pull request <span class="co">#509 from ArthurHoaro/v0.6.5[](.html)</span></code></pre></div> | 217 | <span class="ex">*</span> stable 890afc3 [origin/stable] Merge pull request <span class="co">#509 from ArthurHoaro/v0.6.5[](.html)</span></code></pre></div> |
216 | <p>Shaarli >= <code>v0.8.x</code>: install/update third-party PHP dependencies using <a href="https://getcomposer.org/">Composer</a>:<a href=".html"></a></p> | 218 | <p>Shaarli >= <code>v0.8.x</code>: install/update third-party PHP dependencies using <a href="https://getcomposer.org/">Composer</a>:<a href=".html"></a></p> |
217 | <div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="ex">composer</span> update --no-dev | 219 | <div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="ex">composer</span> install --no-dev |
218 | 220 | ||
219 | <span class="ex">Loading</span> composer repositories with package information | 221 | <span class="ex">Loading</span> composer repositories with package information |
220 | <span class="ex">Updating</span> dependencies | 222 | <span class="ex">Updating</span> dependencies |
@@ -238,5 +240,20 @@ $ <span class="fu">git</span> gc | |||
238 | <span class="ex">Total</span> 3317 (delta 2050), <span class="ex">reused</span> 3301 (delta 2034)<span class="ex">to</span></code></pre></div> | 240 | <span class="ex">Total</span> 3317 (delta 2050), <span class="ex">reused</span> 3301 (delta 2034)<span class="ex">to</span></code></pre></div> |
239 | <h4 id="step-3-configuration">Step 3: configuration</h4> | 241 | <h4 id="step-3-configuration">Step 3: configuration</h4> |
240 | <p>After migrating, access your fresh Shaarli installation from a web browser; the configuration will then be automatically updated, and new settings added to <code>data/config.php</code> (see <a href="Shaarli-configuration.html">Shaarli configuration</a> for more details).</p> | 242 | <p>After migrating, access your fresh Shaarli installation from a web browser; the configuration will then be automatically updated, and new settings added to <code>data/config.php</code> (see <a href="Shaarli-configuration.html">Shaarli configuration</a> for more details).</p> |
243 | <h2 id="troubleshooting">Troubleshooting</h2> | ||
244 | <p>If the solutions provided here doesn't work, please open an issue specifying which version you're upgrading from and to.</p> | ||
245 | <h3 id="you-must-specify-an-integer-as-a-key">You must specify an integer as a key</h3> | ||
246 | <p>In <code>v0.8.1</code> we changed how link keys are handled (from timestamps to incremental integers).<br /> | ||
247 | Take a look at <code>data/updates.txt</code> content.</p> | ||
248 | <h4 id="updates.txt-contains-updatemethoddatastoreids"><code>updates.txt</code> contains <code>updateMethodDatastoreIds</code></h4> | ||
249 | <p>Try to delete it and refresh your page while being logged in.</p> | ||
250 | <h4 id="updates.txt-doesnt-exists-or-doesnt-contain-updatemethoddatastoreids"><code>updates.txt</code> doesn't exists or doesn't contain <code>updateMethodDatastoreIds</code></h4> | ||
251 | <ol> | ||
252 | <li>Create <code>data/updates.txt</code> if it doesn't exist.</li> | ||
253 | <li>Paste this string in the update file <code>;updateMethodRenameDashTags;</code></li> | ||
254 | <li>Login to Shaarli.</li> | ||
255 | <li>Delete the update file.</li> | ||
256 | <li>Refresh.</li> | ||
257 | </ol> | ||
241 | </body> | 258 | </body> |
242 | </html> | 259 | </html> |
diff --git a/doc/Upgrade-and-migration.md b/doc/Upgrade-and-migration.md index 0bc33824..d36eb862 100644 --- a/doc/Upgrade-and-migration.md +++ b/doc/Upgrade-and-migration.md | |||
@@ -1,11 +1,17 @@ | |||
1 | #Upgrade and migration | 1 | #Upgrade and migration |
2 | ## Preparation | 2 | ## Preparation |
3 | ### Note your current version | ||
4 | |||
5 | If anything goes wrong, it's important for us to know which version you're upgrading from. | ||
6 | The current version is present in the `version.php` file. | ||
7 | |||
3 | ### Backup your data | 8 | ### Backup your data |
4 | 9 | ||
5 | Shaarli stores all user data under the `data` directory: | 10 | Shaarli stores all user data under the `data` directory: |
6 | - `data/config.php` - main configuration file | 11 | - `data/config.php` - main configuration file |
7 | - `data/datastore.php` - bookmarked links | 12 | - `data/datastore.php` - bookmarked links |
8 | - `data/ipbans.php` - banned IP addresses | 13 | - `data/ipbans.php` - banned IP addresses |
14 | - `data/updates.txt` - contains all automatic update to the configuration and datastore files already run | ||
9 | 15 | ||
10 | See [Shaarli configuration](Shaarli-configuration.html) for more information about Shaarli resources. | 16 | See [Shaarli configuration](Shaarli-configuration.html) for more information about Shaarli resources. |
11 | 17 | ||
@@ -22,16 +28,14 @@ As all user data is kept under `data`, this is the only directory you need to wo | |||
22 | - update - see the following sections | 28 | - update - see the following sections |
23 | - check or restore the `data` directory | 29 | - check or restore the `data` directory |
24 | 30 | ||
25 | ## Upgrading from release archives | 31 | ## Recommended : Upgrading from release archives |
26 | All tagged revisions can be downloaded as tarballs or ZIP archives from the [releases](https://github.com/shaarli/Shaarli/releases) page.[](.html) | 32 | All tagged revisions can be downloaded as tarballs or ZIP archives from the [releases](https://github.com/shaarli/Shaarli/releases) page.[](.html) |
27 | 33 | ||
28 | We _recommend_ using the releases from the `stable` branch, which are available as: | 34 | We recommend that you use the latest release tarball with the `-full` suffix. It contains the dependencies, please read [Download and installation](Download-and-installation.html) for `git` complete instructions. |
29 | - gzipped tarball - https://github.com/shaarli/Shaarli/archive/stable.tar.gz | ||
30 | - ZIP archive - https://github.com/shaarli/Shaarli/archive/stable.zip | ||
31 | 35 | ||
32 | Once downloaded, extract the archive locally and update your remote installation (e.g. via FTP) -be sure you keep the contents of the `data` directory! | 36 | Once downloaded, extract the archive locally and update your remote installation (e.g. via FTP) -be sure you keep the content of the `data` directory! |
33 | 37 | ||
34 | After upgrading, access your fresh Shaarli installation from a web browser; the configuration will then be automatically updated, and new settings added to `data/config.php` (see [Shaarli configuration](Shaarli-configuration.html) for more details). | 38 | After upgrading, access your fresh Shaarli installation from a web browser; the configuration and data store will then be automatically updated, and new settings added to `data/config.json.php` (see [Shaarli configuration](Shaarli-configuration.html) for more details). |
35 | 39 | ||
36 | ## Upgrading with Git | 40 | ## Upgrading with Git |
37 | ### Updating a community Shaarli | 41 | ### Updating a community Shaarli |
@@ -54,7 +58,7 @@ Fast-forward | |||
54 | Shaarli >= `v0.8.x`: install/update third-party PHP dependencies using [Composer](https://getcomposer.org/):[](.html) | 58 | Shaarli >= `v0.8.x`: install/update third-party PHP dependencies using [Composer](https://getcomposer.org/):[](.html) |
55 | 59 | ||
56 | ```bash | 60 | ```bash |
57 | $ composer update --no-dev | 61 | $ composer install --no-dev |
58 | 62 | ||
59 | Loading composer repositories with package information | 63 | Loading composer repositories with package information |
60 | Updating dependencies | 64 | Updating dependencies |
@@ -129,7 +133,7 @@ $ git branch -vv | |||
129 | Shaarli >= `v0.8.x`: install/update third-party PHP dependencies using [Composer](https://getcomposer.org/):[](.html) | 133 | Shaarli >= `v0.8.x`: install/update third-party PHP dependencies using [Composer](https://getcomposer.org/):[](.html) |
130 | 134 | ||
131 | ```bash | 135 | ```bash |
132 | $ composer update --no-dev | 136 | $ composer install --no-dev |
133 | 137 | ||
134 | Loading composer repositories with package information | 138 | Loading composer repositories with package information |
135 | Updating dependencies | 139 | Updating dependencies |
@@ -159,3 +163,24 @@ Total 3317 (delta 2050), reused 3301 (delta 2034)to | |||
159 | 163 | ||
160 | #### Step 3: configuration | 164 | #### Step 3: configuration |
161 | After migrating, access your fresh Shaarli installation from a web browser; the configuration will then be automatically updated, and new settings added to `data/config.php` (see [Shaarli configuration](Shaarli-configuration.html) for more details). | 165 | After migrating, access your fresh Shaarli installation from a web browser; the configuration will then be automatically updated, and new settings added to `data/config.php` (see [Shaarli configuration](Shaarli-configuration.html) for more details). |
166 | |||
167 | ## Troubleshooting | ||
168 | |||
169 | If the solutions provided here doesn't work, please open an issue specifying which version you're upgrading from and to. | ||
170 | |||
171 | ### You must specify an integer as a key | ||
172 | |||
173 | In `v0.8.1` we changed how link keys are handled (from timestamps to incremental integers). | ||
174 | Take a look at `data/updates.txt` content. | ||
175 | |||
176 | #### `updates.txt` contains `updateMethodDatastoreIds` | ||
177 | |||
178 | Try to delete it and refresh your page while being logged in. | ||
179 | |||
180 | #### `updates.txt` doesn't exists or doesn't contain `updateMethodDatastoreIds` | ||
181 | |||
182 | 1. Create `data/updates.txt` if it doesn't exist. | ||
183 | 2. Paste this string in the update file `;updateMethodRenameDashTags;` | ||
184 | 3. Login to Shaarli. | ||
185 | 4. Delete the update file. | ||
186 | 5. Refresh. | ||
diff --git a/doc/Usage.html b/doc/Usage.html index 63f21d93..b5855881 100644 --- a/doc/Usage.html +++ b/doc/Usage.html | |||
@@ -32,6 +32,7 @@ | |||
32 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | 32 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> |
33 | <li><a href="Firefox-share.html">Firefox share</a></li> | 33 | <li><a href="Firefox-share.html">Firefox share</a></li> |
34 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | 34 | <li><a href="RSS-feeds.html">RSS feeds</a></li> |
35 | <li><a href="REST-API.html">REST API</a></li> | ||
35 | </ul></li> | 36 | </ul></li> |
36 | <li>How To | 37 | <li>How To |
37 | <ul> | 38 | <ul> |
@@ -50,6 +51,7 @@ | |||
50 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | 51 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> |
51 | <li><a href="Plugin-System.html">Plugin System</a></li> | 52 | <li><a href="Plugin-System.html">Plugin System</a></li> |
52 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | 53 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> |
54 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
53 | <li><a href="Security.html">Security</a></li> | 55 | <li><a href="Security.html">Security</a></li> |
54 | <li><a href="Static-analysis.html">Static analysis</a></li> | 56 | <li><a href="Static-analysis.html">Static analysis</a></li> |
55 | <li><a href="Theming.html">Theming</a></li> | 57 | <li><a href="Theming.html">Theming</a></li> |
diff --git a/doc/Versioning-and-Branches.html b/doc/Versioning-and-Branches.html new file mode 100644 index 00000000..4dfe4a91 --- /dev/null +++ b/doc/Versioning-and-Branches.html | |||
@@ -0,0 +1,156 @@ | |||
1 | <!DOCTYPE html> | ||
2 | <html> | ||
3 | <head> | ||
4 | <meta charset="utf-8"> | ||
5 | <meta name="generator" content="pandoc"> | ||
6 | <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes"> | ||
7 | <title>Shaarli – Versioning and Branches</title> | ||
8 | <style type="text/css">code{white-space: pre;}</style> | ||
9 | <style type="text/css"> | ||
10 | div.sourceCode { overflow-x: auto; } | ||
11 | table.sourceCode, tr.sourceCode, td.lineNumbers, td.sourceCode { | ||
12 | margin: 0; padding: 0; vertical-align: baseline; border: none; } | ||
13 | table.sourceCode { width: 100%; line-height: 100%; } | ||
14 | td.lineNumbers { text-align: right; padding-right: 4px; padding-left: 4px; color: #aaaaaa; border-right: 1px solid #aaaaaa; } | ||
15 | td.sourceCode { padding-left: 5px; } | ||
16 | code > span.kw { color: #007020; font-weight: bold; } /* Keyword */ | ||
17 | code > span.dt { color: #902000; } /* DataType */ | ||
18 | code > span.dv { color: #40a070; } /* DecVal */ | ||
19 | code > span.bn { color: #40a070; } /* BaseN */ | ||
20 | code > span.fl { color: #40a070; } /* Float */ | ||
21 | code > span.ch { color: #4070a0; } /* Char */ | ||
22 | code > span.st { color: #4070a0; } /* String */ | ||
23 | code > span.co { color: #60a0b0; font-style: italic; } /* Comment */ | ||
24 | code > span.ot { color: #007020; } /* Other */ | ||
25 | code > span.al { color: #ff0000; font-weight: bold; } /* Alert */ | ||
26 | code > span.fu { color: #06287e; } /* Function */ | ||
27 | code > span.er { color: #ff0000; font-weight: bold; } /* Error */ | ||
28 | code > span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */ | ||
29 | code > span.cn { color: #880000; } /* Constant */ | ||
30 | code > span.sc { color: #4070a0; } /* SpecialChar */ | ||
31 | code > span.vs { color: #4070a0; } /* VerbatimString */ | ||
32 | code > span.ss { color: #bb6688; } /* SpecialString */ | ||
33 | code > span.im { } /* Import */ | ||
34 | code > span.va { color: #19177c; } /* Variable */ | ||
35 | code > span.cf { color: #007020; font-weight: bold; } /* ControlFlow */ | ||
36 | code > span.op { color: #666666; } /* Operator */ | ||
37 | code > span.bu { } /* BuiltIn */ | ||
38 | code > span.ex { } /* Extension */ | ||
39 | code > span.pp { color: #bc7a00; } /* Preprocessor */ | ||
40 | code > span.at { color: #7d9029; } /* Attribute */ | ||
41 | code > span.do { color: #ba2121; font-style: italic; } /* Documentation */ | ||
42 | code > span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */ | ||
43 | code > span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */ | ||
44 | code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */ | ||
45 | </style> | ||
46 | <link rel="stylesheet" href="github-markdown.css"> | ||
47 | <!--[if lt IE 9]> | ||
48 | <script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script> | ||
49 | <![endif]--> | ||
50 | </head> | ||
51 | <body> | ||
52 | <div id="local-sidebar"> | ||
53 | <ul> | ||
54 | <li><a href="Home.html">Home</a></li> | ||
55 | <li>Setup | ||
56 | <ul> | ||
57 | <li><a href="Download-and-Installation.html">Download and Installation</a></li> | ||
58 | <li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li> | ||
59 | <li><a href="Server-requirements.html">Server requirements</a></li> | ||
60 | <li><a href="Server-configuration.html">Server configuration</a></li> | ||
61 | <li><a href="Server-security.html">Server security</a></li> | ||
62 | <li><a href="Shaarli-configuration.html">Shaarli configuration</a></li> | ||
63 | <li><a href="Plugins.html">Plugins</a></li> | ||
64 | </ul></li> | ||
65 | <li><a href="Docker.html">Docker</a></li> | ||
66 | <li><a href="Usage.html">Usage</a> | ||
67 | <ul> | ||
68 | <li><a href="Sharing-button.html">Sharing button</a> (bookmarklet)</li> | ||
69 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | ||
70 | <li><a href="Firefox-share.html">Firefox share</a></li> | ||
71 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | ||
72 | <li><a href="REST-API.html">REST API</a></li> | ||
73 | </ul></li> | ||
74 | <li>How To | ||
75 | <ul> | ||
76 | <li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li> | ||
77 | <li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li> | ||
78 | <li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li> | ||
79 | <li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> | ||
80 | <li><a href="Datastore-hacks.html">Datastore hacks</a></li> | ||
81 | </ul></li> | ||
82 | <li><a href="Troubleshooting.html">Troubleshooting</a></li> | ||
83 | <li><a href="Development.html">Development</a> | ||
84 | <ul> | ||
85 | <li><a href="GnuPG-signature.html">GnuPG signature</a></li> | ||
86 | <li><a href="Coding-guidelines.html">Coding guidelines</a></li> | ||
87 | <li><a href="Directory-structure.html">Directory structure</a></li> | ||
88 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | ||
89 | <li><a href="Plugin-System.html">Plugin System</a></li> | ||
90 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | ||
91 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
92 | <li><a href="Security.html">Security</a></li> | ||
93 | <li><a href="Static-analysis.html">Static analysis</a></li> | ||
94 | <li><a href="Theming.html">Theming</a></li> | ||
95 | <li><a href="Unit-tests.html">Unit tests</a></li> | ||
96 | </ul></li> | ||
97 | <li>About | ||
98 | <ul> | ||
99 | <li><a href="FAQ.html">FAQ</a></li> | ||
100 | <li><a href="Community-&-Related-software.html">Community & Related software</a></li> | ||
101 | </ul></li> | ||
102 | </ul> | ||
103 | </div> | ||
104 | <h1 id="versioning-and-branches">Versioning and Branches</h1> | ||
105 | <p>[<strong>WORK IN PROGRESS</strong>][](.html)</p> | ||
106 | <p>It's important to understand how Shaarli branches work, especially if you're maintaining a 3rd party tools for Shaarli (theme, plugin, etc.), to be sure stay compatible.</p> | ||
107 | <h2 id="master-branch"><code>master</code> branch</h2> | ||
108 | <p>The <code>master</code> branch is the development branch. Any new change MUST go through this branch using Pull Requests.</p> | ||
109 | <p>Remarks:</p> | ||
110 | <ul> | ||
111 | <li>This branch shouldn't be used for production as it isn't necessary stable.</li> | ||
112 | <li>3rd party aren't required to be compatible with the latest changes.</li> | ||
113 | <li>Official plugins, themes and libraries (contained within Shaarli organization repos) must be compatible with the master branch.</li> | ||
114 | <li>The version in this branch is always <code>dev</code>.</li> | ||
115 | </ul> | ||
116 | <h2 id="v0.x-branch"><code>v0.x</code> branch</h2> | ||
117 | <p>This <code>v0.x</code> branch, points to the latest <code>v0.x.y</code> release.</p> | ||
118 | <p>Explanation:</p> | ||
119 | <p>When a new version is released, it might contains a major bug which isn't detected right away. For example, a new PHP version is released, containing backward compatibility issue which doesn't work with Shaarli.</p> | ||
120 | <p>In this case, the issue is fixed in the <code>master</code> branch, and the fix is backported the to the <code>v0.x</code> branch. Then a new release is made from the <code>v0.x</code> branch.</p> | ||
121 | <p>This workflow allow us to fix any major bug detected, without having to release bleeding edge feature too soon.</p> | ||
122 | <h2 id="latest-branch"><code>latest</code> branch</h2> | ||
123 | <p>This branch point the latest release. It recommended to use it to get the latest tested changes.</p> | ||
124 | <h2 id="stable-branch"><code>stable</code> branch</h2> | ||
125 | <p>The <code>stable</code> branch doesn't contain any major bug, and is one major digit version behind the latest release.</p> | ||
126 | <p>For example, the current latest release is <code>v0.8.3</code>, the stable branch is an alias to the latest <code>v0.7.x</code> release. When the <code>v0.9.0</code> version will be released, the stable will move to the latest <code>v0.8.x</code> release.</p> | ||
127 | <p>Remarks:</p> | ||
128 | <ul> | ||
129 | <li>Shaarli release pace isn't fast, and the stable branch might be a few months behind the latest release.</li> | ||
130 | </ul> | ||
131 | <h2 id="releases">Releases</h2> | ||
132 | <p>Releases are always made from the latest <code>v0.x</code> branch.</p> | ||
133 | <p>Note that for every release, we manually generate a tarball which contains all Shaarli dependencies, making Shaarli's installation only one step.</p> | ||
134 | <h2 id="advices-on-3rd-party-git-repos-workflow">Advices on 3rd party git repos workflow</h2> | ||
135 | <h3 id="versioning">Versioning</h3> | ||
136 | <p>Any time a new Shaarli release is published, you should publish a new release of your repo if the changes affected you since the latest release (take a look at the <a href="https://github.com/shaarli/Shaarli/releases">changelog</a> (<em>Draft</em> means not released yet) and the commit log (like <a href="https://github.com/shaarli/Shaarli/commits/master/tpl/default"><code>tpl</code> folder</a> for themes)). You can either:<a href=".html"></a></p> | ||
137 | <ul> | ||
138 | <li>use the Shaarli version number, with your repo version. For example, if Shaarli <code>v0.8.3</code> is released, publish a <code>v0.8.3-1</code> release, where <code>v0.8.3</code> states Shaarli compatibility and <code>-1</code> is your own version digit for the current Shaarli version.</li> | ||
139 | <li>use your own versioning scheme, and state Shaarli compatibility in the release description.</li> | ||
140 | </ul> | ||
141 | <p>Using this, any user will be able to pick the release matching his own Shaarli version.</p> | ||
142 | <h3 id="major-bugfix-backport-releases">Major bugfix backport releases</h3> | ||
143 | <p>To be able to support backported fixes, it recommended to use our workflow:</p> | ||
144 | <div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="co"># In master, fix the major bug</span> | ||
145 | <span class="fu">git</span> commit -m <span class="st">"Katastrophe"</span> | ||
146 | <span class="fu">git</span> push origin master | ||
147 | <span class="co"># Get your commit hash</span> | ||
148 | <span class="fu">git</span> log --format=<span class="st">"%H"</span> -n 1 | ||
149 | <span class="co"># Create a new branch from your latest release, let's say v0.8.2-1 (the tag name)</span> | ||
150 | <span class="fu">git</span> checkout -b katastrophe v0.8.2-1 | ||
151 | <span class="co"># Backport the fix commit to your brand new branch</span> | ||
152 | <span class="fu">git</span> cherry-pick <span class="op"><</span>fix commit hash<span class="op">></span> | ||
153 | <span class="fu">git</span> push origin katastrophe | ||
154 | <span class="co"># Then you just have to make a new release from the `katastrophe` branch tagged `v0.8.3-1`</span></code></pre></div> | ||
155 | </body> | ||
156 | </html> | ||
diff --git a/doc/Versioning-and-Branches.md b/doc/Versioning-and-Branches.md new file mode 100644 index 00000000..bbc7719e --- /dev/null +++ b/doc/Versioning-and-Branches.md | |||
@@ -0,0 +1,76 @@ | |||
1 | #Versioning and Branches | ||
2 | [**WORK IN PROGRESS**][](.html) | ||
3 | |||
4 | It's important to understand how Shaarli branches work, especially if you're maintaining a 3rd party tools for Shaarli (theme, plugin, etc.), to be sure stay compatible. | ||
5 | |||
6 | ## `master` branch | ||
7 | |||
8 | The `master` branch is the development branch. Any new change MUST go through this branch using Pull Requests. | ||
9 | |||
10 | Remarks: | ||
11 | |||
12 | * This branch shouldn't be used for production as it isn't necessary stable. | ||
13 | * 3rd party aren't required to be compatible with the latest changes. | ||
14 | * Official plugins, themes and libraries (contained within Shaarli organization repos) must be compatible with the master branch. | ||
15 | * The version in this branch is always `dev`. | ||
16 | |||
17 | ## `v0.x` branch | ||
18 | |||
19 | This `v0.x` branch, points to the latest `v0.x.y` release. | ||
20 | |||
21 | Explanation: | ||
22 | |||
23 | When a new version is released, it might contains a major bug which isn't detected right away. For example, a new PHP version is released, containing backward compatibility issue which doesn't work with Shaarli. | ||
24 | |||
25 | In this case, the issue is fixed in the `master` branch, and the fix is backported the to the `v0.x` branch. Then a new release is made from the `v0.x` branch. | ||
26 | |||
27 | This workflow allow us to fix any major bug detected, without having to release bleeding edge feature too soon. | ||
28 | |||
29 | ## `latest` branch | ||
30 | |||
31 | This branch point the latest release. It recommended to use it to get the latest tested changes. | ||
32 | |||
33 | ## `stable` branch | ||
34 | |||
35 | The `stable` branch doesn't contain any major bug, and is one major digit version behind the latest release. | ||
36 | |||
37 | For example, the current latest release is `v0.8.3`, the stable branch is an alias to the latest `v0.7.x` release. When the `v0.9.0` version will be released, the stable will move to the latest `v0.8.x` release. | ||
38 | |||
39 | Remarks: | ||
40 | |||
41 | * Shaarli release pace isn't fast, and the stable branch might be a few months behind the latest release. | ||
42 | |||
43 | ## Releases | ||
44 | |||
45 | Releases are always made from the latest `v0.x` branch. | ||
46 | |||
47 | Note that for every release, we manually generate a tarball which contains all Shaarli dependencies, making Shaarli's installation only one step. | ||
48 | |||
49 | ## Advices on 3rd party git repos workflow | ||
50 | |||
51 | ### Versioning | ||
52 | |||
53 | Any time a new Shaarli release is published, you should publish a new release of your repo if the changes affected you since the latest release (take a look at the [changelog](https://github.com/shaarli/Shaarli/releases) (*Draft* means not released yet) and the commit log (like [`tpl` folder](https://github.com/shaarli/Shaarli/commits/master/tpl/default) for themes)). You can either:[](.html) | ||
54 | |||
55 | - use the Shaarli version number, with your repo version. For example, if Shaarli `v0.8.3` is released, publish a `v0.8.3-1` release, where `v0.8.3` states Shaarli compatibility and `-1` is your own version digit for the current Shaarli version. | ||
56 | - use your own versioning scheme, and state Shaarli compatibility in the release description. | ||
57 | |||
58 | Using this, any user will be able to pick the release matching his own Shaarli version. | ||
59 | |||
60 | ### Major bugfix backport releases | ||
61 | |||
62 | To be able to support backported fixes, it recommended to use our workflow: | ||
63 | |||
64 | ```bash | ||
65 | # In master, fix the major bug | ||
66 | git commit -m "Katastrophe" | ||
67 | git push origin master | ||
68 | # Get your commit hash | ||
69 | git log --format="%H" -n 1 | ||
70 | # Create a new branch from your latest release, let's say v0.8.2-1 (the tag name) | ||
71 | git checkout -b katastrophe v0.8.2-1 | ||
72 | # Backport the fix commit to your brand new branch | ||
73 | git cherry-pick <fix commit hash> | ||
74 | git push origin katastrophe | ||
75 | # Then you just have to make a new release from the `katastrophe` branch tagged `v0.8.3-1` | ||
76 | ``` | ||
diff --git a/doc/_Footer.html b/doc/_Footer.html index e8a62d2a..09473a38 100644 --- a/doc/_Footer.html +++ b/doc/_Footer.html | |||
@@ -32,6 +32,7 @@ | |||
32 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | 32 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> |
33 | <li><a href="Firefox-share.html">Firefox share</a></li> | 33 | <li><a href="Firefox-share.html">Firefox share</a></li> |
34 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | 34 | <li><a href="RSS-feeds.html">RSS feeds</a></li> |
35 | <li><a href="REST-API.html">REST API</a></li> | ||
35 | </ul></li> | 36 | </ul></li> |
36 | <li>How To | 37 | <li>How To |
37 | <ul> | 38 | <ul> |
@@ -50,6 +51,7 @@ | |||
50 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | 51 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> |
51 | <li><a href="Plugin-System.html">Plugin System</a></li> | 52 | <li><a href="Plugin-System.html">Plugin System</a></li> |
52 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | 53 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> |
54 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
53 | <li><a href="Security.html">Security</a></li> | 55 | <li><a href="Security.html">Security</a></li> |
54 | <li><a href="Static-analysis.html">Static analysis</a></li> | 56 | <li><a href="Static-analysis.html">Static analysis</a></li> |
55 | <li><a href="Theming.html">Theming</a></li> | 57 | <li><a href="Theming.html">Theming</a></li> |
diff --git a/doc/_Sidebar.html b/doc/_Sidebar.html index bb6dad93..d3f94560 100644 --- a/doc/_Sidebar.html +++ b/doc/_Sidebar.html | |||
@@ -32,6 +32,7 @@ | |||
32 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | 32 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> |
33 | <li><a href="Firefox-share.html">Firefox share</a></li> | 33 | <li><a href="Firefox-share.html">Firefox share</a></li> |
34 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | 34 | <li><a href="RSS-feeds.html">RSS feeds</a></li> |
35 | <li><a href="REST-API.html">REST API</a></li> | ||
35 | </ul></li> | 36 | </ul></li> |
36 | <li>How To | 37 | <li>How To |
37 | <ul> | 38 | <ul> |
@@ -50,6 +51,7 @@ | |||
50 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | 51 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> |
51 | <li><a href="Plugin-System.html">Plugin System</a></li> | 52 | <li><a href="Plugin-System.html">Plugin System</a></li> |
52 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | 53 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> |
54 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
53 | <li><a href="Security.html">Security</a></li> | 55 | <li><a href="Security.html">Security</a></li> |
54 | <li><a href="Static-analysis.html">Static analysis</a></li> | 56 | <li><a href="Static-analysis.html">Static analysis</a></li> |
55 | <li><a href="Theming.html">Theming</a></li> | 57 | <li><a href="Theming.html">Theming</a></li> |
@@ -82,6 +84,7 @@ | |||
82 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | 84 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> |
83 | <li><a href="Firefox-share.html">Firefox share</a></li> | 85 | <li><a href="Firefox-share.html">Firefox share</a></li> |
84 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | 86 | <li><a href="RSS-feeds.html">RSS feeds</a></li> |
87 | <li><a href="REST-API.html">REST API</a></li> | ||
85 | </ul></li> | 88 | </ul></li> |
86 | <li>How To | 89 | <li>How To |
87 | <ul> | 90 | <ul> |
@@ -100,6 +103,7 @@ | |||
100 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | 103 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> |
101 | <li><a href="Plugin-System.html">Plugin System</a></li> | 104 | <li><a href="Plugin-System.html">Plugin System</a></li> |
102 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | 105 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> |
106 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
103 | <li><a href="Security.html">Security</a></li> | 107 | <li><a href="Security.html">Security</a></li> |
104 | <li><a href="Static-analysis.html">Static analysis</a></li> | 108 | <li><a href="Static-analysis.html">Static analysis</a></li> |
105 | <li><a href="Theming.html">Theming</a></li> | 109 | <li><a href="Theming.html">Theming</a></li> |
diff --git a/doc/_Sidebar.md b/doc/_Sidebar.md index 1778e3a3..8df2e565 100644 --- a/doc/_Sidebar.md +++ b/doc/_Sidebar.md | |||
@@ -14,6 +14,7 @@ | |||
14 | - [Browsing and Searching](Browsing-and-Searching.html) | 14 | - [Browsing and Searching](Browsing-and-Searching.html) |
15 | - [Firefox share](Firefox-share.html) | 15 | - [Firefox share](Firefox-share.html) |
16 | - [RSS feeds](RSS-feeds.html) | 16 | - [RSS feeds](RSS-feeds.html) |
17 | - [REST API](REST-API.html) | ||
17 | - How To | 18 | - How To |
18 | - [Backup, restore, import and export](Backup,-restore,-import-and-export.html) | 19 | - [Backup, restore, import and export](Backup,-restore,-import-and-export.html) |
19 | - [Copy an existing installation over SSH and serve it locally](Copy-an-existing-installation-over-SSH-and-serve-it-locally.html) | 20 | - [Copy an existing installation over SSH and serve it locally](Copy-an-existing-installation-over-SSH-and-serve-it-locally.html) |
@@ -28,6 +29,7 @@ | |||
28 | - [3rd party libraries](3rd-party-libraries.html) | 29 | - [3rd party libraries](3rd-party-libraries.html) |
29 | - [Plugin System](Plugin-System.html) | 30 | - [Plugin System](Plugin-System.html) |
30 | - [Release Shaarli](Release-Shaarli.html) | 31 | - [Release Shaarli](Release-Shaarli.html) |
32 | - [Versioning and Branches](Versioning-and-Branches.html) | ||
31 | - [Security](Security.html) | 33 | - [Security](Security.html) |
32 | - [Static analysis](Static-analysis.html) | 34 | - [Static analysis](Static-analysis.html) |
33 | - [Theming](Theming.html) | 35 | - [Theming](Theming.html) |
diff --git a/doc/sidebar.html b/doc/sidebar.html index 4dad0161..478840d1 100644 --- a/doc/sidebar.html +++ b/doc/sidebar.html | |||
@@ -18,6 +18,7 @@ | |||
18 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> | 18 | <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> |
19 | <li><a href="Firefox-share.html">Firefox share</a></li> | 19 | <li><a href="Firefox-share.html">Firefox share</a></li> |
20 | <li><a href="RSS-feeds.html">RSS feeds</a></li> | 20 | <li><a href="RSS-feeds.html">RSS feeds</a></li> |
21 | <li><a href="REST-API.html">REST API</a></li> | ||
21 | </ul></li> | 22 | </ul></li> |
22 | <li>How To | 23 | <li>How To |
23 | <ul> | 24 | <ul> |
@@ -36,6 +37,7 @@ | |||
36 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> | 37 | <li><a href="3rd-party-libraries.html">3rd party libraries</a></li> |
37 | <li><a href="Plugin-System.html">Plugin System</a></li> | 38 | <li><a href="Plugin-System.html">Plugin System</a></li> |
38 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> | 39 | <li><a href="Release-Shaarli.html">Release Shaarli</a></li> |
40 | <li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li> | ||
39 | <li><a href="Security.html">Security</a></li> | 41 | <li><a href="Security.html">Security</a></li> |
40 | <li><a href="Static-analysis.html">Static analysis</a></li> | 42 | <li><a href="Static-analysis.html">Static analysis</a></li> |
41 | <li><a href="Theming.html">Theming</a></li> | 43 | <li><a href="Theming.html">Theming</a></li> |
@@ -62,6 +62,7 @@ require_once 'application/CachedPage.php'; | |||
62 | require_once 'application/config/ConfigPlugin.php'; | 62 | require_once 'application/config/ConfigPlugin.php'; |
63 | require_once 'application/FeedBuilder.php'; | 63 | require_once 'application/FeedBuilder.php'; |
64 | require_once 'application/FileUtils.php'; | 64 | require_once 'application/FileUtils.php'; |
65 | require_once 'application/History.php'; | ||
65 | require_once 'application/HttpUtils.php'; | 66 | require_once 'application/HttpUtils.php'; |
66 | require_once 'application/Languages.php'; | 67 | require_once 'application/Languages.php'; |
67 | require_once 'application/LinkDB.php'; | 68 | require_once 'application/LinkDB.php'; |
@@ -224,27 +225,6 @@ function setup_login_state($conf) | |||
224 | } | 225 | } |
225 | $userIsLoggedIn = setup_login_state($conf); | 226 | $userIsLoggedIn = setup_login_state($conf); |
226 | 227 | ||
227 | /** | ||
228 | * PubSubHubbub protocol support (if enabled) [UNTESTED] | ||
229 | * (Source: http://aldarone.fr/les-flux-rss-shaarli-et-pubsubhubbub/ ) | ||
230 | * | ||
231 | * @param ConfigManager $conf Configuration Manager instance. | ||
232 | */ | ||
233 | function pubsubhub($conf) | ||
234 | { | ||
235 | $pshUrl = $conf->get('config.PUBSUBHUB_URL'); | ||
236 | if (!empty($pshUrl)) | ||
237 | { | ||
238 | include_once './publisher.php'; | ||
239 | $p = new Publisher($pshUrl); | ||
240 | $topic_url = array ( | ||
241 | index_url($_SERVER).'?do=atom', | ||
242 | index_url($_SERVER).'?do=rss' | ||
243 | ); | ||
244 | $p->publish_update($topic_url); | ||
245 | } | ||
246 | } | ||
247 | |||
248 | // ------------------------------------------------------------------------------------------ | 228 | // ------------------------------------------------------------------------------------------ |
249 | // Session management | 229 | // Session management |
250 | 230 | ||
@@ -473,34 +453,6 @@ if (isset($_POST['login'])) | |||
473 | } | 453 | } |
474 | 454 | ||
475 | // ------------------------------------------------------------------------------------------ | 455 | // ------------------------------------------------------------------------------------------ |
476 | // Misc utility functions: | ||
477 | |||
478 | // Convert post_max_size/upload_max_filesize (e.g. '16M') parameters to bytes. | ||
479 | function return_bytes($val) | ||
480 | { | ||
481 | $val = trim($val); $last=strtolower($val[strlen($val)-1]); | ||
482 | switch($last) | ||
483 | { | ||
484 | case 'g': $val *= 1024; | ||
485 | case 'm': $val *= 1024; | ||
486 | case 'k': $val *= 1024; | ||
487 | } | ||
488 | return $val; | ||
489 | } | ||
490 | |||
491 | // Try to determine max file size for uploads (POST). | ||
492 | // Returns an integer (in bytes) | ||
493 | function getMaxFileSize() | ||
494 | { | ||
495 | $size1 = return_bytes(ini_get('post_max_size')); | ||
496 | $size2 = return_bytes(ini_get('upload_max_filesize')); | ||
497 | // Return the smaller of two: | ||
498 | $maxsize = min($size1,$size2); | ||
499 | // FIXME: Then convert back to readable notations ? (e.g. 2M instead of 2000000) | ||
500 | return $maxsize; | ||
501 | } | ||
502 | |||
503 | // ------------------------------------------------------------------------------------------ | ||
504 | // Token management for XSRF protection | 456 | // Token management for XSRF protection |
505 | // Token should be used in any form which acts on data (create,update,delete,import...). | 457 | // Token should be used in any form which acts on data (create,update,delete,import...). |
506 | if (!isset($_SESSION['tokens'])) $_SESSION['tokens']=array(); // Token are attached to the session. | 458 | if (!isset($_SESSION['tokens'])) $_SESSION['tokens']=array(); // Token are attached to the session. |
@@ -695,9 +647,11 @@ function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager) | |||
695 | 647 | ||
696 | $dayDate = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $day.'_000000'); | 648 | $dayDate = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $day.'_000000'); |
697 | $data = array( | 649 | $data = array( |
650 | 'pagetitle' => $conf->get('general.title') .' - '. format_date($dayDate, false), | ||
698 | 'linksToDisplay' => $linksToDisplay, | 651 | 'linksToDisplay' => $linksToDisplay, |
699 | 'cols' => $columns, | 652 | 'cols' => $columns, |
700 | 'day' => $dayDate->getTimestamp(), | 653 | 'day' => $dayDate->getTimestamp(), |
654 | 'dayDate' => $dayDate, | ||
701 | 'previousday' => $previousday, | 655 | 'previousday' => $previousday, |
702 | 'nextday' => $nextday, | 656 | 'nextday' => $nextday, |
703 | ); | 657 | ); |
@@ -732,7 +686,7 @@ function showLinkList($PAGE, $LINKSDB, $conf, $pluginManager) { | |||
732 | * @param PluginManager $pluginManager Plugin Manager instance, | 686 | * @param PluginManager $pluginManager Plugin Manager instance, |
733 | * @param LinkDB $LINKSDB | 687 | * @param LinkDB $LINKSDB |
734 | */ | 688 | */ |
735 | function renderPage($conf, $pluginManager, $LINKSDB) | 689 | function renderPage($conf, $pluginManager, $LINKSDB, $history) |
736 | { | 690 | { |
737 | $updater = new Updater( | 691 | $updater = new Updater( |
738 | read_updates_file($conf->get('resource.updates')), | 692 | read_updates_file($conf->get('resource.updates')), |
@@ -753,7 +707,7 @@ function renderPage($conf, $pluginManager, $LINKSDB) | |||
753 | die($e->getMessage()); | 707 | die($e->getMessage()); |
754 | } | 708 | } |
755 | 709 | ||
756 | $PAGE = new PageBuilder($conf); | 710 | $PAGE = new PageBuilder($conf, $LINKSDB); |
757 | $PAGE->assign('linkcount', count($LINKSDB)); | 711 | $PAGE->assign('linkcount', count($LINKSDB)); |
758 | $PAGE->assign('privateLinkcount', count_private($LINKSDB)); | 712 | $PAGE->assign('privateLinkcount', count_private($LINKSDB)); |
759 | $PAGE->assign('plugin_errors', $pluginManager->getErrors()); | 713 | $PAGE->assign('plugin_errors', $pluginManager->getErrors()); |
@@ -836,7 +790,9 @@ function renderPage($conf, $pluginManager, $LINKSDB) | |||
836 | // -------- Tag cloud | 790 | // -------- Tag cloud |
837 | if ($targetPage == Router::$PAGE_TAGCLOUD) | 791 | if ($targetPage == Router::$PAGE_TAGCLOUD) |
838 | { | 792 | { |
839 | $tags= $LINKSDB->allTags(); | 793 | $visibility = ! empty($_SESSION['privateonly']) ? 'private' : 'all'; |
794 | $filteringTags = isset($_GET['searchtags']) ? explode(' ', $_GET['searchtags']) : []; | ||
795 | $tags = $LINKSDB->linksCountPerTag($filteringTags, $visibility); | ||
840 | 796 | ||
841 | // We sort tags alphabetically, then choose a font size according to count. | 797 | // We sort tags alphabetically, then choose a font size according to count. |
842 | // First, find max value. | 798 | // First, find max value. |
@@ -845,17 +801,7 @@ function renderPage($conf, $pluginManager, $LINKSDB) | |||
845 | $maxcount = max($maxcount, $value); | 801 | $maxcount = max($maxcount, $value); |
846 | } | 802 | } |
847 | 803 | ||
848 | // Sort tags alphabetically: case insensitive, support locale if available. | 804 | alphabetical_sort($tags, true, true); |
849 | uksort($tags, function($a, $b) { | ||
850 | // Collator is part of PHP intl. | ||
851 | if (class_exists('Collator')) { | ||
852 | $c = new Collator(setlocale(LC_COLLATE, 0)); | ||
853 | if (!intl_is_failure(intl_get_error_code())) { | ||
854 | return $c->compare($a, $b); | ||
855 | } | ||
856 | } | ||
857 | return strcasecmp($a, $b); | ||
858 | }); | ||
859 | 805 | ||
860 | $tagList = array(); | 806 | $tagList = array(); |
861 | foreach($tags as $key => $value) { | 807 | foreach($tags as $key => $value) { |
@@ -870,6 +816,7 @@ function renderPage($conf, $pluginManager, $LINKSDB) | |||
870 | } | 816 | } |
871 | 817 | ||
872 | $data = array( | 818 | $data = array( |
819 | 'search_tags' => implode(' ', $filteringTags), | ||
873 | 'tags' => $tagList, | 820 | 'tags' => $tagList, |
874 | ); | 821 | ); |
875 | $pluginManager->executeHooks('render_tagcloud', $data, array('loggedin' => isLoggedIn())); | 822 | $pluginManager->executeHooks('render_tagcloud', $data, array('loggedin' => isLoggedIn())); |
@@ -878,7 +825,32 @@ function renderPage($conf, $pluginManager, $LINKSDB) | |||
878 | $PAGE->assign($key, $value); | 825 | $PAGE->assign($key, $value); |
879 | } | 826 | } |
880 | 827 | ||
881 | $PAGE->renderPage('tagcloud'); | 828 | $PAGE->renderPage('tag.cloud'); |
829 | exit; | ||
830 | } | ||
831 | |||
832 | // -------- Tag cloud | ||
833 | if ($targetPage == Router::$PAGE_TAGLIST) | ||
834 | { | ||
835 | $visibility = ! empty($_SESSION['privateonly']) ? 'private' : 'all'; | ||
836 | $filteringTags = isset($_GET['searchtags']) ? explode(' ', $_GET['searchtags']) : []; | ||
837 | $tags = $LINKSDB->linksCountPerTag($filteringTags, $visibility); | ||
838 | |||
839 | if (! empty($_GET['sort']) && $_GET['sort'] === 'alpha') { | ||
840 | alphabetical_sort($tags, false, true); | ||
841 | } | ||
842 | |||
843 | $data = [ | ||
844 | 'search_tags' => implode(' ', $filteringTags), | ||
845 | 'tags' => $tags, | ||
846 | ]; | ||
847 | $pluginManager->executeHooks('render_taglist', $data, ['loggedin' => isLoggedIn()]); | ||
848 | |||
849 | foreach ($data as $key => $value) { | ||
850 | $PAGE->assign($key, $value); | ||
851 | } | ||
852 | |||
853 | $PAGE->renderPage('tag.list'); | ||
882 | exit; | 854 | exit; |
883 | } | 855 | } |
884 | 856 | ||
@@ -1151,6 +1123,7 @@ function renderPage($conf, $pluginManager, $LINKSDB) | |||
1151 | $conf->set('api.secret', escape($_POST['apiSecret'])); | 1123 | $conf->set('api.secret', escape($_POST['apiSecret'])); |
1152 | try { | 1124 | try { |
1153 | $conf->write(isLoggedIn()); | 1125 | $conf->write(isLoggedIn()); |
1126 | $history->updateSettings(); | ||
1154 | invalidateCaches($conf->get('resource.page_cache')); | 1127 | invalidateCaches($conf->get('resource.page_cache')); |
1155 | } | 1128 | } |
1156 | catch(Exception $e) { | 1129 | catch(Exception $e) { |
@@ -1172,9 +1145,12 @@ function renderPage($conf, $pluginManager, $LINKSDB) | |||
1172 | $PAGE->assign('theme', $conf->get('resource.theme')); | 1145 | $PAGE->assign('theme', $conf->get('resource.theme')); |
1173 | $PAGE->assign('theme_available', ThemeUtils::getThemes($conf->get('resource.raintpl_tpl'))); | 1146 | $PAGE->assign('theme_available', ThemeUtils::getThemes($conf->get('resource.raintpl_tpl'))); |
1174 | $PAGE->assign('redirector', $conf->get('redirector.url')); | 1147 | $PAGE->assign('redirector', $conf->get('redirector.url')); |
1175 | list($timezone_form, $timezone_js) = generateTimeZoneForm($conf->get('general.timezone')); | 1148 | list($continents, $cities) = generateTimeZoneData( |
1176 | $PAGE->assign('timezone_form', $timezone_form); | 1149 | timezone_identifiers_list(), |
1177 | $PAGE->assign('timezone_js',$timezone_js); | 1150 | $conf->get('general.timezone') |
1151 | ); | ||
1152 | $PAGE->assign('continents', $continents); | ||
1153 | $PAGE->assign('cities', $cities); | ||
1178 | $PAGE->assign('private_links_default', $conf->get('privacy.default_private_links', false)); | 1154 | $PAGE->assign('private_links_default', $conf->get('privacy.default_private_links', false)); |
1179 | $PAGE->assign('session_protection_disabled', $conf->get('security.session_protection_disabled', false)); | 1155 | $PAGE->assign('session_protection_disabled', $conf->get('security.session_protection_disabled', false)); |
1180 | $PAGE->assign('enable_rss_permalinks', $conf->get('feed.rss_permalinks', false)); | 1156 | $PAGE->assign('enable_rss_permalinks', $conf->get('feed.rss_permalinks', false)); |
@@ -1191,7 +1167,7 @@ function renderPage($conf, $pluginManager, $LINKSDB) | |||
1191 | if ($targetPage == Router::$PAGE_CHANGETAG) | 1167 | if ($targetPage == Router::$PAGE_CHANGETAG) |
1192 | { | 1168 | { |
1193 | if (empty($_POST['fromtag']) || (empty($_POST['totag']) && isset($_POST['renametag']))) { | 1169 | if (empty($_POST['fromtag']) || (empty($_POST['totag']) && isset($_POST['renametag']))) { |
1194 | $PAGE->assign('tags', $LINKSDB->allTags()); | 1170 | $PAGE->assign('fromtag', ! empty($_GET['fromtag']) ? escape($_GET['fromtag']) : ''); |
1195 | $PAGE->renderPage('changetag'); | 1171 | $PAGE->renderPage('changetag'); |
1196 | exit; | 1172 | exit; |
1197 | } | 1173 | } |
@@ -1211,6 +1187,7 @@ function renderPage($conf, $pluginManager, $LINKSDB) | |||
1211 | unset($tags[array_search($needle,$tags)]); // Remove tag. | 1187 | unset($tags[array_search($needle,$tags)]); // Remove tag. |
1212 | $value['tags']=trim(implode(' ',$tags)); | 1188 | $value['tags']=trim(implode(' ',$tags)); |
1213 | $LINKSDB[$key]=$value; | 1189 | $LINKSDB[$key]=$value; |
1190 | $history->updateLink($LINKSDB[$key]); | ||
1214 | } | 1191 | } |
1215 | $LINKSDB->save($conf->get('resource.page_cache')); | 1192 | $LINKSDB->save($conf->get('resource.page_cache')); |
1216 | echo '<script>alert("Tag was removed from '.count($linksToAlter).' links.");document.location=\'?do=changetag\';</script>'; | 1193 | echo '<script>alert("Tag was removed from '.count($linksToAlter).' links.");document.location=\'?do=changetag\';</script>'; |
@@ -1228,6 +1205,7 @@ function renderPage($conf, $pluginManager, $LINKSDB) | |||
1228 | $tags[array_search($needle, $tags)] = trim($_POST['totag']); | 1205 | $tags[array_search($needle, $tags)] = trim($_POST['totag']); |
1229 | $value['tags'] = implode(' ', array_unique($tags)); | 1206 | $value['tags'] = implode(' ', array_unique($tags)); |
1230 | $LINKSDB[$key] = $value; | 1207 | $LINKSDB[$key] = $value; |
1208 | $history->updateLink($LINKSDB[$key]); | ||
1231 | } | 1209 | } |
1232 | $LINKSDB->save($conf->get('resource.page_cache')); // Save to disk. | 1210 | $LINKSDB->save($conf->get('resource.page_cache')); // Save to disk. |
1233 | echo '<script>alert("Tag was renamed in '.count($linksToAlter).' links.");document.location=\'?searchtags='.urlencode(escape($_POST['totag'])).'\';</script>'; | 1211 | echo '<script>alert("Tag was renamed in '.count($linksToAlter).' links.");document.location=\'?searchtags='.urlencode(escape($_POST['totag'])).'\';</script>'; |
@@ -1262,11 +1240,13 @@ function renderPage($conf, $pluginManager, $LINKSDB) | |||
1262 | $created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate); | 1240 | $created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate); |
1263 | $updated = new DateTime(); | 1241 | $updated = new DateTime(); |
1264 | $shortUrl = $LINKSDB[$id]['shorturl']; | 1242 | $shortUrl = $LINKSDB[$id]['shorturl']; |
1243 | $new = false; | ||
1265 | } else { | 1244 | } else { |
1266 | // New link | 1245 | // New link |
1267 | $created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate); | 1246 | $created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate); |
1268 | $updated = null; | 1247 | $updated = null; |
1269 | $shortUrl = link_small_hash($created, $id); | 1248 | $shortUrl = link_small_hash($created, $id); |
1249 | $new = true; | ||
1270 | } | 1250 | } |
1271 | 1251 | ||
1272 | // Remove multiple spaces. | 1252 | // Remove multiple spaces. |
@@ -1305,6 +1285,11 @@ function renderPage($conf, $pluginManager, $LINKSDB) | |||
1305 | 1285 | ||
1306 | $LINKSDB[$id] = $link; | 1286 | $LINKSDB[$id] = $link; |
1307 | $LINKSDB->save($conf->get('resource.page_cache')); | 1287 | $LINKSDB->save($conf->get('resource.page_cache')); |
1288 | if ($new) { | ||
1289 | $history->addLink($link); | ||
1290 | } else { | ||
1291 | $history->updateLink($link); | ||
1292 | } | ||
1308 | 1293 | ||
1309 | // If we are called from the bookmarklet, we must close the popup: | 1294 | // If we are called from the bookmarklet, we must close the popup: |
1310 | if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { | 1295 | if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { |
@@ -1342,19 +1327,23 @@ function renderPage($conf, $pluginManager, $LINKSDB) | |||
1342 | // -------- User clicked the "Delete" button when editing a link: Delete link from database. | 1327 | // -------- User clicked the "Delete" button when editing a link: Delete link from database. |
1343 | if ($targetPage == Router::$PAGE_DELETELINK) | 1328 | if ($targetPage == Router::$PAGE_DELETELINK) |
1344 | { | 1329 | { |
1345 | // We do not need to ask for confirmation: | ||
1346 | // - confirmation is handled by JavaScript | ||
1347 | // - we are protected from XSRF by the token. | ||
1348 | |||
1349 | if (! tokenOk($_GET['token'])) { | 1330 | if (! tokenOk($_GET['token'])) { |
1350 | die('Wrong token.'); | 1331 | die('Wrong token.'); |
1351 | } | 1332 | } |
1352 | 1333 | ||
1353 | $id = intval(escape($_GET['lf_linkdate'])); | 1334 | if (strpos($_GET['lf_linkdate'], ' ') !== false) { |
1354 | $link = $LINKSDB[$id]; | 1335 | $ids = array_values(array_filter(preg_split('/\s+/', escape($_GET['lf_linkdate'])))); |
1355 | $pluginManager->executeHooks('delete_link', $link); | 1336 | } else { |
1356 | unset($LINKSDB[$id]); | 1337 | $ids = [$_GET['lf_linkdate']]; |
1338 | } | ||
1339 | foreach ($ids as $id) { | ||
1340 | $id = (int) escape($id); | ||
1341 | $link = $LINKSDB[$id]; | ||
1342 | $pluginManager->executeHooks('delete_link', $link); | ||
1343 | unset($LINKSDB[$id]); | ||
1344 | } | ||
1357 | $LINKSDB->save($conf->get('resource.page_cache')); // save to disk | 1345 | $LINKSDB->save($conf->get('resource.page_cache')); // save to disk |
1346 | $history->deleteLink($link); | ||
1358 | 1347 | ||
1359 | // If we are called from the bookmarklet, we must close the popup: | 1348 | // If we are called from the bookmarklet, we must close the popup: |
1360 | if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { echo '<script>self.close();</script>'; exit; } | 1349 | if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { echo '<script>self.close();</script>'; exit; } |
@@ -1384,7 +1373,7 @@ function renderPage($conf, $pluginManager, $LINKSDB) | |||
1384 | 'link' => $link, | 1373 | 'link' => $link, |
1385 | 'link_is_new' => false, | 1374 | 'link_is_new' => false, |
1386 | 'http_referer' => (isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']) : ''), | 1375 | 'http_referer' => (isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']) : ''), |
1387 | 'tags' => $LINKSDB->allTags(), | 1376 | 'tags' => $LINKSDB->linksCountPerTag(), |
1388 | ); | 1377 | ); |
1389 | $pluginManager->executeHooks('render_editlink', $data); | 1378 | $pluginManager->executeHooks('render_editlink', $data); |
1390 | 1379 | ||
@@ -1453,7 +1442,7 @@ function renderPage($conf, $pluginManager, $LINKSDB) | |||
1453 | 'link_is_new' => $link_is_new, | 1442 | 'link_is_new' => $link_is_new, |
1454 | 'http_referer' => (isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']) : ''), | 1443 | 'http_referer' => (isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']) : ''), |
1455 | 'source' => (isset($_GET['source']) ? $_GET['source'] : ''), | 1444 | 'source' => (isset($_GET['source']) ? $_GET['source'] : ''), |
1456 | 'tags' => $LINKSDB->allTags(), | 1445 | 'tags' => $LINKSDB->linksCountPerTag(), |
1457 | 'default_private_links' => $conf->get('privacy.default_private_links', false), | 1446 | 'default_private_links' => $conf->get('privacy.default_private_links', false), |
1458 | ); | 1447 | ); |
1459 | $pluginManager->executeHooks('render_editlink', $data); | 1448 | $pluginManager->executeHooks('render_editlink', $data); |
@@ -1515,7 +1504,22 @@ function renderPage($conf, $pluginManager, $LINKSDB) | |||
1515 | 1504 | ||
1516 | if (! isset($_POST['token']) || ! isset($_FILES['filetoupload'])) { | 1505 | if (! isset($_POST['token']) || ! isset($_FILES['filetoupload'])) { |
1517 | // Show import dialog | 1506 | // Show import dialog |
1518 | $PAGE->assign('maxfilesize', getMaxFileSize()); | 1507 | $PAGE->assign( |
1508 | 'maxfilesize', | ||
1509 | get_max_upload_size( | ||
1510 | ini_get('post_max_size'), | ||
1511 | ini_get('upload_max_filesize'), | ||
1512 | false | ||
1513 | ) | ||
1514 | ); | ||
1515 | $PAGE->assign( | ||
1516 | 'maxfilesizeHuman', | ||
1517 | get_max_upload_size( | ||
1518 | ini_get('post_max_size'), | ||
1519 | ini_get('upload_max_filesize'), | ||
1520 | true | ||
1521 | ) | ||
1522 | ); | ||
1519 | $PAGE->renderPage('import'); | 1523 | $PAGE->renderPage('import'); |
1520 | exit; | 1524 | exit; |
1521 | } | 1525 | } |
@@ -1525,7 +1529,7 @@ function renderPage($conf, $pluginManager, $LINKSDB) | |||
1525 | // The file is too big or some form field may be missing. | 1529 | // The file is too big or some form field may be missing. |
1526 | echo '<script>alert("The file you are trying to upload is probably' | 1530 | echo '<script>alert("The file you are trying to upload is probably' |
1527 | .' bigger than what this webserver can accept (' | 1531 | .' bigger than what this webserver can accept (' |
1528 | .getMaxFileSize().' bytes).' | 1532 | .get_max_upload_size(ini_get('post_max_size'), ini_get('upload_max_filesize')).').' |
1529 | .' Please upload in smaller chunks.");document.location=\'?do=' | 1533 | .' Please upload in smaller chunks.");document.location=\'?do=' |
1530 | .Router::$PAGE_IMPORT .'\';</script>'; | 1534 | .Router::$PAGE_IMPORT .'\';</script>'; |
1531 | exit; | 1535 | exit; |
@@ -1537,7 +1541,8 @@ function renderPage($conf, $pluginManager, $LINKSDB) | |||
1537 | $_POST, | 1541 | $_POST, |
1538 | $_FILES, | 1542 | $_FILES, |
1539 | $LINKSDB, | 1543 | $LINKSDB, |
1540 | $conf | 1544 | $conf, |
1545 | $history | ||
1541 | ); | 1546 | ); |
1542 | echo '<script>alert("'.$status.'");document.location=\'?do=' | 1547 | echo '<script>alert("'.$status.'");document.location=\'?do=' |
1543 | .Router::$PAGE_IMPORT .'\';</script>'; | 1548 | .Router::$PAGE_IMPORT .'\';</script>'; |
@@ -1577,6 +1582,7 @@ function renderPage($conf, $pluginManager, $LINKSDB) | |||
1577 | $conf->set('general.enabled_plugins', save_plugin_config($_POST)); | 1582 | $conf->set('general.enabled_plugins', save_plugin_config($_POST)); |
1578 | } | 1583 | } |
1579 | $conf->write(isLoggedIn()); | 1584 | $conf->write(isLoggedIn()); |
1585 | $history->updateSettings(); | ||
1580 | } | 1586 | } |
1581 | catch (Exception $e) { | 1587 | catch (Exception $e) { |
1582 | error_log( | 1588 | error_log( |
@@ -1592,6 +1598,13 @@ function renderPage($conf, $pluginManager, $LINKSDB) | |||
1592 | exit; | 1598 | exit; |
1593 | } | 1599 | } |
1594 | 1600 | ||
1601 | // Get a fresh token | ||
1602 | if ($targetPage == Router::$GET_TOKEN) { | ||
1603 | header('Content-Type:text/plain'); | ||
1604 | echo getToken($conf); | ||
1605 | exit; | ||
1606 | } | ||
1607 | |||
1595 | // -------- Otherwise, simply display search form and links: | 1608 | // -------- Otherwise, simply display search form and links: |
1596 | showLinkList($PAGE, $LINKSDB, $conf, $pluginManager); | 1609 | showLinkList($PAGE, $LINKSDB, $conf, $pluginManager); |
1597 | exit; | 1610 | exit; |
@@ -1694,7 +1707,6 @@ function buildLinkList($PAGE,$LINKSDB, $conf, $pluginManager) | |||
1694 | 'visibility' => ! empty($_SESSION['privateonly']) ? 'private' : '', | 1707 | 'visibility' => ! empty($_SESSION['privateonly']) ? 'private' : '', |
1695 | 'redirector' => $conf->get('redirector.url'), // Optional redirector URL. | 1708 | 'redirector' => $conf->get('redirector.url'), // Optional redirector URL. |
1696 | 'links' => $linkDisp, | 1709 | 'links' => $linkDisp, |
1697 | 'tags' => $LINKSDB->allTags(), | ||
1698 | ); | 1710 | ); |
1699 | 1711 | ||
1700 | // If there is only a single link, we change on-the-fly the title of the page. | 1712 | // If there is only a single link, we change on-the-fly the title of the page. |
@@ -1980,16 +1992,10 @@ function install($conf) | |||
1980 | exit; | 1992 | exit; |
1981 | } | 1993 | } |
1982 | 1994 | ||
1983 | // Display config form: | ||
1984 | list($timezone_form, $timezone_js) = generateTimeZoneForm(); | ||
1985 | $timezone_html = ''; | ||
1986 | if ($timezone_form != '') { | ||
1987 | $timezone_html = '<tr><td><b>Timezone:</b></td><td>'.$timezone_form.'</td></tr>'; | ||
1988 | } | ||
1989 | |||
1990 | $PAGE = new PageBuilder($conf); | 1995 | $PAGE = new PageBuilder($conf); |
1991 | $PAGE->assign('timezone_html',$timezone_html); | 1996 | list($continents, $cities) = generateTimeZoneData(timezone_identifiers_list(), date_default_timezone_get()); |
1992 | $PAGE->assign('timezone_js',$timezone_js); | 1997 | $PAGE->assign('continents', $continents); |
1998 | $PAGE->assign('cities', $cities); | ||
1993 | $PAGE->renderPage('install'); | 1999 | $PAGE->renderPage('install'); |
1994 | exit; | 2000 | exit; |
1995 | } | 2001 | } |
@@ -2233,16 +2239,27 @@ $linkDb = new LinkDB( | |||
2233 | $conf->get('redirector.encode_url') | 2239 | $conf->get('redirector.encode_url') |
2234 | ); | 2240 | ); |
2235 | 2241 | ||
2242 | try { | ||
2243 | $history = new History($conf->get('resource.history')); | ||
2244 | } catch(Exception $e) { | ||
2245 | die($e->getMessage()); | ||
2246 | } | ||
2247 | |||
2236 | $container = new \Slim\Container(); | 2248 | $container = new \Slim\Container(); |
2237 | $container['conf'] = $conf; | 2249 | $container['conf'] = $conf; |
2238 | $container['plugins'] = $pluginManager; | 2250 | $container['plugins'] = $pluginManager; |
2251 | $container['history'] = $history; | ||
2239 | $app = new \Slim\App($container); | 2252 | $app = new \Slim\App($container); |
2240 | 2253 | ||
2241 | // REST API routes | 2254 | // REST API routes |
2242 | $app->group('/api/v1', function() { | 2255 | $app->group('/api/v1', function() { |
2243 | $this->get('/info', '\Shaarli\Api\Controllers\Info:getInfo'); | 2256 | $this->get('/info', '\Shaarli\Api\Controllers\Info:getInfo')->setName('getInfo'); |
2244 | $this->get('/links', '\Shaarli\Api\Controllers\Links:getLinks'); | 2257 | $this->get('/links', '\Shaarli\Api\Controllers\Links:getLinks')->setName('getLinks'); |
2245 | $this->get('/links/{id:[\d]+}', '\Shaarli\Api\Controllers\Links:getLink'); | 2258 | $this->get('/links/{id:[\d]+}', '\Shaarli\Api\Controllers\Links:getLink')->setName('getLink'); |
2259 | $this->post('/links', '\Shaarli\Api\Controllers\Links:postLink')->setName('postLink'); | ||
2260 | $this->put('/links/{id:[\d]+}', '\Shaarli\Api\Controllers\Links:putLink')->setName('putLink'); | ||
2261 | $this->delete('/links/{id:[\d]+}', '\Shaarli\Api\Controllers\Links:deleteLink')->setName('deleteLink'); | ||
2262 | $this->get('/history', '\Shaarli\Api\Controllers\History:getHistory')->setName('getHistory'); | ||
2246 | })->add('\Shaarli\Api\ApiMiddleware'); | 2263 | })->add('\Shaarli\Api\ApiMiddleware'); |
2247 | 2264 | ||
2248 | $response = $app->run(true); | 2265 | $response = $app->run(true); |
@@ -2251,7 +2268,7 @@ $response = $app->run(true); | |||
2251 | if ($response->getStatusCode() == 404 && strpos($_SERVER['REQUEST_URI'], '/api/v1') === false) { | 2268 | if ($response->getStatusCode() == 404 && strpos($_SERVER['REQUEST_URI'], '/api/v1') === false) { |
2252 | // We use UTF-8 for proper international characters handling. | 2269 | // We use UTF-8 for proper international characters handling. |
2253 | header('Content-Type: text/html; charset=utf-8'); | 2270 | header('Content-Type: text/html; charset=utf-8'); |
2254 | renderPage($conf, $pluginManager, $linkDb); | 2271 | renderPage($conf, $pluginManager, $linkDb, $history); |
2255 | } else { | 2272 | } else { |
2256 | $app->respond($response); | 2273 | $app->respond($response); |
2257 | } | 2274 | } |
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 @@ | |||
1 | description="For each link, add a ReadItYourself icon to save the shaared URL." | ||
2 | parameters=READITYOUSELF_URL; \ No newline at end of file | ||
diff --git a/plugins/readityourself/readityourself.php b/plugins/readityourself/readityourself.php deleted file mode 100644 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 | */ | ||
18 | function 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 | */ | ||
36 | function 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/tests/FileUtilsTest.php b/tests/FileUtilsTest.php new file mode 100644 index 00000000..d764e495 --- /dev/null +++ b/tests/FileUtilsTest.php | |||
@@ -0,0 +1,108 @@ | |||
1 | <?php | ||
2 | |||
3 | require_once 'application/FileUtils.php'; | ||
4 | |||
5 | /** | ||
6 | * Class FileUtilsTest | ||
7 | * | ||
8 | * Test file utility class. | ||
9 | */ | ||
10 | class FileUtilsTest extends PHPUnit_Framework_TestCase | ||
11 | { | ||
12 | /** | ||
13 | * @var string Test file path. | ||
14 | */ | ||
15 | protected static $file = 'sandbox/flat.db'; | ||
16 | |||
17 | /** | ||
18 | * Delete test file after every test. | ||
19 | */ | ||
20 | public function tearDown() | ||
21 | { | ||
22 | @unlink(self::$file); | ||
23 | } | ||
24 | |||
25 | /** | ||
26 | * Test writeDB, then readDB with different data. | ||
27 | */ | ||
28 | public function testSimpleWriteRead() | ||
29 | { | ||
30 | $data = ['blue', 'red']; | ||
31 | $this->assertTrue(FileUtils::writeFlatDB(self::$file, $data) > 0); | ||
32 | $this->assertTrue(startsWith(file_get_contents(self::$file), '<?php /*')); | ||
33 | $this->assertEquals($data, FileUtils::readFlatDB(self::$file)); | ||
34 | |||
35 | $data = 0; | ||
36 | $this->assertTrue(FileUtils::writeFlatDB(self::$file, $data) > 0); | ||
37 | $this->assertEquals($data, FileUtils::readFlatDB(self::$file)); | ||
38 | |||
39 | $data = null; | ||
40 | $this->assertTrue(FileUtils::writeFlatDB(self::$file, $data) > 0); | ||
41 | $this->assertEquals($data, FileUtils::readFlatDB(self::$file)); | ||
42 | |||
43 | $data = false; | ||
44 | $this->assertTrue(FileUtils::writeFlatDB(self::$file, $data) > 0); | ||
45 | $this->assertEquals($data, FileUtils::readFlatDB(self::$file)); | ||
46 | } | ||
47 | |||
48 | /** | ||
49 | * File not writable: raise an exception. | ||
50 | * | ||
51 | * @expectedException IOException | ||
52 | * @expectedExceptionMessage Error accessing "sandbox/flat.db" | ||
53 | */ | ||
54 | public function testWriteWithoutPermission() | ||
55 | { | ||
56 | touch(self::$file); | ||
57 | chmod(self::$file, 0440); | ||
58 | FileUtils::writeFlatDB(self::$file, null); | ||
59 | } | ||
60 | |||
61 | /** | ||
62 | * Folder non existent: raise an exception. | ||
63 | * | ||
64 | * @expectedException IOException | ||
65 | * @expectedExceptionMessage Error accessing "nopefolder" | ||
66 | */ | ||
67 | public function testWriteFolderDoesNotExist() | ||
68 | { | ||
69 | FileUtils::writeFlatDB('nopefolder/file', null); | ||
70 | } | ||
71 | |||
72 | /** | ||
73 | * Folder non writable: raise an exception. | ||
74 | * | ||
75 | * @expectedException IOException | ||
76 | * @expectedExceptionMessage Error accessing "sandbox" | ||
77 | */ | ||
78 | public function testWriteFolderPermission() | ||
79 | { | ||
80 | chmod(dirname(self::$file), 0555); | ||
81 | try { | ||
82 | FileUtils::writeFlatDB(self::$file, null); | ||
83 | } catch (Exception $e) { | ||
84 | chmod(dirname(self::$file), 0755); | ||
85 | throw $e; | ||
86 | } | ||
87 | } | ||
88 | |||
89 | /** | ||
90 | * Read non existent file, use default parameter. | ||
91 | */ | ||
92 | public function testReadNotExistentFile() | ||
93 | { | ||
94 | $this->assertEquals(null, FileUtils::readFlatDB(self::$file)); | ||
95 | $this->assertEquals(['test'], FileUtils::readFlatDB(self::$file, ['test'])); | ||
96 | } | ||
97 | |||
98 | /** | ||
99 | * Read non readable file, use default parameter. | ||
100 | */ | ||
101 | public function testReadNotReadable() | ||
102 | { | ||
103 | touch(self::$file); | ||
104 | chmod(self::$file, 0220); | ||
105 | $this->assertEquals(null, FileUtils::readFlatDB(self::$file)); | ||
106 | $this->assertEquals(['test'], FileUtils::readFlatDB(self::$file, ['test'])); | ||
107 | } | ||
108 | } | ||
diff --git a/tests/HistoryTest.php b/tests/HistoryTest.php new file mode 100644 index 00000000..d3bef5a3 --- /dev/null +++ b/tests/HistoryTest.php | |||
@@ -0,0 +1,207 @@ | |||
1 | <?php | ||
2 | |||
3 | require_once 'application/History.php'; | ||
4 | |||
5 | |||
6 | class HistoryTest extends PHPUnit_Framework_TestCase | ||
7 | { | ||
8 | /** | ||
9 | * @var string History file path | ||
10 | */ | ||
11 | protected static $historyFilePath = 'sandbox/history.php'; | ||
12 | |||
13 | /** | ||
14 | * Delete history file. | ||
15 | */ | ||
16 | public function tearDown() | ||
17 | { | ||
18 | @unlink(self::$historyFilePath); | ||
19 | } | ||
20 | |||
21 | /** | ||
22 | * Test that the history file is created if it doesn't exist. | ||
23 | */ | ||
24 | public function testConstructLazyLoading() | ||
25 | { | ||
26 | new History(self::$historyFilePath); | ||
27 | $this->assertFileNotExists(self::$historyFilePath); | ||
28 | } | ||
29 | |||
30 | /** | ||
31 | * Test that the history file is created if it doesn't exist. | ||
32 | */ | ||
33 | public function testAddEventCreateFile() | ||
34 | { | ||
35 | $history = new History(self::$historyFilePath); | ||
36 | $history->updateSettings(); | ||
37 | $this->assertFileExists(self::$historyFilePath); | ||
38 | } | ||
39 | |||
40 | /** | ||
41 | * Not writable history file: raise an exception. | ||
42 | * | ||
43 | * @expectedException Exception | ||
44 | * @expectedExceptionMessage History file isn't readable or writable | ||
45 | */ | ||
46 | public function testConstructNotWritable() | ||
47 | { | ||
48 | touch(self::$historyFilePath); | ||
49 | chmod(self::$historyFilePath, 0440); | ||
50 | $history = new History(self::$historyFilePath); | ||
51 | $history->updateSettings(); | ||
52 | } | ||
53 | |||
54 | /** | ||
55 | * Not parsable history file: raise an exception. | ||
56 | * | ||
57 | * @expectedException Exception | ||
58 | * @expectedExceptionMessageRegExp /Could not parse history file/ | ||
59 | */ | ||
60 | public function testConstructNotParsable() | ||
61 | { | ||
62 | file_put_contents(self::$historyFilePath, 'not parsable'); | ||
63 | $history = new History(self::$historyFilePath); | ||
64 | // gzinflate generates a warning | ||
65 | @$history->updateSettings(); | ||
66 | } | ||
67 | |||
68 | /** | ||
69 | * Test add link event | ||
70 | */ | ||
71 | public function testAddLink() | ||
72 | { | ||
73 | $history = new History(self::$historyFilePath); | ||
74 | $history->addLink(['id' => 0]); | ||
75 | $actual = $history->getHistory()[0]; | ||
76 | $this->assertEquals(History::CREATED, $actual['event']); | ||
77 | $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']); | ||
78 | $this->assertEquals(0, $actual['id']); | ||
79 | |||
80 | $history = new History(self::$historyFilePath); | ||
81 | $history->addLink(['id' => 1]); | ||
82 | $actual = $history->getHistory()[0]; | ||
83 | $this->assertEquals(History::CREATED, $actual['event']); | ||
84 | $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']); | ||
85 | $this->assertEquals(1, $actual['id']); | ||
86 | |||
87 | $history = new History(self::$historyFilePath); | ||
88 | $history->addLink(['id' => 'str']); | ||
89 | $actual = $history->getHistory()[0]; | ||
90 | $this->assertEquals(History::CREATED, $actual['event']); | ||
91 | $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']); | ||
92 | $this->assertEquals('str', $actual['id']); | ||
93 | } | ||
94 | |||
95 | /** | ||
96 | * Test updated link event | ||
97 | */ | ||
98 | public function testUpdateLink() | ||
99 | { | ||
100 | $history = new History(self::$historyFilePath); | ||
101 | $history->updateLink(['id' => 1]); | ||
102 | $actual = $history->getHistory()[0]; | ||
103 | $this->assertEquals(History::UPDATED, $actual['event']); | ||
104 | $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']); | ||
105 | $this->assertEquals(1, $actual['id']); | ||
106 | } | ||
107 | |||
108 | /** | ||
109 | * Test delete link event | ||
110 | */ | ||
111 | public function testDeleteLink() | ||
112 | { | ||
113 | $history = new History(self::$historyFilePath); | ||
114 | $history->deleteLink(['id' => 1]); | ||
115 | $actual = $history->getHistory()[0]; | ||
116 | $this->assertEquals(History::DELETED, $actual['event']); | ||
117 | $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']); | ||
118 | $this->assertEquals(1, $actual['id']); | ||
119 | } | ||
120 | |||
121 | /** | ||
122 | * Test updated settings event | ||
123 | */ | ||
124 | public function testUpdateSettings() | ||
125 | { | ||
126 | $history = new History(self::$historyFilePath); | ||
127 | $history->updateSettings(); | ||
128 | $actual = $history->getHistory()[0]; | ||
129 | $this->assertEquals(History::SETTINGS, $actual['event']); | ||
130 | $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']); | ||
131 | $this->assertEmpty($actual['id']); | ||
132 | } | ||
133 | |||
134 | /** | ||
135 | * Make sure that new items are stored at the beginning | ||
136 | */ | ||
137 | public function testHistoryOrder() | ||
138 | { | ||
139 | $history = new History(self::$historyFilePath); | ||
140 | $history->updateLink(['id' => 1]); | ||
141 | $actual = $history->getHistory()[0]; | ||
142 | $this->assertEquals(History::UPDATED, $actual['event']); | ||
143 | $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']); | ||
144 | $this->assertEquals(1, $actual['id']); | ||
145 | |||
146 | $history->addLink(['id' => 1]); | ||
147 | $actual = $history->getHistory()[0]; | ||
148 | $this->assertEquals(History::CREATED, $actual['event']); | ||
149 | $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']); | ||
150 | $this->assertEquals(1, $actual['id']); | ||
151 | } | ||
152 | |||
153 | /** | ||
154 | * Re-read history from file after writing an event | ||
155 | */ | ||
156 | public function testHistoryRead() | ||
157 | { | ||
158 | $history = new History(self::$historyFilePath); | ||
159 | $history->updateLink(['id' => 1]); | ||
160 | $history = new History(self::$historyFilePath); | ||
161 | $actual = $history->getHistory()[0]; | ||
162 | $this->assertEquals(History::UPDATED, $actual['event']); | ||
163 | $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']); | ||
164 | $this->assertEquals(1, $actual['id']); | ||
165 | } | ||
166 | |||
167 | /** | ||
168 | * Re-read history from file after writing an event and make sure that the order is correct | ||
169 | */ | ||
170 | public function testHistoryOrderRead() | ||
171 | { | ||
172 | $history = new History(self::$historyFilePath); | ||
173 | $history->updateLink(['id' => 1]); | ||
174 | $history->addLink(['id' => 1]); | ||
175 | |||
176 | $history = new History(self::$historyFilePath); | ||
177 | $actual = $history->getHistory()[0]; | ||
178 | $this->assertEquals(History::CREATED, $actual['event']); | ||
179 | $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']); | ||
180 | $this->assertEquals(1, $actual['id']); | ||
181 | |||
182 | $actual = $history->getHistory()[1]; | ||
183 | $this->assertEquals(History::UPDATED, $actual['event']); | ||
184 | $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']); | ||
185 | $this->assertEquals(1, $actual['id']); | ||
186 | } | ||
187 | |||
188 | /** | ||
189 | * Test retention time: delete old entries. | ||
190 | */ | ||
191 | public function testHistoryRententionTime() | ||
192 | { | ||
193 | $history = new History(self::$historyFilePath, 5); | ||
194 | $history->updateLink(['id' => 1]); | ||
195 | $this->assertEquals(1, count($history->getHistory())); | ||
196 | $arr = $history->getHistory(); | ||
197 | $arr[0]['datetime'] = new DateTime('-1 hour'); | ||
198 | FileUtils::writeFlatDB(self::$historyFilePath, $arr); | ||
199 | |||
200 | $history = new History(self::$historyFilePath, 60); | ||
201 | $this->assertEquals(1, count($history->getHistory())); | ||
202 | $this->assertEquals(1, $history->getHistory()[0]['id']); | ||
203 | $history->updateLink(['id' => 2]); | ||
204 | $this->assertEquals(1, count($history->getHistory())); | ||
205 | $this->assertEquals(2, $history->getHistory()[0]['id']); | ||
206 | } | ||
207 | } | ||
diff --git a/tests/LinkDBTest.php b/tests/LinkDBTest.php index 1f62a34a..2523467d 100644 --- a/tests/LinkDBTest.php +++ b/tests/LinkDBTest.php | |||
@@ -101,7 +101,7 @@ class LinkDBTest extends PHPUnit_Framework_TestCase | |||
101 | * Attempt to instantiate a LinkDB whereas the datastore is not writable | 101 | * Attempt to instantiate a LinkDB whereas the datastore is not writable |
102 | * | 102 | * |
103 | * @expectedException IOException | 103 | * @expectedException IOException |
104 | * @expectedExceptionMessageRegExp /Error accessing\nnull/ | 104 | * @expectedExceptionMessageRegExp /Error accessing "null"/ |
105 | */ | 105 | */ |
106 | public function testConstructDatastoreNotWriteable() | 106 | public function testConstructDatastoreNotWriteable() |
107 | { | 107 | { |
@@ -297,7 +297,7 @@ class LinkDBTest extends PHPUnit_Framework_TestCase | |||
297 | 'sTuff' => 2, | 297 | 'sTuff' => 2, |
298 | 'ut' => 1, | 298 | 'ut' => 1, |
299 | ), | 299 | ), |
300 | self::$publicLinkDB->allTags() | 300 | self::$publicLinkDB->linksCountPerTag() |
301 | ); | 301 | ); |
302 | 302 | ||
303 | $this->assertEquals( | 303 | $this->assertEquals( |
@@ -325,7 +325,34 @@ class LinkDBTest extends PHPUnit_Framework_TestCase | |||
325 | 'tag4' => 1, | 325 | 'tag4' => 1, |
326 | 'ut' => 1, | 326 | 'ut' => 1, |
327 | ), | 327 | ), |
328 | self::$privateLinkDB->allTags() | 328 | self::$privateLinkDB->linksCountPerTag() |
329 | ); | ||
330 | $this->assertEquals( | ||
331 | array( | ||
332 | 'web' => 4, | ||
333 | 'cartoon' => 2, | ||
334 | 'gnu' => 1, | ||
335 | 'dev' => 1, | ||
336 | 'samba' => 1, | ||
337 | 'media' => 1, | ||
338 | 'html' => 1, | ||
339 | 'w3c' => 1, | ||
340 | 'css' => 1, | ||
341 | 'Mercurial' => 1, | ||
342 | '.hidden' => 1, | ||
343 | 'hashtag' => 1, | ||
344 | ), | ||
345 | self::$privateLinkDB->linksCountPerTag(['web']) | ||
346 | ); | ||
347 | $this->assertEquals( | ||
348 | array( | ||
349 | 'web' => 1, | ||
350 | 'html' => 1, | ||
351 | 'w3c' => 1, | ||
352 | 'css' => 1, | ||
353 | 'Mercurial' => 1, | ||
354 | ), | ||
355 | self::$privateLinkDB->linksCountPerTag(['web'], 'private') | ||
329 | ); | 356 | ); |
330 | } | 357 | } |
331 | 358 | ||
diff --git a/tests/NetscapeBookmarkUtils/BookmarkImportTest.php b/tests/NetscapeBookmarkUtils/BookmarkImportTest.php index 5925a8e1..5fc1d1e8 100644 --- a/tests/NetscapeBookmarkUtils/BookmarkImportTest.php +++ b/tests/NetscapeBookmarkUtils/BookmarkImportTest.php | |||
@@ -34,6 +34,11 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
34 | protected static $testDatastore = 'sandbox/datastore.php'; | 34 | protected static $testDatastore = 'sandbox/datastore.php'; |
35 | 35 | ||
36 | /** | 36 | /** |
37 | * @var string History file path | ||
38 | */ | ||
39 | protected static $historyFilePath = 'sandbox/history.php'; | ||
40 | |||
41 | /** | ||
37 | * @var LinkDB private LinkDB instance | 42 | * @var LinkDB private LinkDB instance |
38 | */ | 43 | */ |
39 | protected $linkDb = null; | 44 | protected $linkDb = null; |
@@ -49,6 +54,11 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
49 | protected $conf; | 54 | protected $conf; |
50 | 55 | ||
51 | /** | 56 | /** |
57 | * @var History instance. | ||
58 | */ | ||
59 | protected $history; | ||
60 | |||
61 | /** | ||
52 | * @var string Save the current timezone. | 62 | * @var string Save the current timezone. |
53 | */ | 63 | */ |
54 | protected static $defaultTimeZone; | 64 | protected static $defaultTimeZone; |
@@ -73,6 +83,15 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
73 | $this->linkDb = new LinkDB(self::$testDatastore, true, false); | 83 | $this->linkDb = new LinkDB(self::$testDatastore, true, false); |
74 | $this->conf = new ConfigManager('tests/utils/config/configJson'); | 84 | $this->conf = new ConfigManager('tests/utils/config/configJson'); |
75 | $this->conf->set('resource.page_cache', $this->pagecache); | 85 | $this->conf->set('resource.page_cache', $this->pagecache); |
86 | $this->history = new History(self::$historyFilePath); | ||
87 | } | ||
88 | |||
89 | /** | ||
90 | * Delete history file. | ||
91 | */ | ||
92 | public function tearDown() | ||
93 | { | ||
94 | @unlink(self::$historyFilePath); | ||
76 | } | 95 | } |
77 | 96 | ||
78 | public static function tearDownAfterClass() | 97 | public static function tearDownAfterClass() |
@@ -89,7 +108,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
89 | $this->assertEquals( | 108 | $this->assertEquals( |
90 | 'File empty.htm (0 bytes) has an unknown file format.' | 109 | 'File empty.htm (0 bytes) has an unknown file format.' |
91 | .' Nothing was imported.', | 110 | .' Nothing was imported.', |
92 | NetscapeBookmarkUtils::import(NULL, $files, NULL, $this->conf) | 111 | NetscapeBookmarkUtils::import(null, $files, null, $this->conf, $this->history) |
93 | ); | 112 | ); |
94 | $this->assertEquals(0, count($this->linkDb)); | 113 | $this->assertEquals(0, count($this->linkDb)); |
95 | } | 114 | } |
@@ -102,7 +121,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
102 | $files = file2array('no_doctype.htm'); | 121 | $files = file2array('no_doctype.htm'); |
103 | $this->assertEquals( | 122 | $this->assertEquals( |
104 | 'File no_doctype.htm (350 bytes) has an unknown file format. Nothing was imported.', | 123 | 'File no_doctype.htm (350 bytes) has an unknown file format. Nothing was imported.', |
105 | NetscapeBookmarkUtils::import(NULL, $files, NULL, $this->conf) | 124 | NetscapeBookmarkUtils::import(null, $files, null, $this->conf, $this->history) |
106 | ); | 125 | ); |
107 | $this->assertEquals(0, count($this->linkDb)); | 126 | $this->assertEquals(0, count($this->linkDb)); |
108 | } | 127 | } |
@@ -116,7 +135,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
116 | $this->assertEquals( | 135 | $this->assertEquals( |
117 | 'File internet_explorer_encoding.htm (356 bytes) was successfully processed:' | 136 | 'File internet_explorer_encoding.htm (356 bytes) was successfully processed:' |
118 | .' 1 links imported, 0 links overwritten, 0 links skipped.', | 137 | .' 1 links imported, 0 links overwritten, 0 links skipped.', |
119 | NetscapeBookmarkUtils::import(array(), $files, $this->linkDb, $this->conf) | 138 | NetscapeBookmarkUtils::import([], $files, $this->linkDb, $this->conf, $this->history) |
120 | ); | 139 | ); |
121 | $this->assertEquals(1, count($this->linkDb)); | 140 | $this->assertEquals(1, count($this->linkDb)); |
122 | $this->assertEquals(0, count_private($this->linkDb)); | 141 | $this->assertEquals(0, count_private($this->linkDb)); |
@@ -145,7 +164,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
145 | $this->assertEquals( | 164 | $this->assertEquals( |
146 | 'File netscape_nested.htm (1337 bytes) was successfully processed:' | 165 | 'File netscape_nested.htm (1337 bytes) was successfully processed:' |
147 | .' 8 links imported, 0 links overwritten, 0 links skipped.', | 166 | .' 8 links imported, 0 links overwritten, 0 links skipped.', |
148 | NetscapeBookmarkUtils::import(array(), $files, $this->linkDb, $this->conf) | 167 | NetscapeBookmarkUtils::import([], $files, $this->linkDb, $this->conf, $this->history) |
149 | ); | 168 | ); |
150 | $this->assertEquals(8, count($this->linkDb)); | 169 | $this->assertEquals(8, count($this->linkDb)); |
151 | $this->assertEquals(2, count_private($this->linkDb)); | 170 | $this->assertEquals(2, count_private($this->linkDb)); |
@@ -267,7 +286,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
267 | $this->assertEquals( | 286 | $this->assertEquals( |
268 | 'File netscape_basic.htm (482 bytes) was successfully processed:' | 287 | 'File netscape_basic.htm (482 bytes) was successfully processed:' |
269 | .' 2 links imported, 0 links overwritten, 0 links skipped.', | 288 | .' 2 links imported, 0 links overwritten, 0 links skipped.', |
270 | NetscapeBookmarkUtils::import(array(), $files, $this->linkDb, $this->conf) | 289 | NetscapeBookmarkUtils::import([], $files, $this->linkDb, $this->conf, $this->history) |
271 | ); | 290 | ); |
272 | 291 | ||
273 | $this->assertEquals(2, count($this->linkDb)); | 292 | $this->assertEquals(2, count($this->linkDb)); |
@@ -312,7 +331,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
312 | $this->assertEquals( | 331 | $this->assertEquals( |
313 | 'File netscape_basic.htm (482 bytes) was successfully processed:' | 332 | 'File netscape_basic.htm (482 bytes) was successfully processed:' |
314 | .' 2 links imported, 0 links overwritten, 0 links skipped.', | 333 | .' 2 links imported, 0 links overwritten, 0 links skipped.', |
315 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf) | 334 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) |
316 | ); | 335 | ); |
317 | $this->assertEquals(2, count($this->linkDb)); | 336 | $this->assertEquals(2, count($this->linkDb)); |
318 | $this->assertEquals(1, count_private($this->linkDb)); | 337 | $this->assertEquals(1, count_private($this->linkDb)); |
@@ -356,7 +375,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
356 | $this->assertEquals( | 375 | $this->assertEquals( |
357 | 'File netscape_basic.htm (482 bytes) was successfully processed:' | 376 | 'File netscape_basic.htm (482 bytes) was successfully processed:' |
358 | .' 2 links imported, 0 links overwritten, 0 links skipped.', | 377 | .' 2 links imported, 0 links overwritten, 0 links skipped.', |
359 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf) | 378 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) |
360 | ); | 379 | ); |
361 | $this->assertEquals(2, count($this->linkDb)); | 380 | $this->assertEquals(2, count($this->linkDb)); |
362 | $this->assertEquals(0, count_private($this->linkDb)); | 381 | $this->assertEquals(0, count_private($this->linkDb)); |
@@ -380,7 +399,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
380 | $this->assertEquals( | 399 | $this->assertEquals( |
381 | 'File netscape_basic.htm (482 bytes) was successfully processed:' | 400 | 'File netscape_basic.htm (482 bytes) was successfully processed:' |
382 | .' 2 links imported, 0 links overwritten, 0 links skipped.', | 401 | .' 2 links imported, 0 links overwritten, 0 links skipped.', |
383 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf) | 402 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) |
384 | ); | 403 | ); |
385 | $this->assertEquals(2, count($this->linkDb)); | 404 | $this->assertEquals(2, count($this->linkDb)); |
386 | $this->assertEquals(2, count_private($this->linkDb)); | 405 | $this->assertEquals(2, count_private($this->linkDb)); |
@@ -406,7 +425,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
406 | $this->assertEquals( | 425 | $this->assertEquals( |
407 | 'File netscape_basic.htm (482 bytes) was successfully processed:' | 426 | 'File netscape_basic.htm (482 bytes) was successfully processed:' |
408 | .' 2 links imported, 0 links overwritten, 0 links skipped.', | 427 | .' 2 links imported, 0 links overwritten, 0 links skipped.', |
409 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf) | 428 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) |
410 | ); | 429 | ); |
411 | $this->assertEquals(2, count($this->linkDb)); | 430 | $this->assertEquals(2, count($this->linkDb)); |
412 | $this->assertEquals(2, count_private($this->linkDb)); | 431 | $this->assertEquals(2, count_private($this->linkDb)); |
@@ -426,7 +445,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
426 | $this->assertEquals( | 445 | $this->assertEquals( |
427 | 'File netscape_basic.htm (482 bytes) was successfully processed:' | 446 | 'File netscape_basic.htm (482 bytes) was successfully processed:' |
428 | .' 2 links imported, 2 links overwritten, 0 links skipped.', | 447 | .' 2 links imported, 2 links overwritten, 0 links skipped.', |
429 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf) | 448 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) |
430 | ); | 449 | ); |
431 | $this->assertEquals(2, count($this->linkDb)); | 450 | $this->assertEquals(2, count($this->linkDb)); |
432 | $this->assertEquals(0, count_private($this->linkDb)); | 451 | $this->assertEquals(0, count_private($this->linkDb)); |
@@ -452,7 +471,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
452 | $this->assertEquals( | 471 | $this->assertEquals( |
453 | 'File netscape_basic.htm (482 bytes) was successfully processed:' | 472 | 'File netscape_basic.htm (482 bytes) was successfully processed:' |
454 | .' 2 links imported, 0 links overwritten, 0 links skipped.', | 473 | .' 2 links imported, 0 links overwritten, 0 links skipped.', |
455 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf) | 474 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) |
456 | ); | 475 | ); |
457 | $this->assertEquals(2, count($this->linkDb)); | 476 | $this->assertEquals(2, count($this->linkDb)); |
458 | $this->assertEquals(0, count_private($this->linkDb)); | 477 | $this->assertEquals(0, count_private($this->linkDb)); |
@@ -473,7 +492,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
473 | $this->assertEquals( | 492 | $this->assertEquals( |
474 | 'File netscape_basic.htm (482 bytes) was successfully processed:' | 493 | 'File netscape_basic.htm (482 bytes) was successfully processed:' |
475 | .' 2 links imported, 2 links overwritten, 0 links skipped.', | 494 | .' 2 links imported, 2 links overwritten, 0 links skipped.', |
476 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf) | 495 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) |
477 | ); | 496 | ); |
478 | $this->assertEquals(2, count($this->linkDb)); | 497 | $this->assertEquals(2, count($this->linkDb)); |
479 | $this->assertEquals(2, count_private($this->linkDb)); | 498 | $this->assertEquals(2, count_private($this->linkDb)); |
@@ -497,7 +516,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
497 | $this->assertEquals( | 516 | $this->assertEquals( |
498 | 'File netscape_basic.htm (482 bytes) was successfully processed:' | 517 | 'File netscape_basic.htm (482 bytes) was successfully processed:' |
499 | .' 2 links imported, 0 links overwritten, 0 links skipped.', | 518 | .' 2 links imported, 0 links overwritten, 0 links skipped.', |
500 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf) | 519 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) |
501 | ); | 520 | ); |
502 | $this->assertEquals(2, count($this->linkDb)); | 521 | $this->assertEquals(2, count($this->linkDb)); |
503 | $this->assertEquals(0, count_private($this->linkDb)); | 522 | $this->assertEquals(0, count_private($this->linkDb)); |
@@ -507,7 +526,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
507 | $this->assertEquals( | 526 | $this->assertEquals( |
508 | 'File netscape_basic.htm (482 bytes) was successfully processed:' | 527 | 'File netscape_basic.htm (482 bytes) was successfully processed:' |
509 | .' 0 links imported, 0 links overwritten, 2 links skipped.', | 528 | .' 0 links imported, 0 links overwritten, 2 links skipped.', |
510 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf) | 529 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) |
511 | ); | 530 | ); |
512 | $this->assertEquals(2, count($this->linkDb)); | 531 | $this->assertEquals(2, count($this->linkDb)); |
513 | $this->assertEquals(0, count_private($this->linkDb)); | 532 | $this->assertEquals(0, count_private($this->linkDb)); |
@@ -526,7 +545,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
526 | $this->assertEquals( | 545 | $this->assertEquals( |
527 | 'File netscape_basic.htm (482 bytes) was successfully processed:' | 546 | 'File netscape_basic.htm (482 bytes) was successfully processed:' |
528 | .' 2 links imported, 0 links overwritten, 0 links skipped.', | 547 | .' 2 links imported, 0 links overwritten, 0 links skipped.', |
529 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf) | 548 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) |
530 | ); | 549 | ); |
531 | $this->assertEquals(2, count($this->linkDb)); | 550 | $this->assertEquals(2, count($this->linkDb)); |
532 | $this->assertEquals(0, count_private($this->linkDb)); | 551 | $this->assertEquals(0, count_private($this->linkDb)); |
@@ -553,7 +572,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
553 | $this->assertEquals( | 572 | $this->assertEquals( |
554 | 'File netscape_basic.htm (482 bytes) was successfully processed:' | 573 | 'File netscape_basic.htm (482 bytes) was successfully processed:' |
555 | .' 2 links imported, 0 links overwritten, 0 links skipped.', | 574 | .' 2 links imported, 0 links overwritten, 0 links skipped.', |
556 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf) | 575 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) |
557 | ); | 576 | ); |
558 | $this->assertEquals(2, count($this->linkDb)); | 577 | $this->assertEquals(2, count($this->linkDb)); |
559 | $this->assertEquals(0, count_private($this->linkDb)); | 578 | $this->assertEquals(0, count_private($this->linkDb)); |
@@ -578,7 +597,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
578 | $this->assertEquals( | 597 | $this->assertEquals( |
579 | 'File same_date.htm (453 bytes) was successfully processed:' | 598 | 'File same_date.htm (453 bytes) was successfully processed:' |
580 | .' 3 links imported, 0 links overwritten, 0 links skipped.', | 599 | .' 3 links imported, 0 links overwritten, 0 links skipped.', |
581 | NetscapeBookmarkUtils::import(array(), $files, $this->linkDb, $this->conf) | 600 | NetscapeBookmarkUtils::import(array(), $files, $this->linkDb, $this->conf, $this->history) |
582 | ); | 601 | ); |
583 | $this->assertEquals(3, count($this->linkDb)); | 602 | $this->assertEquals(3, count($this->linkDb)); |
584 | $this->assertEquals(0, count_private($this->linkDb)); | 603 | $this->assertEquals(0, count_private($this->linkDb)); |
@@ -595,4 +614,32 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
595 | $this->linkDb[2]['id'] | 614 | $this->linkDb[2]['id'] |
596 | ); | 615 | ); |
597 | } | 616 | } |
617 | |||
618 | public function testImportCreateUpdateHistory() | ||
619 | { | ||
620 | $post = [ | ||
621 | 'privacy' => 'public', | ||
622 | 'overwrite' => 'true', | ||
623 | ]; | ||
624 | $files = file2array('netscape_basic.htm'); | ||
625 | $nbLinks = 2; | ||
626 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history); | ||
627 | $history = $this->history->getHistory(); | ||
628 | $this->assertEquals($nbLinks, count($history)); | ||
629 | foreach ($history as $value) { | ||
630 | $this->assertEquals(History::CREATED, $value['event']); | ||
631 | $this->assertTrue(new DateTime('-5 seconds') < $value['datetime']); | ||
632 | $this->assertTrue(is_int($value['id'])); | ||
633 | } | ||
634 | |||
635 | // re-import as private, enable overwriting | ||
636 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history); | ||
637 | $history = $this->history->getHistory(); | ||
638 | $this->assertEquals($nbLinks * 2, count($history)); | ||
639 | for ($i = 0 ; $i < $nbLinks ; $i++) { | ||
640 | $this->assertEquals(History::UPDATED, $history[$i]['event']); | ||
641 | $this->assertTrue(new DateTime('-5 seconds') < $history[$i]['datetime']); | ||
642 | $this->assertTrue(is_int($history[$i]['id'])); | ||
643 | } | ||
644 | } | ||
598 | } | 645 | } |
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'; | |||
11 | class TimeZoneTest extends PHPUnit_Framework_TestCase | 11 | class 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/Updater/UpdaterTest.php b/tests/Updater/UpdaterTest.php index 11b6444a..fed175df 100644 --- a/tests/Updater/UpdaterTest.php +++ b/tests/Updater/UpdaterTest.php | |||
@@ -470,46 +470,6 @@ $GLOBALS[\'privateLinkByDefault\'] = true;'; | |||
470 | } | 470 | } |
471 | 471 | ||
472 | /** | 472 | /** |
473 | * Test updateMethodDefaultThemeVintage with the default theme enabled. | ||
474 | */ | ||
475 | public function testSetDefaultThemeToVintage() | ||
476 | { | ||
477 | $sandboxConf = 'sandbox/config'; | ||
478 | copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); | ||
479 | $this->conf = new ConfigManager($sandboxConf); | ||
480 | |||
481 | $this->conf->set('resource.theme', 'default'); | ||
482 | $updater = new Updater([], [], $this->conf, true); | ||
483 | $this->assertTrue($updater->updateMethodDefaultThemeVintage()); | ||
484 | $this->assertEquals('vintage', $this->conf->get('resource.theme')); | ||
485 | |||
486 | // reload from file | ||
487 | $this->conf = new ConfigManager($sandboxConf); | ||
488 | $this->assertEquals('vintage', $this->conf->get('resource.theme')); | ||
489 | } | ||
490 | |||
491 | /** | ||
492 | * Test updateMethodDefaultThemeVintage with custom theme enabled => nothing to do. | ||
493 | */ | ||
494 | public function testSetDefaultThemeNothingToDo() | ||
495 | { | ||
496 | $sandboxConf = 'sandbox/config'; | ||
497 | copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); | ||
498 | $this->conf = new ConfigManager($sandboxConf); | ||
499 | |||
500 | $theme = 'myawesometheme'; | ||
501 | $this->conf->set('resource.theme', $theme); | ||
502 | $this->conf->write(true); | ||
503 | $updater = new Updater([], [], $this->conf, true); | ||
504 | $this->assertTrue($updater->updateMethodDefaultThemeVintage()); | ||
505 | $this->assertEquals($theme, $this->conf->get('resource.theme')); | ||
506 | |||
507 | // reload from file | ||
508 | $this->conf = new ConfigManager($sandboxConf); | ||
509 | $this->assertEquals($theme, $this->conf->get('resource.theme')); | ||
510 | } | ||
511 | |||
512 | /** | ||
513 | * Test updateMethodEscapeMarkdown with markdown plugin enabled | 473 | * Test updateMethodEscapeMarkdown with markdown plugin enabled |
514 | * => setting markdown_escape set to false. | 474 | * => setting markdown_escape set to false. |
515 | */ | 475 | */ |
diff --git a/tests/UtilsTest.php b/tests/UtilsTest.php index e70cc1ae..3d1aa653 100644 --- a/tests/UtilsTest.php +++ b/tests/UtilsTest.php | |||
@@ -4,6 +4,7 @@ | |||
4 | */ | 4 | */ |
5 | 5 | ||
6 | require_once 'application/Utils.php'; | 6 | require_once 'application/Utils.php'; |
7 | require_once 'application/Languages.php'; | ||
7 | require_once 'tests/utils/ReferenceSessionIdHashes.php'; | 8 | require_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,206 @@ 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 | } | ||
420 | |||
421 | /** | ||
422 | * Test alphabetical_sort by value, not reversed, with php-intl. | ||
423 | */ | ||
424 | public function testAlphabeticalSortByValue() | ||
425 | { | ||
426 | $arr = [ | ||
427 | 'zZz', | ||
428 | 'éee', | ||
429 | 'éae', | ||
430 | 'eee', | ||
431 | 'A', | ||
432 | 'a', | ||
433 | 'zzz', | ||
434 | ]; | ||
435 | $expected = [ | ||
436 | 'a', | ||
437 | 'A', | ||
438 | 'éae', | ||
439 | 'eee', | ||
440 | 'éee', | ||
441 | 'zzz', | ||
442 | 'zZz', | ||
443 | ]; | ||
444 | |||
445 | alphabetical_sort($arr); | ||
446 | $this->assertEquals($expected, $arr); | ||
447 | } | ||
448 | |||
449 | /** | ||
450 | * Test alphabetical_sort by value, reversed, with php-intl. | ||
451 | */ | ||
452 | public function testAlphabeticalSortByValueReversed() | ||
453 | { | ||
454 | $arr = [ | ||
455 | 'zZz', | ||
456 | 'éee', | ||
457 | 'éae', | ||
458 | 'eee', | ||
459 | 'A', | ||
460 | 'a', | ||
461 | 'zzz', | ||
462 | ]; | ||
463 | $expected = [ | ||
464 | 'zZz', | ||
465 | 'zzz', | ||
466 | 'éee', | ||
467 | 'eee', | ||
468 | 'éae', | ||
469 | 'A', | ||
470 | 'a', | ||
471 | ]; | ||
472 | |||
473 | alphabetical_sort($arr, true); | ||
474 | $this->assertEquals($expected, $arr); | ||
475 | } | ||
476 | |||
477 | /** | ||
478 | * Test alphabetical_sort by keys, not reversed, with php-intl. | ||
479 | */ | ||
480 | public function testAlphabeticalSortByKeys() | ||
481 | { | ||
482 | $arr = [ | ||
483 | 'zZz' => true, | ||
484 | 'éee' => true, | ||
485 | 'éae' => true, | ||
486 | 'eee' => true, | ||
487 | 'A' => true, | ||
488 | 'a' => true, | ||
489 | 'zzz' => true, | ||
490 | ]; | ||
491 | $expected = [ | ||
492 | 'a' => true, | ||
493 | 'A' => true, | ||
494 | 'éae' => true, | ||
495 | 'eee' => true, | ||
496 | 'éee' => true, | ||
497 | 'zzz' => true, | ||
498 | 'zZz' => true, | ||
499 | ]; | ||
500 | |||
501 | alphabetical_sort($arr, true, true); | ||
502 | $this->assertEquals($expected, $arr); | ||
503 | } | ||
504 | |||
505 | /** | ||
506 | * Test alphabetical_sort by keys, reversed, with php-intl. | ||
507 | */ | ||
508 | public function testAlphabeticalSortByKeysReversed() | ||
509 | { | ||
510 | $arr = [ | ||
511 | 'zZz' => true, | ||
512 | 'éee' => true, | ||
513 | 'éae' => true, | ||
514 | 'eee' => true, | ||
515 | 'A' => true, | ||
516 | 'a' => true, | ||
517 | 'zzz' => true, | ||
518 | ]; | ||
519 | $expected = [ | ||
520 | 'zZz' => true, | ||
521 | 'zzz' => true, | ||
522 | 'éee' => true, | ||
523 | 'eee' => true, | ||
524 | 'éae' => true, | ||
525 | 'A' => true, | ||
526 | 'a' => true, | ||
527 | ]; | ||
528 | |||
529 | alphabetical_sort($arr, true, true); | ||
530 | $this->assertEquals($expected, $arr); | ||
531 | } | ||
329 | } | 532 | } |
diff --git a/tests/api/ApiUtilsTest.php b/tests/api/ApiUtilsTest.php index b4431d1b..62baf4c5 100644 --- a/tests/api/ApiUtilsTest.php +++ b/tests/api/ApiUtilsTest.php | |||
@@ -271,4 +271,82 @@ class ApiUtilsTest extends \PHPUnit_Framework_TestCase | |||
271 | 271 | ||
272 | $this->assertEquals($expected, ApiUtils::formatLink($link, $indexUrl)); | 272 | $this->assertEquals($expected, ApiUtils::formatLink($link, $indexUrl)); |
273 | } | 273 | } |
274 | |||
275 | /** | ||
276 | * Test updateLink with valid data, and also unnecessary fields. | ||
277 | */ | ||
278 | public function testUpdateLink() | ||
279 | { | ||
280 | $created = \DateTime::createFromFormat('Ymd_His', '20170107_160102'); | ||
281 | $old = [ | ||
282 | 'id' => 12, | ||
283 | 'url' => '?abc', | ||
284 | 'shorturl' => 'abc', | ||
285 | 'title' => 'Note', | ||
286 | 'description' => '', | ||
287 | 'tags' => '', | ||
288 | 'private' => '', | ||
289 | 'created' => $created, | ||
290 | ]; | ||
291 | |||
292 | $new = [ | ||
293 | 'id' => 13, | ||
294 | 'shorturl' => 'nope', | ||
295 | 'url' => 'http://somewhere.else', | ||
296 | 'title' => 'Le Cid', | ||
297 | 'description' => 'Percé jusques au fond du cœur [...]', | ||
298 | 'tags' => 'corneille rodrigue', | ||
299 | 'private' => true, | ||
300 | 'created' => 'creation', | ||
301 | 'updated' => 'updation', | ||
302 | ]; | ||
303 | |||
304 | $result = ApiUtils::updateLink($old, $new); | ||
305 | $this->assertEquals(12, $result['id']); | ||
306 | $this->assertEquals('http://somewhere.else', $result['url']); | ||
307 | $this->assertEquals('abc', $result['shorturl']); | ||
308 | $this->assertEquals('Le Cid', $result['title']); | ||
309 | $this->assertEquals('Percé jusques au fond du cœur [...]', $result['description']); | ||
310 | $this->assertEquals('corneille rodrigue', $result['tags']); | ||
311 | $this->assertEquals(true, $result['private']); | ||
312 | $this->assertEquals($created, $result['created']); | ||
313 | $this->assertTrue(new \DateTime('5 seconds ago') < $result['updated']); | ||
314 | } | ||
315 | |||
316 | /** | ||
317 | * Test updateLink with minimal data. | ||
318 | */ | ||
319 | public function testUpdateLinkMinimal() | ||
320 | { | ||
321 | $created = \DateTime::createFromFormat('Ymd_His', '20170107_160102'); | ||
322 | $old = [ | ||
323 | 'id' => 12, | ||
324 | 'url' => '?abc', | ||
325 | 'shorturl' => 'abc', | ||
326 | 'title' => 'Note', | ||
327 | 'description' => 'Interesting description!', | ||
328 | 'tags' => 'doggo', | ||
329 | 'private' => true, | ||
330 | 'created' => $created, | ||
331 | ]; | ||
332 | |||
333 | $new = [ | ||
334 | 'url' => '', | ||
335 | 'title' => '', | ||
336 | 'description' => '', | ||
337 | 'tags' => '', | ||
338 | 'private' => false, | ||
339 | ]; | ||
340 | |||
341 | $result = ApiUtils::updateLink($old, $new); | ||
342 | $this->assertEquals(12, $result['id']); | ||
343 | $this->assertEquals('?abc', $result['url']); | ||
344 | $this->assertEquals('abc', $result['shorturl']); | ||
345 | $this->assertEquals('?abc', $result['title']); | ||
346 | $this->assertEquals('', $result['description']); | ||
347 | $this->assertEquals('', $result['tags']); | ||
348 | $this->assertEquals(false, $result['private']); | ||
349 | $this->assertEquals($created, $result['created']); | ||
350 | $this->assertTrue(new \DateTime('5 seconds ago') < $result['updated']); | ||
351 | } | ||
274 | } | 352 | } |
diff --git a/tests/api/controllers/DeleteLinkTest.php b/tests/api/controllers/DeleteLinkTest.php new file mode 100644 index 00000000..7d797137 --- /dev/null +++ b/tests/api/controllers/DeleteLinkTest.php | |||
@@ -0,0 +1,126 @@ | |||
1 | <?php | ||
2 | |||
3 | |||
4 | namespace Shaarli\Api\Controllers; | ||
5 | |||
6 | use Shaarli\Config\ConfigManager; | ||
7 | use Slim\Container; | ||
8 | use Slim\Http\Environment; | ||
9 | use Slim\Http\Request; | ||
10 | use Slim\Http\Response; | ||
11 | |||
12 | class DeleteLinkTest extends \PHPUnit_Framework_TestCase | ||
13 | { | ||
14 | /** | ||
15 | * @var string datastore to test write operations | ||
16 | */ | ||
17 | protected static $testDatastore = 'sandbox/datastore.php'; | ||
18 | |||
19 | /** | ||
20 | * @var string datastore to test write operations | ||
21 | */ | ||
22 | protected static $testHistory = 'sandbox/history.php'; | ||
23 | |||
24 | /** | ||
25 | * @var ConfigManager instance | ||
26 | */ | ||
27 | protected $conf; | ||
28 | |||
29 | /** | ||
30 | * @var \ReferenceLinkDB instance. | ||
31 | */ | ||
32 | protected $refDB = null; | ||
33 | |||
34 | /** | ||
35 | * @var \LinkDB instance. | ||
36 | */ | ||
37 | protected $linkDB; | ||
38 | |||
39 | /** | ||
40 | * @var \History instance. | ||
41 | */ | ||
42 | protected $history; | ||
43 | |||
44 | /** | ||
45 | * @var Container instance. | ||
46 | */ | ||
47 | protected $container; | ||
48 | |||
49 | /** | ||
50 | * @var Links controller instance. | ||
51 | */ | ||
52 | protected $controller; | ||
53 | |||
54 | /** | ||
55 | * Before each test, instantiate a new Api with its config, plugins and links. | ||
56 | */ | ||
57 | public function setUp() | ||
58 | { | ||
59 | $this->conf = new ConfigManager('tests/utils/config/configJson'); | ||
60 | $this->refDB = new \ReferenceLinkDB(); | ||
61 | $this->refDB->write(self::$testDatastore); | ||
62 | $this->linkDB = new \LinkDB(self::$testDatastore, true, false); | ||
63 | $refHistory = new \ReferenceHistory(); | ||
64 | $refHistory->write(self::$testHistory); | ||
65 | $this->history = new \History(self::$testHistory); | ||
66 | $this->container = new Container(); | ||
67 | $this->container['conf'] = $this->conf; | ||
68 | $this->container['db'] = $this->linkDB; | ||
69 | $this->container['history'] = $this->history; | ||
70 | |||
71 | $this->controller = new Links($this->container); | ||
72 | } | ||
73 | |||
74 | /** | ||
75 | * After each test, remove the test datastore. | ||
76 | */ | ||
77 | public function tearDown() | ||
78 | { | ||
79 | @unlink(self::$testDatastore); | ||
80 | @unlink(self::$testHistory); | ||
81 | } | ||
82 | |||
83 | /** | ||
84 | * Test DELETE link endpoint: the link should be removed. | ||
85 | */ | ||
86 | public function testDeleteLinkValid() | ||
87 | { | ||
88 | $id = '41'; | ||
89 | $this->assertTrue(isset($this->linkDB[$id])); | ||
90 | $env = Environment::mock([ | ||
91 | 'REQUEST_METHOD' => 'DELETE', | ||
92 | ]); | ||
93 | $request = Request::createFromEnvironment($env); | ||
94 | |||
95 | $response = $this->controller->deleteLink($request, new Response(), ['id' => $id]); | ||
96 | $this->assertEquals(204, $response->getStatusCode()); | ||
97 | $this->assertEmpty((string) $response->getBody()); | ||
98 | |||
99 | $this->linkDB = new \LinkDB(self::$testDatastore, true, false); | ||
100 | $this->assertFalse(isset($this->linkDB[$id])); | ||
101 | |||
102 | $historyEntry = $this->history->getHistory()[0]; | ||
103 | $this->assertEquals(\History::DELETED, $historyEntry['event']); | ||
104 | $this->assertTrue( | ||
105 | (new \DateTime())->add(\DateInterval::createFromDateString('-5 seconds')) < $historyEntry['datetime'] | ||
106 | ); | ||
107 | $this->assertEquals($id, $historyEntry['id']); | ||
108 | } | ||
109 | |||
110 | /** | ||
111 | * Test DELETE link endpoint: reach not existing ID. | ||
112 | * | ||
113 | * @expectedException Shaarli\Api\Exceptions\ApiLinkNotFoundException | ||
114 | */ | ||
115 | public function testDeleteLink404() | ||
116 | { | ||
117 | $id = -1; | ||
118 | $this->assertFalse(isset($this->linkDB[$id])); | ||
119 | $env = Environment::mock([ | ||
120 | 'REQUEST_METHOD' => 'DELETE', | ||
121 | ]); | ||
122 | $request = Request::createFromEnvironment($env); | ||
123 | |||
124 | $this->controller->deleteLink($request, new Response(), ['id' => $id]); | ||
125 | } | ||
126 | } | ||
diff --git a/tests/api/controllers/GetLinkIdTest.php b/tests/api/controllers/GetLinkIdTest.php index 45b18e6a..57528d5a 100644 --- a/tests/api/controllers/GetLinkIdTest.php +++ b/tests/api/controllers/GetLinkIdTest.php | |||
@@ -62,6 +62,7 @@ class GetLinkIdTest extends \PHPUnit_Framework_TestCase | |||
62 | $this->container = new Container(); | 62 | $this->container = new Container(); |
63 | $this->container['conf'] = $this->conf; | 63 | $this->container['conf'] = $this->conf; |
64 | $this->container['db'] = new \LinkDB(self::$testDatastore, true, false); | 64 | $this->container['db'] = new \LinkDB(self::$testDatastore, true, false); |
65 | $this->container['history'] = null; | ||
65 | 66 | ||
66 | $this->controller = new Links($this->container); | 67 | $this->controller = new Links($this->container); |
67 | } | 68 | } |
diff --git a/tests/api/controllers/GetLinksTest.php b/tests/api/controllers/GetLinksTest.php index 10330cd9..84ae7f7a 100644 --- a/tests/api/controllers/GetLinksTest.php +++ b/tests/api/controllers/GetLinksTest.php | |||
@@ -61,6 +61,7 @@ class GetLinksTest extends \PHPUnit_Framework_TestCase | |||
61 | $this->container = new Container(); | 61 | $this->container = new Container(); |
62 | $this->container['conf'] = $this->conf; | 62 | $this->container['conf'] = $this->conf; |
63 | $this->container['db'] = new \LinkDB(self::$testDatastore, true, false); | 63 | $this->container['db'] = new \LinkDB(self::$testDatastore, true, false); |
64 | $this->container['history'] = null; | ||
64 | 65 | ||
65 | $this->controller = new Links($this->container); | 66 | $this->controller = new Links($this->container); |
66 | } | 67 | } |
diff --git a/tests/api/controllers/HistoryTest.php b/tests/api/controllers/HistoryTest.php new file mode 100644 index 00000000..61046d97 --- /dev/null +++ b/tests/api/controllers/HistoryTest.php | |||
@@ -0,0 +1,216 @@ | |||
1 | <?php | ||
2 | |||
3 | |||
4 | namespace Shaarli\Api\Controllers; | ||
5 | |||
6 | |||
7 | use Shaarli\Config\ConfigManager; | ||
8 | use Slim\Container; | ||
9 | use Slim\Http\Environment; | ||
10 | use Slim\Http\Request; | ||
11 | use Slim\Http\Response; | ||
12 | |||
13 | require_once 'tests/utils/ReferenceHistory.php'; | ||
14 | |||
15 | class HistoryTest extends \PHPUnit_Framework_TestCase | ||
16 | { | ||
17 | /** | ||
18 | * @var string datastore to test write operations | ||
19 | */ | ||
20 | protected static $testHistory = 'sandbox/history.php'; | ||
21 | |||
22 | /** | ||
23 | * @var ConfigManager instance | ||
24 | */ | ||
25 | protected $conf; | ||
26 | |||
27 | /** | ||
28 | * @var \ReferenceHistory instance. | ||
29 | */ | ||
30 | protected $refHistory = null; | ||
31 | |||
32 | /** | ||
33 | * @var Container instance. | ||
34 | */ | ||
35 | protected $container; | ||
36 | |||
37 | /** | ||
38 | * @var History controller instance. | ||
39 | */ | ||
40 | protected $controller; | ||
41 | |||
42 | /** | ||
43 | * Before every test, instantiate a new Api with its config, plugins and links. | ||
44 | */ | ||
45 | public function setUp() | ||
46 | { | ||
47 | $this->conf = new ConfigManager('tests/utils/config/configJson.json.php'); | ||
48 | $this->refHistory = new \ReferenceHistory(); | ||
49 | $this->refHistory->write(self::$testHistory); | ||
50 | $this->container = new Container(); | ||
51 | $this->container['conf'] = $this->conf; | ||
52 | $this->container['db'] = true; | ||
53 | $this->container['history'] = new \History(self::$testHistory); | ||
54 | |||
55 | $this->controller = new History($this->container); | ||
56 | } | ||
57 | |||
58 | /** | ||
59 | * After every test, remove the test datastore. | ||
60 | */ | ||
61 | public function tearDown() | ||
62 | { | ||
63 | @unlink(self::$testHistory); | ||
64 | } | ||
65 | |||
66 | /** | ||
67 | * Test /history service without parameter. | ||
68 | */ | ||
69 | public function testGetHistory() | ||
70 | { | ||
71 | $env = Environment::mock([ | ||
72 | 'REQUEST_METHOD' => 'GET', | ||
73 | ]); | ||
74 | $request = Request::createFromEnvironment($env); | ||
75 | |||
76 | $response = $this->controller->getHistory($request, new Response()); | ||
77 | $this->assertEquals(200, $response->getStatusCode()); | ||
78 | $data = json_decode((string) $response->getBody(), true); | ||
79 | |||
80 | $this->assertEquals($this->refHistory->count(), count($data)); | ||
81 | |||
82 | $this->assertEquals(\History::DELETED, $data[0]['event']); | ||
83 | $this->assertEquals( | ||
84 | \DateTime::createFromFormat('Ymd_His', '20170303_121216')->format(\DateTime::ATOM), | ||
85 | $data[0]['datetime'] | ||
86 | ); | ||
87 | $this->assertEquals(124, $data[0]['id']); | ||
88 | |||
89 | $this->assertEquals(\History::SETTINGS, $data[1]['event']); | ||
90 | $this->assertEquals( | ||
91 | \DateTime::createFromFormat('Ymd_His', '20170302_121215')->format(\DateTime::ATOM), | ||
92 | $data[1]['datetime'] | ||
93 | ); | ||
94 | $this->assertNull($data[1]['id']); | ||
95 | |||
96 | $this->assertEquals(\History::UPDATED, $data[2]['event']); | ||
97 | $this->assertEquals( | ||
98 | \DateTime::createFromFormat('Ymd_His', '20170301_121214')->format(\DateTime::ATOM), | ||
99 | $data[2]['datetime'] | ||
100 | ); | ||
101 | $this->assertEquals(123, $data[2]['id']); | ||
102 | |||
103 | $this->assertEquals(\History::CREATED, $data[3]['event']); | ||
104 | $this->assertEquals( | ||
105 | \DateTime::createFromFormat('Ymd_His', '20170201_121214')->format(\DateTime::ATOM), | ||
106 | $data[3]['datetime'] | ||
107 | ); | ||
108 | $this->assertEquals(124, $data[3]['id']); | ||
109 | |||
110 | $this->assertEquals(\History::CREATED, $data[4]['event']); | ||
111 | $this->assertEquals( | ||
112 | \DateTime::createFromFormat('Ymd_His', '20170101_121212')->format(\DateTime::ATOM), | ||
113 | $data[4]['datetime'] | ||
114 | ); | ||
115 | $this->assertEquals(123, $data[4]['id']); | ||
116 | } | ||
117 | |||
118 | /** | ||
119 | * Test /history service with limit parameter. | ||
120 | */ | ||
121 | public function testGetHistoryLimit() | ||
122 | { | ||
123 | $env = Environment::mock([ | ||
124 | 'REQUEST_METHOD' => 'GET', | ||
125 | 'QUERY_STRING' => 'limit=1' | ||
126 | ]); | ||
127 | $request = Request::createFromEnvironment($env); | ||
128 | |||
129 | $response = $this->controller->getHistory($request, new Response()); | ||
130 | $this->assertEquals(200, $response->getStatusCode()); | ||
131 | $data = json_decode((string) $response->getBody(), true); | ||
132 | |||
133 | $this->assertEquals(1, count($data)); | ||
134 | |||
135 | $this->assertEquals(\History::DELETED, $data[0]['event']); | ||
136 | $this->assertEquals( | ||
137 | \DateTime::createFromFormat('Ymd_His', '20170303_121216')->format(\DateTime::ATOM), | ||
138 | $data[0]['datetime'] | ||
139 | ); | ||
140 | $this->assertEquals(124, $data[0]['id']); | ||
141 | } | ||
142 | |||
143 | /** | ||
144 | * Test /history service with offset parameter. | ||
145 | */ | ||
146 | public function testGetHistoryOffset() | ||
147 | { | ||
148 | $env = Environment::mock([ | ||
149 | 'REQUEST_METHOD' => 'GET', | ||
150 | 'QUERY_STRING' => 'offset=4' | ||
151 | ]); | ||
152 | $request = Request::createFromEnvironment($env); | ||
153 | |||
154 | $response = $this->controller->getHistory($request, new Response()); | ||
155 | $this->assertEquals(200, $response->getStatusCode()); | ||
156 | $data = json_decode((string) $response->getBody(), true); | ||
157 | |||
158 | $this->assertEquals(1, count($data)); | ||
159 | |||
160 | $this->assertEquals(\History::CREATED, $data[0]['event']); | ||
161 | $this->assertEquals( | ||
162 | \DateTime::createFromFormat('Ymd_His', '20170101_121212')->format(\DateTime::ATOM), | ||
163 | $data[0]['datetime'] | ||
164 | ); | ||
165 | $this->assertEquals(123, $data[0]['id']); | ||
166 | } | ||
167 | |||
168 | /** | ||
169 | * Test /history service with since parameter. | ||
170 | */ | ||
171 | public function testGetHistorySince() | ||
172 | { | ||
173 | $env = Environment::mock([ | ||
174 | 'REQUEST_METHOD' => 'GET', | ||
175 | 'QUERY_STRING' => 'since=2017-03-03T00:00:00%2B00:00' | ||
176 | ]); | ||
177 | $request = Request::createFromEnvironment($env); | ||
178 | |||
179 | $response = $this->controller->getHistory($request, new Response()); | ||
180 | $this->assertEquals(200, $response->getStatusCode()); | ||
181 | $data = json_decode((string) $response->getBody(), true); | ||
182 | |||
183 | $this->assertEquals(1, count($data)); | ||
184 | |||
185 | $this->assertEquals(\History::DELETED, $data[0]['event']); | ||
186 | $this->assertEquals( | ||
187 | \DateTime::createFromFormat('Ymd_His', '20170303_121216')->format(\DateTime::ATOM), | ||
188 | $data[0]['datetime'] | ||
189 | ); | ||
190 | $this->assertEquals(124, $data[0]['id']); | ||
191 | } | ||
192 | |||
193 | /** | ||
194 | * Test /history service with since parameter. | ||
195 | */ | ||
196 | public function testGetHistorySinceOffsetLimit() | ||
197 | { | ||
198 | $env = Environment::mock([ | ||
199 | 'REQUEST_METHOD' => 'GET', | ||
200 | 'QUERY_STRING' => 'since=2017-02-01T00:00:00%2B00:00&offset=1&limit=1' | ||
201 | ]); | ||
202 | $request = Request::createFromEnvironment($env); | ||
203 | |||
204 | $response = $this->controller->getHistory($request, new Response()); | ||
205 | $this->assertEquals(200, $response->getStatusCode()); | ||
206 | $data = json_decode((string) $response->getBody(), true); | ||
207 | |||
208 | $this->assertEquals(1, count($data)); | ||
209 | |||
210 | $this->assertEquals(\History::SETTINGS, $data[0]['event']); | ||
211 | $this->assertEquals( | ||
212 | \DateTime::createFromFormat('Ymd_His', '20170302_121215')->format(\DateTime::ATOM), | ||
213 | $data[0]['datetime'] | ||
214 | ); | ||
215 | } | ||
216 | } | ||
diff --git a/tests/api/controllers/InfoTest.php b/tests/api/controllers/InfoTest.php index 4beef3f7..e85eb281 100644 --- a/tests/api/controllers/InfoTest.php +++ b/tests/api/controllers/InfoTest.php | |||
@@ -54,6 +54,7 @@ class InfoTest extends \PHPUnit_Framework_TestCase | |||
54 | $this->container = new Container(); | 54 | $this->container = new Container(); |
55 | $this->container['conf'] = $this->conf; | 55 | $this->container['conf'] = $this->conf; |
56 | $this->container['db'] = new \LinkDB(self::$testDatastore, true, false); | 56 | $this->container['db'] = new \LinkDB(self::$testDatastore, true, false); |
57 | $this->container['history'] = null; | ||
57 | 58 | ||
58 | $this->controller = new Info($this->container); | 59 | $this->controller = new Info($this->container); |
59 | } | 60 | } |
diff --git a/tests/api/controllers/PostLinkTest.php b/tests/api/controllers/PostLinkTest.php new file mode 100644 index 00000000..31954e39 --- /dev/null +++ b/tests/api/controllers/PostLinkTest.php | |||
@@ -0,0 +1,216 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Shaarli\Api\Controllers; | ||
4 | |||
5 | |||
6 | use Shaarli\Config\ConfigManager; | ||
7 | use Slim\Container; | ||
8 | use Slim\Http\Environment; | ||
9 | use Slim\Http\Request; | ||
10 | use Slim\Http\Response; | ||
11 | |||
12 | /** | ||
13 | * Class PostLinkTest | ||
14 | * | ||
15 | * Test POST Link REST API service. | ||
16 | * | ||
17 | * @package Shaarli\Api\Controllers | ||
18 | */ | ||
19 | class 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 string datastore to test write operations | ||
28 | */ | ||
29 | protected static $testHistory = 'sandbox/history.php'; | ||
30 | |||
31 | /** | ||
32 | * @var ConfigManager instance | ||
33 | */ | ||
34 | protected $conf; | ||
35 | |||
36 | /** | ||
37 | * @var \ReferenceLinkDB instance. | ||
38 | */ | ||
39 | protected $refDB = null; | ||
40 | |||
41 | /** | ||
42 | * @var \History instance. | ||
43 | */ | ||
44 | protected $history; | ||
45 | |||
46 | /** | ||
47 | * @var Container instance. | ||
48 | */ | ||
49 | protected $container; | ||
50 | |||
51 | /** | ||
52 | * @var Links controller instance. | ||
53 | */ | ||
54 | protected $controller; | ||
55 | |||
56 | /** | ||
57 | * Number of JSON field per link. | ||
58 | */ | ||
59 | const NB_FIELDS_LINK = 9; | ||
60 | |||
61 | /** | ||
62 | * Before every test, instantiate a new Api with its config, plugins and links. | ||
63 | */ | ||
64 | public function setUp() | ||
65 | { | ||
66 | $this->conf = new ConfigManager('tests/utils/config/configJson.json.php'); | ||
67 | $this->refDB = new \ReferenceLinkDB(); | ||
68 | $this->refDB->write(self::$testDatastore); | ||
69 | |||
70 | $refHistory = new \ReferenceHistory(); | ||
71 | $refHistory->write(self::$testHistory); | ||
72 | $this->history = new \History(self::$testHistory); | ||
73 | |||
74 | $this->container = new Container(); | ||
75 | $this->container['conf'] = $this->conf; | ||
76 | $this->container['db'] = new \LinkDB(self::$testDatastore, true, false); | ||
77 | $this->container['history'] = new \History(self::$testHistory); | ||
78 | |||
79 | $this->controller = new Links($this->container); | ||
80 | |||
81 | $mock = $this->getMock('\Slim\Router', ['relativePathFor']); | ||
82 | $mock->expects($this->any()) | ||
83 | ->method('relativePathFor') | ||
84 | ->willReturn('api/v1/links/1'); | ||
85 | |||
86 | // affect @property-read... seems to work | ||
87 | $this->controller->getCi()->router = $mock; | ||
88 | |||
89 | // Used by index_url(). | ||
90 | $this->controller->getCi()['environment'] = [ | ||
91 | 'SERVER_NAME' => 'domain.tld', | ||
92 | 'SERVER_PORT' => 80, | ||
93 | 'SCRIPT_NAME' => '/', | ||
94 | ]; | ||
95 | } | ||
96 | |||
97 | /** | ||
98 | * After every test, remove the test datastore. | ||
99 | */ | ||
100 | public function tearDown() | ||
101 | { | ||
102 | @unlink(self::$testDatastore); | ||
103 | @unlink(self::$testHistory); | ||
104 | } | ||
105 | |||
106 | /** | ||
107 | * Test link creation without any field: creates a blank note. | ||
108 | */ | ||
109 | public function testPostLinkMinimal() | ||
110 | { | ||
111 | $env = Environment::mock([ | ||
112 | 'REQUEST_METHOD' => 'POST', | ||
113 | ]); | ||
114 | |||
115 | $request = Request::createFromEnvironment($env); | ||
116 | |||
117 | $response = $this->controller->postLink($request, new Response()); | ||
118 | $this->assertEquals(201, $response->getStatusCode()); | ||
119 | $this->assertEquals('api/v1/links/1', $response->getHeader('Location')[0]); | ||
120 | $data = json_decode((string) $response->getBody(), true); | ||
121 | $this->assertEquals(self::NB_FIELDS_LINK, count($data)); | ||
122 | $this->assertEquals(43, $data['id']); | ||
123 | $this->assertRegExp('/[\w-_]{6}/', $data['shorturl']); | ||
124 | $this->assertEquals('http://domain.tld/?' . $data['shorturl'], $data['url']); | ||
125 | $this->assertEquals('?' . $data['shorturl'], $data['title']); | ||
126 | $this->assertEquals('', $data['description']); | ||
127 | $this->assertEquals([], $data['tags']); | ||
128 | $this->assertEquals(false, $data['private']); | ||
129 | $this->assertTrue(new \DateTime('5 seconds ago') < \DateTime::createFromFormat(\DateTime::ATOM, $data['created'])); | ||
130 | $this->assertEquals('', $data['updated']); | ||
131 | |||
132 | $historyEntry = $this->history->getHistory()[0]; | ||
133 | $this->assertEquals(\History::CREATED, $historyEntry['event']); | ||
134 | $this->assertTrue( | ||
135 | (new \DateTime())->add(\DateInterval::createFromDateString('-5 seconds')) < $historyEntry['datetime'] | ||
136 | ); | ||
137 | $this->assertEquals(43, $historyEntry['id']); | ||
138 | } | ||
139 | |||
140 | /** | ||
141 | * Test link creation with all available fields. | ||
142 | */ | ||
143 | public function testPostLinkFull() | ||
144 | { | ||
145 | $link = [ | ||
146 | 'url' => 'website.tld/test?foo=bar', | ||
147 | 'title' => 'new entry', | ||
148 | 'description' => 'shaare description', | ||
149 | 'tags' => ['one', 'two'], | ||
150 | 'private' => true, | ||
151 | ]; | ||
152 | $env = Environment::mock([ | ||
153 | 'REQUEST_METHOD' => 'POST', | ||
154 | 'CONTENT_TYPE' => 'application/json' | ||
155 | ]); | ||
156 | |||
157 | $request = Request::createFromEnvironment($env); | ||
158 | $request = $request->withParsedBody($link); | ||
159 | $response = $this->controller->postLink($request, new Response()); | ||
160 | |||
161 | $this->assertEquals(201, $response->getStatusCode()); | ||
162 | $this->assertEquals('api/v1/links/1', $response->getHeader('Location')[0]); | ||
163 | $data = json_decode((string) $response->getBody(), true); | ||
164 | $this->assertEquals(self::NB_FIELDS_LINK, count($data)); | ||
165 | $this->assertEquals(43, $data['id']); | ||
166 | $this->assertRegExp('/[\w-_]{6}/', $data['shorturl']); | ||
167 | $this->assertEquals('http://' . $link['url'], $data['url']); | ||
168 | $this->assertEquals($link['title'], $data['title']); | ||
169 | $this->assertEquals($link['description'], $data['description']); | ||
170 | $this->assertEquals($link['tags'], $data['tags']); | ||
171 | $this->assertEquals(true, $data['private']); | ||
172 | $this->assertTrue(new \DateTime('2 seconds ago') < \DateTime::createFromFormat(\DateTime::ATOM, $data['created'])); | ||
173 | $this->assertEquals('', $data['updated']); | ||
174 | } | ||
175 | |||
176 | /** | ||
177 | * Test link creation with an existing link (duplicate URL). Should return a 409 HTTP error and the existing link. | ||
178 | */ | ||
179 | public function testPostLinkDuplicate() | ||
180 | { | ||
181 | $link = [ | ||
182 | 'url' => 'mediagoblin.org/', | ||
183 | 'title' => 'new entry', | ||
184 | 'description' => 'shaare description', | ||
185 | 'tags' => ['one', 'two'], | ||
186 | 'private' => true, | ||
187 | ]; | ||
188 | $env = Environment::mock([ | ||
189 | 'REQUEST_METHOD' => 'POST', | ||
190 | 'CONTENT_TYPE' => 'application/json' | ||
191 | ]); | ||
192 | |||
193 | $request = Request::createFromEnvironment($env); | ||
194 | $request = $request->withParsedBody($link); | ||
195 | $response = $this->controller->postLink($request, new Response()); | ||
196 | |||
197 | $this->assertEquals(409, $response->getStatusCode()); | ||
198 | $data = json_decode((string) $response->getBody(), true); | ||
199 | $this->assertEquals(self::NB_FIELDS_LINK, count($data)); | ||
200 | $this->assertEquals(7, $data['id']); | ||
201 | $this->assertEquals('IuWvgA', $data['shorturl']); | ||
202 | $this->assertEquals('http://mediagoblin.org/', $data['url']); | ||
203 | $this->assertEquals('MediaGoblin', $data['title']); | ||
204 | $this->assertEquals('A free software media publishing platform #hashtagOther', $data['description']); | ||
205 | $this->assertEquals(['gnu', 'media', 'web', '.hidden', 'hashtag'], $data['tags']); | ||
206 | $this->assertEquals(false, $data['private']); | ||
207 | $this->assertEquals( | ||
208 | \DateTime::createFromFormat(\LinkDB::LINK_DATE_FORMAT, '20130614_184135'), | ||
209 | \DateTime::createFromFormat(\DateTime::ATOM, $data['created']) | ||
210 | ); | ||
211 | $this->assertEquals( | ||
212 | \DateTime::createFromFormat(\LinkDB::LINK_DATE_FORMAT, '20130615_184230'), | ||
213 | \DateTime::createFromFormat(\DateTime::ATOM, $data['updated']) | ||
214 | ); | ||
215 | } | ||
216 | } | ||
diff --git a/tests/api/controllers/PutLinkTest.php b/tests/api/controllers/PutLinkTest.php new file mode 100644 index 00000000..8a562571 --- /dev/null +++ b/tests/api/controllers/PutLinkTest.php | |||
@@ -0,0 +1,222 @@ | |||
1 | <?php | ||
2 | |||
3 | |||
4 | namespace Shaarli\Api\Controllers; | ||
5 | |||
6 | |||
7 | use Shaarli\Config\ConfigManager; | ||
8 | use Slim\Container; | ||
9 | use Slim\Http\Environment; | ||
10 | use Slim\Http\Request; | ||
11 | use Slim\Http\Response; | ||
12 | |||
13 | class PutLinkTest extends \PHPUnit_Framework_TestCase | ||
14 | { | ||
15 | /** | ||
16 | * @var string datastore to test write operations | ||
17 | */ | ||
18 | protected static $testDatastore = 'sandbox/datastore.php'; | ||
19 | |||
20 | /** | ||
21 | * @var string datastore to test write operations | ||
22 | */ | ||
23 | protected static $testHistory = 'sandbox/history.php'; | ||
24 | |||
25 | /** | ||
26 | * @var ConfigManager instance | ||
27 | */ | ||
28 | protected $conf; | ||
29 | |||
30 | /** | ||
31 | * @var \ReferenceLinkDB instance. | ||
32 | */ | ||
33 | protected $refDB = null; | ||
34 | |||
35 | /** | ||
36 | * @var \History instance. | ||
37 | */ | ||
38 | protected $history; | ||
39 | |||
40 | /** | ||
41 | * @var Container instance. | ||
42 | */ | ||
43 | protected $container; | ||
44 | |||
45 | /** | ||
46 | * @var Links controller instance. | ||
47 | */ | ||
48 | protected $controller; | ||
49 | |||
50 | /** | ||
51 | * Number of JSON field per link. | ||
52 | */ | ||
53 | const NB_FIELDS_LINK = 9; | ||
54 | |||
55 | /** | ||
56 | * Before every test, instantiate a new Api with its config, plugins and links. | ||
57 | */ | ||
58 | public function setUp() | ||
59 | { | ||
60 | $this->conf = new ConfigManager('tests/utils/config/configJson.json.php'); | ||
61 | $this->refDB = new \ReferenceLinkDB(); | ||
62 | $this->refDB->write(self::$testDatastore); | ||
63 | |||
64 | $refHistory = new \ReferenceHistory(); | ||
65 | $refHistory->write(self::$testHistory); | ||
66 | $this->history = new \History(self::$testHistory); | ||
67 | |||
68 | $this->container = new Container(); | ||
69 | $this->container['conf'] = $this->conf; | ||
70 | $this->container['db'] = new \LinkDB(self::$testDatastore, true, false); | ||
71 | $this->container['history'] = new \History(self::$testHistory); | ||
72 | |||
73 | $this->controller = new Links($this->container); | ||
74 | |||
75 | // Used by index_url(). | ||
76 | $this->controller->getCi()['environment'] = [ | ||
77 | 'SERVER_NAME' => 'domain.tld', | ||
78 | 'SERVER_PORT' => 80, | ||
79 | 'SCRIPT_NAME' => '/', | ||
80 | ]; | ||
81 | } | ||
82 | |||
83 | /** | ||
84 | * After every test, remove the test datastore. | ||
85 | */ | ||
86 | public function tearDown() | ||
87 | { | ||
88 | @unlink(self::$testDatastore); | ||
89 | @unlink(self::$testHistory); | ||
90 | } | ||
91 | |||
92 | /** | ||
93 | * Test link update without value: reset the link to default values | ||
94 | */ | ||
95 | public function testPutLinkMinimal() | ||
96 | { | ||
97 | $env = Environment::mock([ | ||
98 | 'REQUEST_METHOD' => 'PUT', | ||
99 | ]); | ||
100 | $id = '41'; | ||
101 | $request = Request::createFromEnvironment($env); | ||
102 | |||
103 | $response = $this->controller->putLink($request, new Response(), ['id' => $id]); | ||
104 | $this->assertEquals(200, $response->getStatusCode()); | ||
105 | $data = json_decode((string) $response->getBody(), true); | ||
106 | $this->assertEquals(self::NB_FIELDS_LINK, count($data)); | ||
107 | $this->assertEquals($id, $data['id']); | ||
108 | $this->assertEquals('WDWyig', $data['shorturl']); | ||
109 | $this->assertEquals('http://domain.tld/?WDWyig', $data['url']); | ||
110 | $this->assertEquals('?WDWyig', $data['title']); | ||
111 | $this->assertEquals('', $data['description']); | ||
112 | $this->assertEquals([], $data['tags']); | ||
113 | $this->assertEquals(false, $data['private']); | ||
114 | $this->assertEquals( | ||
115 | \DateTime::createFromFormat('Ymd_His', '20150310_114651'), | ||
116 | \DateTime::createFromFormat(\DateTime::ATOM, $data['created']) | ||
117 | ); | ||
118 | $this->assertTrue(new \DateTime('5 seconds ago') < \DateTime::createFromFormat(\DateTime::ATOM, $data['updated'])); | ||
119 | |||
120 | $historyEntry = $this->history->getHistory()[0]; | ||
121 | $this->assertEquals(\History::UPDATED, $historyEntry['event']); | ||
122 | $this->assertTrue( | ||
123 | (new \DateTime())->add(\DateInterval::createFromDateString('-5 seconds')) < $historyEntry['datetime'] | ||
124 | ); | ||
125 | $this->assertEquals($id, $historyEntry['id']); | ||
126 | } | ||
127 | |||
128 | /** | ||
129 | * Test link update with new values | ||
130 | */ | ||
131 | public function testPutLinkWithValues() | ||
132 | { | ||
133 | $env = Environment::mock([ | ||
134 | 'REQUEST_METHOD' => 'PUT', | ||
135 | 'CONTENT_TYPE' => 'application/json' | ||
136 | ]); | ||
137 | $id = 41; | ||
138 | $update = [ | ||
139 | 'url' => 'http://somewhere.else', | ||
140 | 'title' => 'Le Cid', | ||
141 | 'description' => 'Percé jusques au fond du cœur [...]', | ||
142 | 'tags' => ['corneille', 'rodrigue'], | ||
143 | 'private' => true, | ||
144 | ]; | ||
145 | $request = Request::createFromEnvironment($env); | ||
146 | $request = $request->withParsedBody($update); | ||
147 | |||
148 | $response = $this->controller->putLink($request, new Response(), ['id' => $id]); | ||
149 | $this->assertEquals(200, $response->getStatusCode()); | ||
150 | $data = json_decode((string) $response->getBody(), true); | ||
151 | $this->assertEquals(self::NB_FIELDS_LINK, count($data)); | ||
152 | $this->assertEquals($id, $data['id']); | ||
153 | $this->assertEquals('WDWyig', $data['shorturl']); | ||
154 | $this->assertEquals('http://somewhere.else', $data['url']); | ||
155 | $this->assertEquals('Le Cid', $data['title']); | ||
156 | $this->assertEquals('Percé jusques au fond du cœur [...]', $data['description']); | ||
157 | $this->assertEquals(['corneille', 'rodrigue'], $data['tags']); | ||
158 | $this->assertEquals(true, $data['private']); | ||
159 | $this->assertEquals( | ||
160 | \DateTime::createFromFormat('Ymd_His', '20150310_114651'), | ||
161 | \DateTime::createFromFormat(\DateTime::ATOM, $data['created']) | ||
162 | ); | ||
163 | $this->assertTrue(new \DateTime('5 seconds ago') < \DateTime::createFromFormat(\DateTime::ATOM, $data['updated'])); | ||
164 | } | ||
165 | |||
166 | /** | ||
167 | * Test link update with an existing URL: 409 Conflict with the existing link as body | ||
168 | */ | ||
169 | public function testPutLinkDuplicate() | ||
170 | { | ||
171 | $link = [ | ||
172 | 'url' => 'mediagoblin.org/', | ||
173 | 'title' => 'new entry', | ||
174 | 'description' => 'shaare description', | ||
175 | 'tags' => ['one', 'two'], | ||
176 | 'private' => true, | ||
177 | ]; | ||
178 | $env = Environment::mock([ | ||
179 | 'REQUEST_METHOD' => 'PUT', | ||
180 | 'CONTENT_TYPE' => 'application/json' | ||
181 | ]); | ||
182 | |||
183 | $request = Request::createFromEnvironment($env); | ||
184 | $request = $request->withParsedBody($link); | ||
185 | $response = $this->controller->putLink($request, new Response(), ['id' => 41]); | ||
186 | |||
187 | $this->assertEquals(409, $response->getStatusCode()); | ||
188 | $data = json_decode((string) $response->getBody(), true); | ||
189 | $this->assertEquals(self::NB_FIELDS_LINK, count($data)); | ||
190 | $this->assertEquals(7, $data['id']); | ||
191 | $this->assertEquals('IuWvgA', $data['shorturl']); | ||
192 | $this->assertEquals('http://mediagoblin.org/', $data['url']); | ||
193 | $this->assertEquals('MediaGoblin', $data['title']); | ||
194 | $this->assertEquals('A free software media publishing platform #hashtagOther', $data['description']); | ||
195 | $this->assertEquals(['gnu', 'media', 'web', '.hidden', 'hashtag'], $data['tags']); | ||
196 | $this->assertEquals(false, $data['private']); | ||
197 | $this->assertEquals( | ||
198 | \DateTime::createFromFormat(\LinkDB::LINK_DATE_FORMAT, '20130614_184135'), | ||
199 | \DateTime::createFromFormat(\DateTime::ATOM, $data['created']) | ||
200 | ); | ||
201 | $this->assertEquals( | ||
202 | \DateTime::createFromFormat(\LinkDB::LINK_DATE_FORMAT, '20130615_184230'), | ||
203 | \DateTime::createFromFormat(\DateTime::ATOM, $data['updated']) | ||
204 | ); | ||
205 | } | ||
206 | |||
207 | /** | ||
208 | * Test link update on non existent link => ApiLinkNotFoundException. | ||
209 | * | ||
210 | * @expectedException Shaarli\Api\Exceptions\ApiLinkNotFoundException | ||
211 | * @expectedExceptionMessage Link not found | ||
212 | */ | ||
213 | public function testGetLink404() | ||
214 | { | ||
215 | $env = Environment::mock([ | ||
216 | 'REQUEST_METHOD' => 'PUT', | ||
217 | ]); | ||
218 | $request = Request::createFromEnvironment($env); | ||
219 | |||
220 | $this->controller->putLink($request, new Response(), ['id' => -1]); | ||
221 | } | ||
222 | } | ||
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 | ||
2 | use Shaarli\Config\ConfigManager; | ||
3 | |||
4 | /** | ||
5 | * PluginReadityourselfTest.php.php | ||
6 | */ | ||
7 | |||
8 | require_once 'plugins/readityourself/readityourself.php'; | ||
9 | |||
10 | /** | ||
11 | * Class PluginWallabagTest | ||
12 | * Unit test for the Wallabag plugin | ||
13 | */ | ||
14 | class 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/ReferenceHistory.php b/tests/utils/ReferenceHistory.php new file mode 100644 index 00000000..75cbb326 --- /dev/null +++ b/tests/utils/ReferenceHistory.php | |||
@@ -0,0 +1,82 @@ | |||
1 | <?php | ||
2 | |||
3 | /** | ||
4 | * Populates a reference history | ||
5 | */ | ||
6 | class ReferenceHistory | ||
7 | { | ||
8 | private $count; | ||
9 | |||
10 | private $history = []; | ||
11 | |||
12 | /** | ||
13 | * Populates the test DB with reference data | ||
14 | */ | ||
15 | public function __construct() | ||
16 | { | ||
17 | $this->addEntry( | ||
18 | History::DELETED, | ||
19 | DateTime::createFromFormat('Ymd_His', '20170303_121216'), | ||
20 | 124 | ||
21 | ); | ||
22 | |||
23 | $this->addEntry( | ||
24 | History::SETTINGS, | ||
25 | DateTime::createFromFormat('Ymd_His', '20170302_121215') | ||
26 | ); | ||
27 | |||
28 | $this->addEntry( | ||
29 | History::UPDATED, | ||
30 | DateTime::createFromFormat('Ymd_His', '20170301_121214'), | ||
31 | 123 | ||
32 | ); | ||
33 | |||
34 | $this->addEntry( | ||
35 | History::CREATED, | ||
36 | DateTime::createFromFormat('Ymd_His', '20170201_121214'), | ||
37 | 124 | ||
38 | ); | ||
39 | |||
40 | $this->addEntry( | ||
41 | History::CREATED, | ||
42 | DateTime::createFromFormat('Ymd_His', '20170101_121212'), | ||
43 | 123 | ||
44 | ); | ||
45 | } | ||
46 | |||
47 | /** | ||
48 | * Adds a new history entry | ||
49 | * | ||
50 | * @param string $event Event identifier | ||
51 | * @param DateTime $datetime creation date | ||
52 | * @param int $id optional: related link ID | ||
53 | */ | ||
54 | protected function addEntry($event, $datetime, $id = null) | ||
55 | { | ||
56 | $link = [ | ||
57 | 'event' => $event, | ||
58 | 'datetime' => $datetime, | ||
59 | 'id' => $id, | ||
60 | ]; | ||
61 | $this->history[] = $link; | ||
62 | $this->count++; | ||
63 | } | ||
64 | |||
65 | /** | ||
66 | * Writes data to the datastore | ||
67 | * | ||
68 | * @param string $filename write history content to. | ||
69 | */ | ||
70 | public function write($filename) | ||
71 | { | ||
72 | FileUtils::writeFlatDB($filename, $this->history); | ||
73 | } | ||
74 | |||
75 | /** | ||
76 | * Returns the number of links in the reference data | ||
77 | */ | ||
78 | public function count() | ||
79 | { | ||
80 | return $this->count; | ||
81 | } | ||
82 | } | ||
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/changetag.html b/tpl/default/changetag.html index 8d263a16..49dd20d9 100644 --- a/tpl/default/changetag.html +++ b/tpl/default/changetag.html | |||
@@ -11,7 +11,7 @@ | |||
11 | <h2 class="window-title">{"Manage tags"|t}</h2> | 11 | <h2 class="window-title">{"Manage tags"|t}</h2> |
12 | <form method="POST" action="#" name="changetag" id="changetag"> | 12 | <form method="POST" action="#" name="changetag" id="changetag"> |
13 | <div> | 13 | <div> |
14 | <input type="text" name="fromtag" placeholder="{'Tag'|t}" | 14 | <input type="text" name="fromtag" placeholder="{'Tag'|t}" value="{$fromtag}" |
15 | list="tagsList" autocomplete="off" class="awesomplete autofocus" data-minChars="1"> | 15 | list="tagsList" autocomplete="off" class="awesomplete autofocus" data-minChars="1"> |
16 | <datalist id="tagsList"> | 16 | <datalist id="tagsList"> |
17 | {loop="$tags"}<option>{$key}</option>{/loop} | 17 | {loop="$tags"}<option>{$key}</option>{/loop} |
@@ -31,6 +31,8 @@ | |||
31 | <input type="submit" value="{'Delete'|t}" name="deletetag" class="button button-red confirm-delete"> | 31 | <input type="submit" value="{'Delete'|t}" name="deletetag" class="button button-red confirm-delete"> |
32 | </div> | 32 | </div> |
33 | </form> | 33 | </form> |
34 | |||
35 | <p>You can also edit tags in the <a href="?do=taglist&sort=usage">tag list</a>.</p> | ||
34 | </div> | 36 | </div> |
35 | </div> | 37 | </div> |
36 | {include="page.footer"} | 38 | {include="page.footer"} |
diff --git a/tpl/default/configure.html b/tpl/default/configure.html index 12261487..76a1b9fd 100644 --- a/tpl/default/configure.html +++ b/tpl/default/configure.html | |||
@@ -34,7 +34,7 @@ | |||
34 | <div class="pure-u-lg-{$ratioLabel} pure-u-1"> | 34 | <div class="pure-u-lg-{$ratioLabel} pure-u-1"> |
35 | <div class="form-label"> | 35 | <div class="form-label"> |
36 | <label for="titleLink"> | 36 | <label for="titleLink"> |
37 | <span class="label-name">{'Title link'|t}</span><br> | 37 | <span class="label-name">{'Home link'|t}</span><br> |
38 | <span class="label-desc">{'Default value'|t}: ?</span> | 38 | <span class="label-desc">{'Default value'|t}: ?</span> |
39 | </label> | 39 | </label> |
40 | </div> | 40 | </div> |
@@ -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} · {'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> |
diff --git a/tpl/default/css/shaarli.css b/tpl/default/css/shaarli.css index 8fcd13af..28920648 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 | |||
59 | body, .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 | */ |
@@ -200,7 +211,7 @@ pre { | |||
200 | } | 211 | } |
201 | } | 212 | } |
202 | 213 | ||
203 | #search, #search-linklist { | 214 | #search, #search-linklist, #search-tagcloud { |
204 | text-align: center; | 215 | text-align: center; |
205 | width: 100%; | 216 | width: 100%; |
206 | } | 217 | } |
@@ -223,6 +234,7 @@ pre { | |||
223 | } | 234 | } |
224 | 235 | ||
225 | #search button, | 236 | #search button, |
237 | #search-tagcloud button, | ||
226 | #search-linklist button { | 238 | #search-linklist button { |
227 | background: transparent; | 239 | background: transparent; |
228 | border: none; | 240 | border: none; |
@@ -240,6 +252,9 @@ pre { | |||
240 | #search-linklist button:hover { | 252 | #search-linklist button:hover { |
241 | color: #fff; | 253 | color: #fff; |
242 | } | 254 | } |
255 | #search-tagcloud button:hover { | ||
256 | color: #d0d0d0; | ||
257 | } | ||
243 | 258 | ||
244 | #search-linklist { | 259 | #search-linklist { |
245 | padding: 5px 0; | 260 | padding: 5px 0; |
@@ -264,6 +279,19 @@ pre { | |||
264 | } | 279 | } |
265 | } | 280 | } |
266 | 281 | ||
282 | .subheader-form a.button { | ||
283 | color: #f5f5f5; | ||
284 | font-weight: bold; | ||
285 | text-decoration: none; | ||
286 | border: 2px solid #f5f5f5; | ||
287 | border-radius: 5px; | ||
288 | padding: 3px 10px; | ||
289 | } | ||
290 | |||
291 | .linklist-item-editbuttons .delete-checkbox { | ||
292 | display: none; | ||
293 | } | ||
294 | |||
267 | #header-login-form input[type="text"], #header-login-form input[type="password"] { | 295 | #header-login-form input[type="text"], #header-login-form input[type="password"] { |
268 | width: 200px; | 296 | width: 200px; |
269 | } | 297 | } |
@@ -504,7 +532,6 @@ pre { | |||
504 | color: #252525; | 532 | color: #252525; |
505 | text-decoration: none; | 533 | text-decoration: none; |
506 | vertical-align: middle; | 534 | vertical-align: middle; |
507 | font-family: Roboto Slab, Arial, sans-serif; | ||
508 | } | 535 | } |
509 | 536 | ||
510 | .linklist-item-title .linklist-link { | 537 | .linklist-item-title .linklist-link { |
@@ -560,7 +587,6 @@ pre { | |||
560 | .linklist-item-description { | 587 | .linklist-item-description { |
561 | position: relative; | 588 | position: relative; |
562 | padding: 10px; | 589 | padding: 10px; |
563 | font-family: Roboto Slab, Arial, sans-serif; | ||
564 | word-wrap: break-word; | 590 | word-wrap: break-word; |
565 | color: #252525; | 591 | color: #252525; |
566 | line-height: 1.3em; | 592 | line-height: 1.3em; |
@@ -725,10 +751,11 @@ pre { | |||
725 | .page-form a { | 751 | .page-form a { |
726 | color: #1b926c; | 752 | color: #1b926c; |
727 | font-weight: bold; | 753 | font-weight: bold; |
754 | text-decoration: none; | ||
728 | } | 755 | } |
729 | 756 | ||
730 | .page-form p { | 757 | .page-form p { |
731 | padding: 0 10px; | 758 | padding: 5px 10px; |
732 | margin: 0; | 759 | margin: 0; |
733 | } | 760 | } |
734 | 761 | ||
@@ -1044,7 +1071,7 @@ form[name="linkform"].page-form { | |||
1044 | } | 1071 | } |
1045 | 1072 | ||
1046 | #cloudtag, #cloudtag a { | 1073 | #cloudtag, #cloudtag a { |
1047 | color: #000; | 1074 | color: #252525; |
1048 | text-decoration: none; | 1075 | text-decoration: none; |
1049 | } | 1076 | } |
1050 | 1077 | ||
@@ -1053,6 +1080,42 @@ form[name="linkform"].page-form { | |||
1053 | } | 1080 | } |
1054 | 1081 | ||
1055 | /** | 1082 | /** |
1083 | * TAG LIST | ||
1084 | */ | ||
1085 | #taglist { | ||
1086 | padding: 0 10px; | ||
1087 | } | ||
1088 | |||
1089 | #taglist a { | ||
1090 | color: #252525; | ||
1091 | text-decoration: none; | ||
1092 | } | ||
1093 | |||
1094 | #taglist .count { | ||
1095 | display: inline-block; | ||
1096 | width: 35px; | ||
1097 | text-align: right; | ||
1098 | color: #7f7f7f; | ||
1099 | } | ||
1100 | |||
1101 | #taglist .rename-tag-form { | ||
1102 | display: none; | ||
1103 | } | ||
1104 | |||
1105 | #taglist .delete-tag { | ||
1106 | color: #ac2925; | ||
1107 | display: none; | ||
1108 | } | ||
1109 | |||
1110 | #taglist .rename-tag { | ||
1111 | color: #0b5ea6; | ||
1112 | } | ||
1113 | |||
1114 | #taglist .validate-rename-tag { | ||
1115 | color: #1b926c; | ||
1116 | } | ||
1117 | |||
1118 | /** | ||
1056 | * Picture wall CSS | 1119 | * Picture wall CSS |
1057 | */ | 1120 | */ |
1058 | #picwall_container { | 1121 | #picwall_container { |
@@ -1201,3 +1264,16 @@ form[name="linkform"].page-form { | |||
1201 | .pure-button { | 1264 | .pure-button { |
1202 | -moz-user-select: auto; | 1265 | -moz-user-select: auto; |
1203 | } | 1266 | } |
1267 | |||
1268 | .tag-sort { | ||
1269 | margin-top: 30px; | ||
1270 | text-align: center; | ||
1271 | } | ||
1272 | |||
1273 | .tag-sort a { | ||
1274 | display: inline-block; | ||
1275 | margin: 0 15px; | ||
1276 | color: white; | ||
1277 | text-decoration: none; | ||
1278 | font-weight: bold; | ||
1279 | } | ||
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/includes.html b/tpl/default/includes.html index 91c6ca3b..0350ef66 100644 --- a/tpl/default/includes.html +++ b/tpl/default/includes.html | |||
@@ -11,7 +11,7 @@ | |||
11 | <link type="text/css" rel="stylesheet" href="css/font-awesome.min.css" /> | 11 | <link type="text/css" rel="stylesheet" href="css/font-awesome.min.css" /> |
12 | <link type="text/css" rel="stylesheet" href="inc/awesomplete.css#" /> | 12 | <link type="text/css" rel="stylesheet" href="inc/awesomplete.css#" /> |
13 | <link type="text/css" rel="stylesheet" href="css/shaarli.css" /> | 13 | <link type="text/css" rel="stylesheet" href="css/shaarli.css" /> |
14 | {if="is_file('inc/user.css')"} | 14 | {if="is_file('data/user.css')"} |
15 | <link type="text/css" rel="stylesheet" href="data/user.css#" /> | 15 | <link type="text/css" rel="stylesheet" href="data/user.css#" /> |
16 | {/if} | 16 | {/if} |
17 | {loop="$plugins_includes.css_files"} | 17 | {loop="$plugins_includes.css_files"} |
diff --git a/tpl/default/install.html b/tpl/default/install.html index 663397ac..164d453b 100644 --- a/tpl/default/install.html +++ b/tpl/default/install.html | |||
@@ -45,24 +45,22 @@ | |||
45 | </div> | 45 | </div> |
46 | <div class="pure-u-lg-{$ratioInput} pure-u-1"> | 46 | <div class="pure-u-lg-{$ratioInput} pure-u-1"> |
47 | <div class="form-input"> | 47 | <div class="form-input"> |
48 | <input type="text" name="setpassword" id="password"> | 48 | <input type="password" name="setpassword" id="password"> |
49 | </div> | 49 | </div> |
50 | </div> | 50 | </div> |
51 | </div> | 51 | </div> |
52 | 52 | ||
53 | <div class="pure-g"> | 53 | <div class="pure-g"> |
54 | <div class="pure-u-lg-{$ratioLabel} pure-u-1 "> | 54 | <div class="pure-u-lg-{$ratioLabel} pure-u-1"> |
55 | <div class="form-label"> | 55 | <div class="form-label"> |
56 | <label> | 56 | <label for="title"> |
57 | <span class="label-name">{'Timezone'|t}</span> | 57 | <span class="label-name">{'Shaarli title'|t}</span> |
58 | </label> | 58 | </label> |
59 | </div> | 59 | </div> |
60 | </div> | 60 | </div> |
61 | <div class="pure-u-lg-{$ratioInput} pure-u-1 "> | 61 | <div class="pure-u-lg-{$ratioInput} pure-u-1"> |
62 | <div class="form-input"> | 62 | <div class="form-input"> |
63 | {ignore}FIXME! too hackish, needs to be fixed upstream{/ignore} | 63 | <input type="text" name="title" id="title" placeholder="{'My links'|t}"> |
64 | <div class="timezone" id="timezone-remove">{$timezone_html}</div> | ||
65 | <div class="timezone" id="timezone-add"></div> | ||
66 | </div> | 64 | </div> |
67 | </div> | 65 | </div> |
68 | </div> | 66 | </div> |
@@ -70,14 +68,36 @@ | |||
70 | <div class="pure-g"> | 68 | <div class="pure-g"> |
71 | <div class="pure-u-lg-{$ratioLabel} pure-u-1"> | 69 | <div class="pure-u-lg-{$ratioLabel} pure-u-1"> |
72 | <div class="form-label"> | 70 | <div class="form-label"> |
73 | <label for="title"> | 71 | <label> |
74 | <span class="label-name">{'Shaarli title'|t}</span> | 72 | <span class="label-name">{'Timezone'|t}</span><br> |
73 | <span class="label-desc">{'Continent'|t} · {'City'|t}</span> | ||
75 | </label> | 74 | </label> |
76 | </div> | 75 | </div> |
77 | </div> | 76 | </div> |
78 | <div class="pure-u-lg-{$ratioInput} pure-u-1"> | 77 | <div class="pure-u-lg-{$ratioInput} pure-u-1"> |
79 | <div class="form-input"> | 78 | <div class="form-input"> |
80 | <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> | ||
81 | </div> | 101 | </div> |
82 | </div> | 102 | </div> |
83 | </div> | 103 | </div> |
diff --git a/tpl/default/js/shaarli.js b/tpl/default/js/shaarli.js index 30d8ed6f..4ebb7815 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); |
@@ -213,14 +216,14 @@ window.onload = function () { | |||
213 | /** | 216 | /** |
214 | * Autofocus text fields | 217 | * Autofocus text fields |
215 | */ | 218 | */ |
216 | // ES6 syntax | 219 | var autofocusElements = document.querySelectorAll('.autofocus'); |
217 | let autofocusElements = document.querySelectorAll('.autofocus'); | 220 | var breakLoop = false; |
218 | for (let autofocusElement of autofocusElements) { | 221 | [].forEach.call(autofocusElements, function(autofocusElement) { |
219 | if (autofocusElement.value == '') { | 222 | if (autofocusElement.value == '' && ! breakLoop) { |
220 | autofocusElement.focus(); | 223 | autofocusElement.focus(); |
221 | break; | 224 | breakLoop = true; |
222 | } | 225 | } |
223 | } | 226 | }); |
224 | 227 | ||
225 | /** | 228 | /** |
226 | * Handle sub menus/forms | 229 | * Handle sub menus/forms |
@@ -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,8 +353,256 @@ 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 | } | ||
365 | |||
366 | /** | ||
367 | * Bulk actions | ||
368 | */ | ||
369 | var linkCheckboxes = document.querySelectorAll('.delete-checkbox'); | ||
370 | var bar = document.getElementById('actions'); | ||
371 | [].forEach.call(linkCheckboxes, function(checkbox) { | ||
372 | checkbox.style.display = 'block'; | ||
373 | checkbox.addEventListener('click', function(event) { | ||
374 | var count = 0; | ||
375 | var linkCheckedCheckboxes = document.querySelectorAll('.delete-checkbox:checked'); | ||
376 | [].forEach.call(linkCheckedCheckboxes, function(checkbox) { | ||
377 | count++; | ||
378 | }); | ||
379 | if (count == 0 && bar.classList.contains('open')) { | ||
380 | bar.classList.toggle('open'); | ||
381 | } else if (count > 0 && ! bar.classList.contains('open')) { | ||
382 | bar.classList.toggle('open'); | ||
383 | } | ||
384 | }); | ||
385 | }); | ||
386 | |||
387 | var deleteButton = document.getElementById('actions-delete'); | ||
388 | var token = document.querySelector('input[type="hidden"][name="token"]'); | ||
389 | if (deleteButton != null && token != null) { | ||
390 | deleteButton.addEventListener('click', function(event) { | ||
391 | event.preventDefault(); | ||
392 | |||
393 | var links = []; | ||
394 | var linkCheckedCheckboxes = document.querySelectorAll('.delete-checkbox:checked'); | ||
395 | [].forEach.call(linkCheckedCheckboxes, function(checkbox) { | ||
396 | links.push({ | ||
397 | 'id': checkbox.value, | ||
398 | 'title': document.querySelector('.linklist-item[data-id="'+ checkbox.value +'"] .linklist-link').innerHTML | ||
399 | }); | ||
400 | }); | ||
401 | |||
402 | var message = 'Are you sure you want to delete '+ links.length +' links?\n'; | ||
403 | message += 'This action is IRREVERSIBLE!\n\nTitles:\n'; | ||
404 | var ids = ''; | ||
405 | links.forEach(function(item) { | ||
406 | message += ' - '+ item['title'] +'\n'; | ||
407 | ids += item['id'] +'+'; | ||
408 | }); | ||
409 | |||
410 | if (window.confirm(message)) { | ||
411 | window.location = '?delete_link&lf_linkdate='+ ids +'&token='+ token.value; | ||
412 | } | ||
413 | }); | ||
414 | } | ||
415 | |||
416 | /** | ||
417 | * Tag list operations | ||
418 | * | ||
419 | * TODO: support error code in the backend for AJAX requests | ||
420 | */ | ||
421 | var existingTags = document.querySelector('input[name="taglist"]').value.split(' '); | ||
422 | var awesomepletes = []; | ||
423 | |||
424 | // Display/Hide rename form | ||
425 | var renameTagButtons = document.querySelectorAll('.rename-tag'); | ||
426 | [].forEach.call(renameTagButtons, function(rename) { | ||
427 | rename.addEventListener('click', function(event) { | ||
428 | event.preventDefault(); | ||
429 | var block = findParent(event.target, 'div', {'class': 'tag-list-item'}); | ||
430 | var form = block.querySelector('.rename-tag-form'); | ||
431 | if (form.style.display == 'none' || form.style.display == '') { | ||
432 | form.style.display = 'block'; | ||
433 | } else { | ||
434 | form.style.display = 'none'; | ||
435 | } | ||
436 | block.querySelector('input').focus(); | ||
437 | }); | ||
438 | }); | ||
439 | |||
440 | // Rename a tag with an AJAX request | ||
441 | var renameTagSubmits = document.querySelectorAll('.validate-rename-tag'); | ||
442 | [].forEach.call(renameTagSubmits, function(rename) { | ||
443 | rename.addEventListener('click', function(event) { | ||
444 | event.preventDefault(); | ||
445 | var block = findParent(event.target, 'div', {'class': 'tag-list-item'}); | ||
446 | var input = block.querySelector('.rename-tag-input'); | ||
447 | var totag = input.value.replace('/"/g', '\\"'); | ||
448 | if (totag.trim() == '') { | ||
449 | return; | ||
450 | } | ||
451 | var fromtag = block.getAttribute('data-tag'); | ||
452 | var token = document.getElementById('token').value; | ||
453 | |||
454 | xhr = new XMLHttpRequest(); | ||
455 | xhr.open('POST', '?do=changetag'); | ||
456 | xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); | ||
457 | xhr.onload = function() { | ||
458 | if (xhr.status !== 200) { | ||
459 | alert('An error occurred. Return code: '+ xhr.status); | ||
460 | location.reload(); | ||
461 | } else { | ||
462 | block.setAttribute('data-tag', totag); | ||
463 | input.setAttribute('name', totag); | ||
464 | input.setAttribute('value', totag); | ||
465 | findParent(input, 'div', {'class': 'rename-tag-form'}).style.display = 'none'; | ||
466 | block.querySelector('a.tag-link').innerHTML = htmlEntities(totag); | ||
467 | block.querySelector('a.tag-link').setAttribute('href', '?searchtags='+ encodeURIComponent(totag)); | ||
468 | block.querySelector('a.rename-tag').setAttribute('href', '?do=changetag&fromtag='+ encodeURIComponent(totag)); | ||
469 | |||
470 | // Refresh awesomplete values | ||
471 | for (var key in existingTags) { | ||
472 | if (existingTags[key] == fromtag) { | ||
473 | existingTags[key] = totag; | ||
474 | } | ||
475 | } | ||
476 | awesomepletes = updateAwesompleteList('.rename-tag-input', existingTags, awesomepletes); | ||
477 | } | ||
478 | }; | ||
479 | xhr.send('renametag=1&fromtag='+ encodeURIComponent(fromtag) +'&totag='+ encodeURIComponent(totag) +'&token='+ token); | ||
480 | refreshToken(); | ||
481 | }); | ||
482 | }); | ||
483 | |||
484 | // Validate input with enter key | ||
485 | var renameTagInputs = document.querySelectorAll('.rename-tag-input'); | ||
486 | [].forEach.call(renameTagInputs, function(rename) { | ||
487 | |||
488 | rename.addEventListener('keypress', function(event) { | ||
489 | if (event.keyCode === 13) { // enter | ||
490 | findParent(event.target, 'div', {'class': 'tag-list-item'}).querySelector('.validate-rename-tag').click(); | ||
491 | } | ||
492 | }); | ||
493 | }); | ||
494 | |||
495 | // Delete a tag with an AJAX query (alert popup confirmation) | ||
496 | var deleteTagButtons = document.querySelectorAll('.delete-tag'); | ||
497 | [].forEach.call(deleteTagButtons, function(rename) { | ||
498 | rename.style.display = 'inline'; | ||
499 | rename.addEventListener('click', function(event) { | ||
500 | event.preventDefault(); | ||
501 | var block = findParent(event.target, 'div', {'class': 'tag-list-item'}); | ||
502 | var tag = block.getAttribute('data-tag'); | ||
503 | var token = document.getElementById('token').value; | ||
504 | |||
505 | if (confirm('Are you sure you want to delete the tag "'+ tag +'"?')) { | ||
506 | xhr = new XMLHttpRequest(); | ||
507 | xhr.open('POST', '?do=changetag'); | ||
508 | xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); | ||
509 | xhr.onload = function() { | ||
510 | block.remove(); | ||
511 | }; | ||
512 | xhr.send(encodeURI('deletetag=1&fromtag='+ tag +'&token='+ token)); | ||
513 | refreshToken(); | ||
514 | } | ||
515 | }); | ||
516 | }); | ||
517 | |||
518 | updateAwesompleteList('.rename-tag-input', document.querySelector('input[name="taglist"]').value.split(' '), awesomepletes); | ||
369 | }; | 519 | }; |
370 | 520 | ||
521 | /** | ||
522 | * Find a parent element according to its tag and its attributes | ||
523 | * | ||
524 | * @param element Element where to start the search | ||
525 | * @param tagName Expected parent tag name | ||
526 | * @param attributes Associative array of expected attributes (name=>value). | ||
527 | * | ||
528 | * @returns Found element or null. | ||
529 | */ | ||
530 | function findParent(element, tagName, attributes) | ||
531 | { | ||
532 | while (element) { | ||
533 | if (element.tagName.toLowerCase() == tagName) { | ||
534 | var match = true; | ||
535 | for (var key in attributes) { | ||
536 | if (! element.hasAttribute(key) | ||
537 | || (attributes[key] != '' && element.getAttribute(key).indexOf(attributes[key]) == -1) | ||
538 | ) { | ||
539 | match = false; | ||
540 | break; | ||
541 | } | ||
542 | } | ||
543 | |||
544 | if (match) { | ||
545 | return element; | ||
546 | } | ||
547 | } | ||
548 | element = element.parentElement; | ||
549 | } | ||
550 | return null; | ||
551 | } | ||
552 | |||
553 | /** | ||
554 | * Ajax request to refresh the CSRF token. | ||
555 | */ | ||
556 | function refreshToken() | ||
557 | { | ||
558 | var xhr = new XMLHttpRequest(); | ||
559 | xhr.open('GET', '?do=token'); | ||
560 | xhr.onload = function() { | ||
561 | var token = document.getElementById('token'); | ||
562 | token.setAttribute('value', xhr.responseText); | ||
563 | }; | ||
564 | xhr.send(); | ||
565 | } | ||
566 | |||
567 | /** | ||
568 | * Update awesomplete list of tag for all elements matching the given selector | ||
569 | * | ||
570 | * @param selector CSS selector | ||
571 | * @param tags Array of tags | ||
572 | * @param instances List of existing awesomplete instances | ||
573 | */ | ||
574 | function updateAwesompleteList(selector, tags, instances) | ||
575 | { | ||
576 | // First load: create Awesomplete instances | ||
577 | if (instances.length == 0) { | ||
578 | var elements = document.querySelectorAll(selector); | ||
579 | [].forEach.call(elements, function (element) { | ||
580 | instances.push(new Awesomplete( | ||
581 | element, | ||
582 | {'list': tags} | ||
583 | )); | ||
584 | }); | ||
585 | } else { | ||
586 | // Update awesomplete tag list | ||
587 | for (var key in instances) { | ||
588 | instances[key].list = tags; | ||
589 | } | ||
590 | } | ||
591 | return instances; | ||
592 | } | ||
593 | |||
594 | /** | ||
595 | * html_entities in JS | ||
596 | * | ||
597 | * @see http://stackoverflow.com/questions/18749591/encode-html-entities-in-javascript | ||
598 | */ | ||
599 | function htmlEntities(str) | ||
600 | { | ||
601 | return str.replace(/[\u00A0-\u9999<>\&]/gim, function(i) { | ||
602 | return '&#'+i.charCodeAt(0)+';'; | ||
603 | }); | ||
604 | } | ||
605 | |||
371 | function activateFirefoxSocial(node) { | 606 | function activateFirefoxSocial(node) { |
372 | var loc = location.href; | 607 | var loc = location.href; |
373 | var baseURL = loc.substring(0, loc.lastIndexOf("/")); | 608 | var baseURL = loc.substring(0, loc.lastIndexOf("/")); |
@@ -391,3 +626,28 @@ function activateFirefoxSocial(node) { | |||
391 | var activate = new CustomEvent("ActivateSocialFeature"); | 626 | var activate = new CustomEvent("ActivateSocialFeature"); |
392 | node.dispatchEvent(activate); | 627 | node.dispatchEvent(activate); |
393 | } | 628 | } |
629 | |||
630 | /** | ||
631 | * Add the class 'hidden' to city options not attached to the current selected continent. | ||
632 | * | ||
633 | * @param cities List of <option> elements | ||
634 | * @param currentContinent Current selected continent | ||
635 | * @param reset Set to true to reset the selected value | ||
636 | */ | ||
637 | function hideTimezoneCities(cities, currentContinent) { | ||
638 | var first = true; | ||
639 | if (reset == null) { | ||
640 | reset = false; | ||
641 | } | ||
642 | [].forEach.call(cities, function (option) { | ||
643 | if (option.getAttribute('data-continent') != currentContinent) { | ||
644 | option.className = 'hidden'; | ||
645 | } else { | ||
646 | option.className = ''; | ||
647 | if (reset === true && first === true) { | ||
648 | option.setAttribute('selected', 'selected'); | ||
649 | first = false; | ||
650 | } | ||
651 | } | ||
652 | }); | ||
653 | } | ||
diff --git a/tpl/default/linklist.html b/tpl/default/linklist.html index 57ef4567..6a4e14a6 100644 --- a/tpl/default/linklist.html +++ b/tpl/default/linklist.html | |||
@@ -15,6 +15,8 @@ | |||
15 | {/if} | 15 | {/if} |
16 | </div> | 16 | </div> |
17 | 17 | ||
18 | <input type="hidden" name="token" value="{$token}"> | ||
19 | |||
18 | <div id="search-linklist"> | 20 | <div id="search-linklist"> |
19 | 21 | ||
20 | <div class="pure-g"> | 22 | <div class="pure-g"> |
@@ -121,7 +123,7 @@ | |||
121 | <div class="pure-u-lg-20-24 pure-u-22-24"> | 123 | <div class="pure-u-lg-20-24 pure-u-22-24"> |
122 | {loop="links"} | 124 | {loop="links"} |
123 | <div class="anchor" id="{$value.shorturl}"></div> | 125 | <div class="anchor" id="{$value.shorturl}"></div> |
124 | <div class="linklist-item{if="$value.class"} {$value.class}{/if}"> | 126 | <div class="linklist-item linklist-item{if="$value.class"} {$value.class}{/if}" data-id="{$value.id}"> |
125 | 127 | ||
126 | <div class="linklist-item-title"> | 128 | <div class="linklist-item-title"> |
127 | {if="isLoggedIn()"} | 129 | {if="isLoggedIn()"} |
@@ -129,6 +131,7 @@ | |||
129 | {if="$value.private"} | 131 | {if="$value.private"} |
130 | <span class="label label-private">{'Private'|t}</span> | 132 | <span class="label label-private">{'Private'|t}</span> |
131 | {/if} | 133 | {/if} |
134 | <input type="checkbox" class="delete-checkbox" value="{$value.id}"> | ||
132 | <!-- FIXME! JS translation --> | 135 | <!-- FIXME! JS translation --> |
133 | <a href="?edit_link={$value.id}" title="{'Edit'|t}"><i class="fa fa-pencil-square-o edit-link"></i></a> | 136 | <a href="?edit_link={$value.id}" title="{'Edit'|t}"><i class="fa fa-pencil-square-o edit-link"></i></a> |
134 | <a href="#" title="{'Fold'|t}" class="fold-button"><i class="fa fa-chevron-up"></i></a> | 137 | <a href="#" title="{'Fold'|t}" class="fold-button"><i class="fa fa-chevron-up"></i></a> |
diff --git a/tpl/default/page.footer.html b/tpl/default/page.footer.html index 77fc65dd..02fc7642 100644 --- a/tpl/default/page.footer.html +++ b/tpl/default/page.footer.html | |||
@@ -16,6 +16,9 @@ | |||
16 | </div> | 16 | </div> |
17 | <div class="pure-u-2-24"></div> | 17 | <div class="pure-u-2-24"></div> |
18 | </div> | 18 | </div> |
19 | |||
20 | <input type="hidden" name="token" value="{$token}" id="token" /> | ||
21 | |||
19 | {loop="$plugins_footer.endofpage"} | 22 | {loop="$plugins_footer.endofpage"} |
20 | {$value} | 23 | {$value} |
21 | {/loop} | 24 | {/loop} |
diff --git a/tpl/default/page.header.html b/tpl/default/page.header.html index 9388ef79..6c71a718 100644 --- a/tpl/default/page.header.html +++ b/tpl/default/page.header.html | |||
@@ -122,6 +122,13 @@ | |||
122 | </div> | 122 | </div> |
123 | </div> | 123 | </div> |
124 | </div> | 124 | </div> |
125 | <div id="actions" class="subheader-form"> | ||
126 | <div class="pure-g"> | ||
127 | <div class="pure-u-1"> | ||
128 | <a href="" id="actions-delete" class="button">Delete</a> | ||
129 | </div> | ||
130 | </div> | ||
131 | </div> | ||
125 | {if="!isLoggedIn()"} | 132 | {if="!isLoggedIn()"} |
126 | <form method="post" name="loginform"> | 133 | <form method="post" name="loginform"> |
127 | <div class="subheader-form" id="header-login-form"> | 134 | <div class="subheader-form" id="header-login-form"> |
diff --git a/tpl/default/tagcloud.html b/tpl/default/tag.cloud.html index 53c31748..59aa2ee0 100644 --- a/tpl/default/tagcloud.html +++ b/tpl/default/tag.cloud.html | |||
@@ -6,12 +6,32 @@ | |||
6 | <body> | 6 | <body> |
7 | {include="page.header"} | 7 | {include="page.header"} |
8 | 8 | ||
9 | {include="tag.sort"} | ||
10 | |||
9 | <div class="pure-g"> | 11 | <div class="pure-g"> |
10 | <div class="pure-u-lg-1-6 pure-u-1-24"></div> | 12 | <div class="pure-u-lg-1-6 pure-u-1-24"></div> |
11 | <div class="pure-u-lg-2-3 pure-u-22-24 page-form page-visitor"> | 13 | <div class="pure-u-lg-2-3 pure-u-22-24 page-form page-visitor"> |
12 | {$countTags=count($tags)} | 14 | {$countTags=count($tags)} |
13 | <h2 class="window-title">{'Tag cloud'|t} - {$countTags} {'tags'|t}</h2> | 15 | <h2 class="window-title">{'Tag cloud'|t} - {$countTags} {'tags'|t}</h2> |
14 | 16 | ||
17 | <div id="search-tagcloud" class="pure-g"> | ||
18 | <div class="pure-u-lg-1-4"></div> | ||
19 | <div class="pure-u-1 pure-u-lg-1-2"> | ||
20 | <form method="GET"> | ||
21 | <input type="hidden" name="do" value="tagcloud"> | ||
22 | <input type="text" name="searchtags" placeholder="{'Filter by tag'|t}" | ||
23 | {if="!empty($search_tags)"} | ||
24 | value="{$search_tags}" | ||
25 | {/if} | ||
26 | autocomplete="off" data-multiple data-autofirst data-minChars="1" | ||
27 | data-list="{loop="$tags"}{$key}, {/loop}" | ||
28 | > | ||
29 | <button type="submit" class="search-button"><i class="fa fa-search"></i></button> | ||
30 | </form> | ||
31 | </div> | ||
32 | <div class="pure-u-lg-1-4"></div> | ||
33 | </div> | ||
34 | |||
15 | <div id="plugin_zone_start_tagcloud" class="plugin_zone"> | 35 | <div id="plugin_zone_start_tagcloud" class="plugin_zone"> |
16 | {loop="$plugin_start_zone"} | 36 | {loop="$plugin_start_zone"} |
17 | {$value} | 37 | {$value} |
@@ -21,7 +41,7 @@ | |||
21 | <div id="cloudtag"> | 41 | <div id="cloudtag"> |
22 | {loop="tags"} | 42 | {loop="tags"} |
23 | <a href="?searchtags={$key|urlencode}" style="font-size:{$value.size}em;">{$key}</a | 43 | <a href="?searchtags={$key|urlencode}" style="font-size:{$value.size}em;">{$key}</a |
24 | ><span class="count">{$value.count}</span> | 44 | ><a href="?addtag={$key|urlencode}" title="{'Filter by tag'|t}" class="count">{$value.count}</a> |
25 | {loop="$value.tag_plugin"} | 45 | {loop="$value.tag_plugin"} |
26 | {$value} | 46 | {$value} |
27 | {/loop} | 47 | {/loop} |
@@ -36,6 +56,8 @@ | |||
36 | </div> | 56 | </div> |
37 | </div> | 57 | </div> |
38 | 58 | ||
59 | {include="tag.sort"} | ||
60 | |||
39 | {include="page.footer"} | 61 | {include="page.footer"} |
40 | </body> | 62 | </body> |
41 | </html> | 63 | </html> |
diff --git a/tpl/default/tag.list.html b/tpl/default/tag.list.html new file mode 100644 index 00000000..62e2e7c6 --- /dev/null +++ b/tpl/default/tag.list.html | |||
@@ -0,0 +1,86 @@ | |||
1 | <!DOCTYPE html> | ||
2 | <html> | ||
3 | <head> | ||
4 | {include="includes"} | ||
5 | </head> | ||
6 | <body> | ||
7 | {include="page.header"} | ||
8 | |||
9 | {include="tag.sort"} | ||
10 | |||
11 | <div class="pure-g"> | ||
12 | <div class="pure-u-lg-1-6 pure-u-1-24"></div> | ||
13 | <div class="pure-u-lg-2-3 pure-u-22-24 page-form page-visitor"> | ||
14 | {$countTags=count($tags)} | ||
15 | <h2 class="window-title">{'Tag list'|t} - {$countTags} {'tags'|t}</h2> | ||
16 | |||
17 | <div id="search-tagcloud" class="pure-g"> | ||
18 | <div class="pure-u-lg-1-4"></div> | ||
19 | <div class="pure-u-1 pure-u-lg-1-2"> | ||
20 | <form method="GET"> | ||
21 | <input type="hidden" name="do" value="taglist"> | ||
22 | <input type="text" name="searchtags" placeholder="{'Filter by tag'|t}" | ||
23 | {if="!empty($search_tags)"} | ||
24 | value="{$search_tags}" | ||
25 | {/if} | ||
26 | autocomplete="off" data-multiple data-autofirst data-minChars="1" | ||
27 | data-list="{loop="$tags"}{$key}, {/loop}" | ||
28 | > | ||
29 | <button type="submit" class="search-button"><i class="fa fa-search"></i></button> | ||
30 | </form> | ||
31 | </div> | ||
32 | <div class="pure-u-lg-1-4"></div> | ||
33 | </div> | ||
34 | |||
35 | <div id="plugin_zone_start_tagcloud" class="plugin_zone"> | ||
36 | {loop="$plugin_start_zone"} | ||
37 | {$value} | ||
38 | {/loop} | ||
39 | </div> | ||
40 | |||
41 | <div id="taglist"> | ||
42 | {loop="tags"} | ||
43 | <div class="tag-list-item pure-g" data-tag="{$key}"> | ||
44 | <div class="pure-u-1"> | ||
45 | {if="isLoggedIn()===true"} | ||
46 | <a href="#" class="delete-tag"><i class="fa fa-trash"></i></a> | ||
47 | <a href="?do=changetag&fromtag={$key|urlencode}" class="rename-tag"> | ||
48 | <i class="fa fa-pencil-square-o {$key}"></i> | ||
49 | </a> | ||
50 | {/if} | ||
51 | |||
52 | <a href="?addtag={$key|urlencode}" title="{'Filter by tag'|t}" class="count">{$value}</a> | ||
53 | <a href="?searchtags={$key|urlencode}" class="tag-link">{$key}</a> | ||
54 | |||
55 | {loop="$value.tag_plugin"} | ||
56 | {$value} | ||
57 | {/loop} | ||
58 | </div> | ||
59 | {if="isLoggedIn()===true"} | ||
60 | <div class="rename-tag-form pure-u-1"> | ||
61 | <input type="text" name="{$key}" value="{$key}" class="rename-tag-input" /> | ||
62 | <a href="#" class="validate-rename-tag"><i class="fa fa-check"></i></a> | ||
63 | </div> | ||
64 | {/if} | ||
65 | </div> | ||
66 | {/loop} | ||
67 | </div> | ||
68 | |||
69 | <div id="plugin_zone_end_tagcloud" class="plugin_zone"> | ||
70 | {loop="$plugin_end_zone"} | ||
71 | {$value} | ||
72 | {/loop} | ||
73 | </div> | ||
74 | </div> | ||
75 | </div> | ||
76 | |||
77 | {if="isLoggedIn()===true"} | ||
78 | <input type="hidden" name="taglist" value="{loop="$tags"}{$key} {/loop}" | ||
79 | {/if} | ||
80 | |||
81 | {include="tag.sort"} | ||
82 | |||
83 | {include="page.footer"} | ||
84 | </body> | ||
85 | </html> | ||
86 | |||
diff --git a/tpl/default/tag.sort.html b/tpl/default/tag.sort.html new file mode 100644 index 00000000..89acda0d --- /dev/null +++ b/tpl/default/tag.sort.html | |||
@@ -0,0 +1,8 @@ | |||
1 | <div class="pure-g"> | ||
2 | <div class="pure-u-1 pure-alert pure-alert-success tag-sort"> | ||
3 | {'Sort by:'|t} | ||
4 | <a href="?do=tagcloud" title="cloud">{'Cloud'|t}</a> · | ||
5 | <a href="?do=taglist&sort=usage" title="cloud">{'Most used'|t}</a> · | ||
6 | <a href="?do=taglist&sort=alpha" title="cloud">{'Alphabetical'|t}</a> | ||
7 | </div> | ||
8 | </div> \ No newline at end of file | ||
diff --git a/tpl/vintage/configure.html b/tpl/vintage/configure.html index 704389c5..479284eb 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"> |
@@ -15,7 +14,7 @@ | |||
15 | </tr> | 14 | </tr> |
16 | 15 | ||
17 | <tr> | 16 | <tr> |
18 | <td><b>Title link:</b></td> | 17 | <td><b>Home link:</b></td> |
19 | <td><input type="text" name="titleLink" id="titleLink" size="50" value="{$titleLink}"><br/><label | 18 | <td><input type="text" name="titleLink" id="titleLink" size="50" value="{$titleLink}"><br/><label |
20 | for="titleLink">(default value is: ?)</label></td> | 19 | for="titleLink">(default value is: ?)</label></td> |
21 | </tr> | 20 | </tr> |
@@ -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> |
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"> Notify me when a new release is ready</label></td> | 39 | <input type="checkbox" name="updateCheck" id="updateCheck" checked="checked"><label for="updateCheck"> 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 @@ | |||
1 | window.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 | */ | ||
19 | function 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} |
diff --git a/tpl/vintage/page.header.html b/tpl/vintage/page.header.html index cce61ec4..8a58844e 100644 --- a/tpl/vintage/page.header.html +++ b/tpl/vintage/page.header.html | |||
@@ -1,5 +1,5 @@ | |||
1 | 1 | ||
2 | <div id="logo" title="Share your links !" onclick="document.location='?';"></div> | 2 | <div id="logo" title="Share your links !" onclick="document.location='{$titleLink}';"></div> |
3 | 3 | ||
4 | <div id="linkcount" class="nomobile"> | 4 | <div id="linkcount" class="nomobile"> |
5 | {if="!empty($linkcount)"}{$linkcount} links{/if}<br> | 5 | {if="!empty($linkcount)"}{$linkcount} links{/if}<br> |
@@ -16,7 +16,7 @@ | |||
16 | {if="!empty($_GET['source']) && $_GET['source']=='bookmarklet'"} | 16 | {if="!empty($_GET['source']) && $_GET['source']=='bookmarklet'"} |
17 | {ignore} When called as a popup from bookmarklet, do not display menu. {/ignore} | 17 | {ignore} When called as a popup from bookmarklet, do not display menu. {/ignore} |
18 | {else} | 18 | {else} |
19 | <li><a href="?" class="nomobile">Home</a></li> | 19 | <li><a href="{$titleLink}" class="nomobile">Home</a></li> |
20 | {if="isLoggedIn()"} | 20 | {if="isLoggedIn()"} |
21 | <li><a href="?do=logout">Logout</a></li> | 21 | <li><a href="?do=logout">Logout</a></li> |
22 | <li><a href="?do=tools">Tools</a></li> | 22 | <li><a href="?do=tools">Tools</a></li> |
diff --git a/tpl/vintage/tagcloud.html b/tpl/vintage/tag.cloud.html index 05e45273..d93bf4f9 100644 --- a/tpl/vintage/tagcloud.html +++ b/tpl/vintage/tag.cloud.html | |||
@@ -12,7 +12,7 @@ | |||
12 | 12 | ||
13 | <div id="cloudtag"> | 13 | <div id="cloudtag"> |
14 | {loop="$tags"} | 14 | {loop="$tags"} |
15 | <span class="count">{$value.count}</span><a | 15 | <a href="?addtag={$key|urlencode}" class="count">{$value.count}</a><a |
16 | href="?searchtags={$key|urlencode}" style="font-size:{$value.size}em;">{$key}</a> | 16 | href="?searchtags={$key|urlencode}" style="font-size:{$value.size}em;">{$key}</a> |
17 | {loop="$value.tag_plugin"} | 17 | {loop="$value.tag_plugin"} |
18 | {$value} | 18 | {$value} |