aboutsummaryrefslogtreecommitdiffhomepage
path: root/application
diff options
context:
space:
mode:
Diffstat (limited to 'application')
-rw-r--r--application/ApplicationUtils.php92
-rw-r--r--application/FileUtils.php8
-rw-r--r--application/History.php12
-rw-r--r--application/Languages.php16
-rw-r--r--application/Router.php55
-rw-r--r--application/Thumbnailer.php130
-rw-r--r--application/Utils.php16
-rw-r--r--application/api/ApiMiddleware.php10
-rw-r--r--application/api/ApiUtils.php6
-rw-r--r--application/api/controllers/ApiController.php9
-rw-r--r--application/api/controllers/HistoryController.php (renamed from application/api/controllers/History.php)5
-rw-r--r--application/api/controllers/Info.php4
-rw-r--r--application/api/controllers/Tags.php1
-rw-r--r--application/api/exceptions/ApiException.php8
-rw-r--r--application/api/exceptions/ApiLinkNotFoundException.php3
-rw-r--r--application/api/exceptions/ApiTagNotFoundException.php3
-rw-r--r--application/bookmark/LinkDB.php (renamed from application/LinkDB.php)89
-rw-r--r--application/bookmark/LinkFilter.php (renamed from application/LinkFilter.php)67
-rw-r--r--application/bookmark/LinkUtils.php (renamed from application/LinkUtils.php)11
-rw-r--r--application/bookmark/exception/LinkNotFoundException.php15
-rw-r--r--application/config/ConfigJson.php2
-rw-r--r--application/config/ConfigManager.php58
-rw-r--r--application/config/ConfigPhp.php16
-rw-r--r--application/config/ConfigPlugin.php3
-rw-r--r--application/exceptions/IOException.php5
-rw-r--r--application/feed/Cache.php (renamed from application/Cache.php)0
-rw-r--r--application/feed/CachedPage.php (renamed from application/CachedPage.php)5
-rw-r--r--application/feed/FeedBuilder.php (renamed from application/FeedBuilder.php)43
-rw-r--r--application/http/Base64Url.php (renamed from application/Base64Url.php)9
-rw-r--r--application/http/HttpUtils.php (renamed from application/HttpUtils.php)34
-rw-r--r--application/http/Url.php (renamed from application/Url.php)108
-rw-r--r--application/http/UrlUtils.php88
-rw-r--r--application/netscape/NetscapeBookmarkUtils.php (renamed from application/NetscapeBookmarkUtils.php)45
-rw-r--r--application/plugin/PluginManager.php (renamed from application/PluginManager.php)42
-rw-r--r--application/plugin/exception/PluginFileNotFoundException.php23
-rw-r--r--application/render/PageBuilder.php (renamed from application/PageBuilder.php)60
-rw-r--r--application/render/ThemeUtils.php (renamed from application/ThemeUtils.php)2
-rw-r--r--application/security/LoginManager.php1
-rw-r--r--application/updater/Updater.php (renamed from application/Updater.php)168
-rw-r--r--application/updater/UpdaterUtils.php39
-rw-r--r--application/updater/exception/UpdaterException.php60
41 files changed, 885 insertions, 486 deletions
diff --git a/application/ApplicationUtils.php b/application/ApplicationUtils.php
index 911873a0..7fe3cb32 100644
--- a/application/ApplicationUtils.php
+++ b/application/ApplicationUtils.php
@@ -1,4 +1,9 @@
1<?php 1<?php
2namespace Shaarli;
3
4use Exception;
5use Shaarli\Config\ConfigManager;
6
2/** 7/**
3 * Shaarli (application) utilities 8 * Shaarli (application) utilities
4 */ 9 */
@@ -24,7 +29,7 @@ class ApplicationUtils
24 * 29 *
25 * @return mixed the version code from the repository if available, else 'false' 30 * @return mixed the version code from the repository if available, else 'false'
26 */ 31 */
27 public static function getLatestGitVersionCode($url, $timeout=2) 32 public static function getLatestGitVersionCode($url, $timeout = 2)
28 { 33 {
29 list($headers, $data) = get_http_response($url, $timeout); 34 list($headers, $data) = get_http_response($url, $timeout);
30 35
@@ -51,7 +56,7 @@ class ApplicationUtils
51 return false; 56 return false;
52 } 57 }
53 } else { 58 } else {
54 if (! is_file($remote)) { 59 if (!is_file($remote)) {
55 return false; 60 return false;
56 } 61 }
57 $data = file_get_contents($remote); 62 $data = file_get_contents($remote);
@@ -86,17 +91,18 @@ class ApplicationUtils
86 * 91 *
87 * @return mixed the new version code if available and greater, else 'false' 92 * @return mixed the new version code if available and greater, else 'false'
88 */ 93 */
89 public static function checkUpdate($currentVersion, 94 public static function checkUpdate(
90 $updateFile, 95 $currentVersion,
91 $checkInterval, 96 $updateFile,
92 $enableCheck, 97 $checkInterval,
93 $isLoggedIn, 98 $enableCheck,
94 $branch='stable') 99 $isLoggedIn,
95 { 100 $branch = 'stable'
101 ) {
96 // Do not check versions for visitors 102 // Do not check versions for visitors
97 // Do not check if the user doesn't want to 103 // Do not check if the user doesn't want to
98 // Do not check with dev version 104 // Do not check with dev version
99 if (! $isLoggedIn || empty($enableCheck) || $currentVersion === 'dev') { 105 if (!$isLoggedIn || empty($enableCheck) || $currentVersion === 'dev') {
100 return false; 106 return false;
101 } 107 }
102 108
@@ -110,7 +116,7 @@ class ApplicationUtils
110 return false; 116 return false;
111 } 117 }
112 118
113 if (! in_array($branch, self::$GIT_BRANCHES)) { 119 if (!in_array($branch, self::$GIT_BRANCHES)) {
114 throw new Exception( 120 throw new Exception(
115 'Invalid branch selected for updates: "' . $branch . '"' 121 'Invalid branch selected for updates: "' . $branch . '"'
116 ); 122 );
@@ -122,7 +128,7 @@ class ApplicationUtils
122 self::$GIT_URL . '/' . $branch . '/' . self::$VERSION_FILE 128 self::$GIT_URL . '/' . $branch . '/' . self::$VERSION_FILE
123 ); 129 );
124 130
125 if (! $latestVersion) { 131 if (!$latestVersion) {
126 // Only update the file's modification date 132 // Only update the file's modification date
127 file_put_contents($updateFile, $currentVersion); 133 file_put_contents($updateFile, $currentVersion);
128 return false; 134 return false;
@@ -151,9 +157,9 @@ class ApplicationUtils
151 if (version_compare($curVersion, $minVersion) < 0) { 157 if (version_compare($curVersion, $minVersion) < 0) {
152 $msg = t( 158 $msg = t(
153 'Your PHP version is obsolete!' 159 'Your PHP version is obsolete!'
154 . ' Shaarli requires at least PHP %s, and thus cannot run.' 160 . ' Shaarli requires at least PHP %s, and thus cannot run.'
155 . ' Your PHP version has known security vulnerabilities and should be' 161 . ' Your PHP version has known security vulnerabilities and should be'
156 . ' updated as soon as possible.' 162 . ' updated as soon as possible.'
157 ); 163 );
158 throw new Exception(sprintf($msg, $minVersion)); 164 throw new Exception(sprintf($msg, $minVersion));
159 } 165 }
@@ -173,50 +179,50 @@ class ApplicationUtils
173 179
174 // Check script and template directories are readable 180 // Check script and template directories are readable
175 foreach (array( 181 foreach (array(
176 'application', 182 'application',
177 'inc', 183 'inc',
178 'plugins', 184 'plugins',
179 $rainTplDir, 185 $rainTplDir,
180 $rainTplDir.'/'.$conf->get('resource.theme'), 186 $rainTplDir . '/' . $conf->get('resource.theme'),
181 ) as $path) { 187 ) as $path) {
182 if (! is_readable(realpath($path))) { 188 if (!is_readable(realpath($path))) {
183 $errors[] = '"'.$path.'" '. t('directory is not readable'); 189 $errors[] = '"' . $path . '" ' . t('directory is not readable');
184 } 190 }
185 } 191 }
186 192
187 // Check cache and data directories are readable and writable 193 // Check cache and data directories are readable and writable
188 foreach (array( 194 foreach (array(
189 $conf->get('resource.thumbnails_cache'), 195 $conf->get('resource.thumbnails_cache'),
190 $conf->get('resource.data_dir'), 196 $conf->get('resource.data_dir'),
191 $conf->get('resource.page_cache'), 197 $conf->get('resource.page_cache'),
192 $conf->get('resource.raintpl_tmp'), 198 $conf->get('resource.raintpl_tmp'),
193 ) as $path) { 199 ) as $path) {
194 if (! is_readable(realpath($path))) { 200 if (!is_readable(realpath($path))) {
195 $errors[] = '"'.$path.'" '. t('directory is not readable'); 201 $errors[] = '"' . $path . '" ' . t('directory is not readable');
196 } 202 }
197 if (! is_writable(realpath($path))) { 203 if (!is_writable(realpath($path))) {
198 $errors[] = '"'.$path.'" '. t('directory is not writable'); 204 $errors[] = '"' . $path . '" ' . t('directory is not writable');
199 } 205 }
200 } 206 }
201 207
202 // Check configuration files are readable and writable 208 // Check configuration files are readable and writable
203 foreach (array( 209 foreach (array(
204 $conf->getConfigFileExt(), 210 $conf->getConfigFileExt(),
205 $conf->get('resource.datastore'), 211 $conf->get('resource.datastore'),
206 $conf->get('resource.ban_file'), 212 $conf->get('resource.ban_file'),
207 $conf->get('resource.log'), 213 $conf->get('resource.log'),
208 $conf->get('resource.update_check'), 214 $conf->get('resource.update_check'),
209 ) as $path) { 215 ) as $path) {
210 if (! is_file(realpath($path))) { 216 if (!is_file(realpath($path))) {
211 # the file may not exist yet 217 # the file may not exist yet
212 continue; 218 continue;
213 } 219 }
214 220
215 if (! is_readable(realpath($path))) { 221 if (!is_readable(realpath($path))) {
216 $errors[] = '"'.$path.'" '. t('file is not readable'); 222 $errors[] = '"' . $path . '" ' . t('file is not readable');
217 } 223 }
218 if (! is_writable(realpath($path))) { 224 if (!is_writable(realpath($path))) {
219 $errors[] = '"'.$path.'" '. t('file is not writable'); 225 $errors[] = '"' . $path . '" ' . t('file is not writable');
220 } 226 }
221 } 227 }
222 228
diff --git a/application/FileUtils.php b/application/FileUtils.php
index b89ea12b..30560bfc 100644
--- a/application/FileUtils.php
+++ b/application/FileUtils.php
@@ -1,6 +1,8 @@
1<?php 1<?php
2 2
3require_once 'exceptions/IOException.php'; 3namespace Shaarli;
4
5use Shaarli\Exceptions\IOException;
4 6
5/** 7/**
6 * Class FileUtils 8 * Class FileUtils
@@ -44,7 +46,7 @@ class FileUtils
44 46
45 return file_put_contents( 47 return file_put_contents(
46 $file, 48 $file,
47 self::$phpPrefix.base64_encode(gzdeflate(serialize($content))).self::$phpSuffix 49 self::$phpPrefix . base64_encode(gzdeflate(serialize($content))) . self::$phpSuffix
48 ); 50 );
49 } 51 }
50 52
@@ -62,7 +64,7 @@ class FileUtils
62 { 64 {
63 // Note that gzinflate is faster than gzuncompress. 65 // Note that gzinflate is faster than gzuncompress.
64 // See: http://www.php.net/manual/en/function.gzdeflate.php#96439 66 // See: http://www.php.net/manual/en/function.gzdeflate.php#96439
65 if (! is_readable($file)) { 67 if (!is_readable($file)) {
66 return $default; 68 return $default;
67 } 69 }
68 70
diff --git a/application/History.php b/application/History.php
index 35ec016a..a5846652 100644
--- a/application/History.php
+++ b/application/History.php
@@ -1,4 +1,8 @@
1<?php 1<?php
2namespace Shaarli;
3
4use DateTime;
5use Exception;
2 6
3/** 7/**
4 * Class History 8 * Class History
@@ -66,7 +70,7 @@ class History
66 * History constructor. 70 * History constructor.
67 * 71 *
68 * @param string $historyFilePath History file path. 72 * @param string $historyFilePath History file path.
69 * @param int $retentionTime History content rentention time in seconds. 73 * @param int $retentionTime History content retention time in seconds.
70 * 74 *
71 * @throws Exception if something goes wrong. 75 * @throws Exception if something goes wrong.
72 */ 76 */
@@ -166,11 +170,11 @@ class History
166 */ 170 */
167 protected function check() 171 protected function check()
168 { 172 {
169 if (! is_file($this->historyFilePath)) { 173 if (!is_file($this->historyFilePath)) {
170 FileUtils::writeFlatDB($this->historyFilePath, []); 174 FileUtils::writeFlatDB($this->historyFilePath, []);
171 } 175 }
172 176
173 if (! is_writable($this->historyFilePath)) { 177 if (!is_writable($this->historyFilePath)) {
174 throw new Exception(t('History file isn\'t readable or writable')); 178 throw new Exception(t('History file isn\'t readable or writable'));
175 } 179 }
176 } 180 }
@@ -191,7 +195,7 @@ class History
191 */ 195 */
192 protected function write() 196 protected function write()
193 { 197 {
194 $comparaison = new DateTime('-'. $this->retentionTime . ' seconds'); 198 $comparaison = new DateTime('-' . $this->retentionTime . ' seconds');
195 foreach ($this->history as $key => $value) { 199 foreach ($this->history as $key => $value) {
196 if ($value['datetime'] < $comparaison) { 200 if ($value['datetime'] < $comparaison) {
197 unset($this->history[$key]); 201 unset($this->history[$key]);
diff --git a/application/Languages.php b/application/Languages.php
index 4fa32426..5cda802e 100644
--- a/application/Languages.php
+++ b/application/Languages.php
@@ -3,7 +3,6 @@
3namespace Shaarli; 3namespace Shaarli;
4 4
5use Gettext\GettextTranslator; 5use Gettext\GettextTranslator;
6use Gettext\Merge;
7use Gettext\Translations; 6use Gettext\Translations;
8use Gettext\Translator; 7use Gettext\Translator;
9use Gettext\TranslatorInterface; 8use Gettext\TranslatorInterface;
@@ -92,7 +91,7 @@ class Languages
92 /** 91 /**
93 * Initialize the translator using php gettext extension (gettext dependency act as a wrapper). 92 * Initialize the translator using php gettext extension (gettext dependency act as a wrapper).
94 */ 93 */
95 protected function initGettextTranslator () 94 protected function initGettextTranslator()
96 { 95 {
97 $this->translator = new GettextTranslator(); 96 $this->translator = new GettextTranslator();
98 $this->translator->setLanguage($this->language); 97 $this->translator->setLanguage($this->language);
@@ -125,7 +124,8 @@ class Languages
125 $translations = $translations->addFromPoFile('inc/languages/'. $this->language .'/LC_MESSAGES/shaarli.po'); 124 $translations = $translations->addFromPoFile('inc/languages/'. $this->language .'/LC_MESSAGES/shaarli.po');
126 $translations->setDomain('shaarli'); 125 $translations->setDomain('shaarli');
127 $this->translator->loadTranslations($translations); 126 $this->translator->loadTranslations($translations);
128 } catch (\InvalidArgumentException $e) {} 127 } catch (\InvalidArgumentException $e) {
128 }
129 129
130 // Default extension translation from the current theme 130 // Default extension translation from the current theme
131 $theme = $this->conf->get('theme'); 131 $theme = $this->conf->get('theme');
@@ -137,7 +137,8 @@ class Languages
137 ); 137 );
138 $translations->setDomain($theme); 138 $translations->setDomain($theme);
139 $this->translator->loadTranslations($translations); 139 $this->translator->loadTranslations($translations);
140 } catch (\InvalidArgumentException $e) {} 140 } catch (\InvalidArgumentException $e) {
141 }
141 } 142 }
142 143
143 // Extension translations (plugins, themes, etc.). 144 // Extension translations (plugins, themes, etc.).
@@ -147,10 +148,13 @@ class Languages
147 } 148 }
148 149
149 try { 150 try {
150 $extension = Translations::fromPoFile($translationPath . $this->language .'/LC_MESSAGES/'. $domain .'.po'); 151 $extension = Translations::fromPoFile(
152 $translationPath . $this->language .'/LC_MESSAGES/'. $domain .'.po'
153 );
151 $extension->setDomain($domain); 154 $extension->setDomain($domain);
152 $this->translator->loadTranslations($extension); 155 $this->translator->loadTranslations($extension);
153 } catch (\InvalidArgumentException $e) {} 156 } catch (\InvalidArgumentException $e) {
157 }
154 } 158 }
155 } 159 }
156 160
diff --git a/application/Router.php b/application/Router.php
index 4df0387c..05877acd 100644
--- a/application/Router.php
+++ b/application/Router.php
@@ -1,4 +1,5 @@
1<?php 1<?php
2namespace Shaarli;
2 3
3/** 4/**
4 * Class Router 5 * Class Router
@@ -7,6 +8,8 @@
7 */ 8 */
8class Router 9class Router
9{ 10{
11 public static $AJAX_THUMB_UPDATE = 'ajax_thumb_update';
12
10 public static $PAGE_LOGIN = 'login'; 13 public static $PAGE_LOGIN = 'login';
11 14
12 public static $PAGE_PICWALL = 'picwall'; 15 public static $PAGE_PICWALL = 'picwall';
@@ -35,6 +38,8 @@ class Router
35 38
36 public static $PAGE_DELETELINK = 'delete_link'; 39 public static $PAGE_DELETELINK = 'delete_link';
37 40
41 public static $PAGE_PINLINK = 'pin';
42
38 public static $PAGE_EXPORT = 'export'; 43 public static $PAGE_EXPORT = 'export';
39 44
40 public static $PAGE_IMPORT = 'import'; 45 public static $PAGE_IMPORT = 'import';
@@ -47,6 +52,8 @@ class Router
47 52
48 public static $PAGE_SAVE_PLUGINSADMIN = 'save_pluginadmin'; 53 public static $PAGE_SAVE_PLUGINSADMIN = 'save_pluginadmin';
49 54
55 public static $PAGE_THUMBS_UPDATE = 'thumbs_update';
56
50 public static $GET_TOKEN = 'token'; 57 public static $GET_TOKEN = 'token';
51 58
52 /** 59 /**
@@ -69,60 +76,68 @@ class Router
69 return self::$PAGE_LINKLIST; 76 return self::$PAGE_LINKLIST;
70 } 77 }
71 78
72 if (startsWith($query, 'do='. self::$PAGE_LOGIN) && $loggedIn === false) { 79 if (startsWith($query, 'do=' . self::$PAGE_LOGIN) && $loggedIn === false) {
73 return self::$PAGE_LOGIN; 80 return self::$PAGE_LOGIN;
74 } 81 }
75 82
76 if (startsWith($query, 'do='. self::$PAGE_PICWALL)) { 83 if (startsWith($query, 'do=' . self::$PAGE_PICWALL)) {
77 return self::$PAGE_PICWALL; 84 return self::$PAGE_PICWALL;
78 } 85 }
79 86
80 if (startsWith($query, 'do='. self::$PAGE_TAGCLOUD)) { 87 if (startsWith($query, 'do=' . self::$PAGE_TAGCLOUD)) {
81 return self::$PAGE_TAGCLOUD; 88 return self::$PAGE_TAGCLOUD;
82 } 89 }
83 90
84 if (startsWith($query, 'do='. self::$PAGE_TAGLIST)) { 91 if (startsWith($query, 'do=' . self::$PAGE_TAGLIST)) {
85 return self::$PAGE_TAGLIST; 92 return self::$PAGE_TAGLIST;
86 } 93 }
87 94
88 if (startsWith($query, 'do='. self::$PAGE_OPENSEARCH)) { 95 if (startsWith($query, 'do=' . self::$PAGE_OPENSEARCH)) {
89 return self::$PAGE_OPENSEARCH; 96 return self::$PAGE_OPENSEARCH;
90 } 97 }
91 98
92 if (startsWith($query, 'do='. self::$PAGE_DAILY)) { 99 if (startsWith($query, 'do=' . self::$PAGE_DAILY)) {
93 return self::$PAGE_DAILY; 100 return self::$PAGE_DAILY;
94 } 101 }
95 102
96 if (startsWith($query, 'do='. self::$PAGE_FEED_ATOM)) { 103 if (startsWith($query, 'do=' . self::$PAGE_FEED_ATOM)) {
97 return self::$PAGE_FEED_ATOM; 104 return self::$PAGE_FEED_ATOM;
98 } 105 }
99 106
100 if (startsWith($query, 'do='. self::$PAGE_FEED_RSS)) { 107 if (startsWith($query, 'do=' . self::$PAGE_FEED_RSS)) {
101 return self::$PAGE_FEED_RSS; 108 return self::$PAGE_FEED_RSS;
102 } 109 }
103 110
111 if (startsWith($query, 'do=' . self::$PAGE_THUMBS_UPDATE)) {
112 return self::$PAGE_THUMBS_UPDATE;
113 }
114
115 if (startsWith($query, 'do=' . self::$AJAX_THUMB_UPDATE)) {
116 return self::$AJAX_THUMB_UPDATE;
117 }
118
104 // At this point, only loggedin pages. 119 // At this point, only loggedin pages.
105 if (!$loggedIn) { 120 if (!$loggedIn) {
106 return self::$PAGE_LINKLIST; 121 return self::$PAGE_LINKLIST;
107 } 122 }
108 123
109 if (startsWith($query, 'do='. self::$PAGE_TOOLS)) { 124 if (startsWith($query, 'do=' . self::$PAGE_TOOLS)) {
110 return self::$PAGE_TOOLS; 125 return self::$PAGE_TOOLS;
111 } 126 }
112 127
113 if (startsWith($query, 'do='. self::$PAGE_CHANGEPASSWORD)) { 128 if (startsWith($query, 'do=' . self::$PAGE_CHANGEPASSWORD)) {
114 return self::$PAGE_CHANGEPASSWORD; 129 return self::$PAGE_CHANGEPASSWORD;
115 } 130 }
116 131
117 if (startsWith($query, 'do='. self::$PAGE_CONFIGURE)) { 132 if (startsWith($query, 'do=' . self::$PAGE_CONFIGURE)) {
118 return self::$PAGE_CONFIGURE; 133 return self::$PAGE_CONFIGURE;
119 } 134 }
120 135
121 if (startsWith($query, 'do='. self::$PAGE_CHANGETAG)) { 136 if (startsWith($query, 'do=' . self::$PAGE_CHANGETAG)) {
122 return self::$PAGE_CHANGETAG; 137 return self::$PAGE_CHANGETAG;
123 } 138 }
124 139
125 if (startsWith($query, 'do='. self::$PAGE_ADDLINK)) { 140 if (startsWith($query, 'do=' . self::$PAGE_ADDLINK)) {
126 return self::$PAGE_ADDLINK; 141 return self::$PAGE_ADDLINK;
127 } 142 }
128 143
@@ -134,23 +149,27 @@ class Router
134 return self::$PAGE_DELETELINK; 149 return self::$PAGE_DELETELINK;
135 } 150 }
136 151
137 if (startsWith($query, 'do='. self::$PAGE_EXPORT)) { 152 if (startsWith($query, 'do=' . self::$PAGE_PINLINK)) {
153 return self::$PAGE_PINLINK;
154 }
155
156 if (startsWith($query, 'do=' . self::$PAGE_EXPORT)) {
138 return self::$PAGE_EXPORT; 157 return self::$PAGE_EXPORT;
139 } 158 }
140 159
141 if (startsWith($query, 'do='. self::$PAGE_IMPORT)) { 160 if (startsWith($query, 'do=' . self::$PAGE_IMPORT)) {
142 return self::$PAGE_IMPORT; 161 return self::$PAGE_IMPORT;
143 } 162 }
144 163
145 if (startsWith($query, 'do='. self::$PAGE_PLUGINSADMIN)) { 164 if (startsWith($query, 'do=' . self::$PAGE_PLUGINSADMIN)) {
146 return self::$PAGE_PLUGINSADMIN; 165 return self::$PAGE_PLUGINSADMIN;
147 } 166 }
148 167
149 if (startsWith($query, 'do='. self::$PAGE_SAVE_PLUGINSADMIN)) { 168 if (startsWith($query, 'do=' . self::$PAGE_SAVE_PLUGINSADMIN)) {
150 return self::$PAGE_SAVE_PLUGINSADMIN; 169 return self::$PAGE_SAVE_PLUGINSADMIN;
151 } 170 }
152 171
153 if (startsWith($query, 'do='. self::$GET_TOKEN)) { 172 if (startsWith($query, 'do=' . self::$GET_TOKEN)) {
154 return self::$GET_TOKEN; 173 return self::$GET_TOKEN;
155 } 174 }
156 175
diff --git a/application/Thumbnailer.php b/application/Thumbnailer.php
new file mode 100644
index 00000000..a23f98e9
--- /dev/null
+++ b/application/Thumbnailer.php
@@ -0,0 +1,130 @@
1<?php
2
3namespace Shaarli;
4
5use Shaarli\Config\ConfigManager;
6use WebThumbnailer\Application\ConfigManager as WTConfigManager;
7use WebThumbnailer\Exception\WebThumbnailerException;
8use WebThumbnailer\WebThumbnailer;
9
10/**
11 * Class Thumbnailer
12 *
13 * Utility class used to retrieve thumbnails using web-thumbnailer dependency.
14 */
15class Thumbnailer
16{
17 const COMMON_MEDIA_DOMAINS = [
18 'imgur.com',
19 'flickr.com',
20 'youtube.com',
21 'wikimedia.org',
22 'redd.it',
23 'gfycat.com',
24 'media.giphy.com',
25 'twitter.com',
26 'twimg.com',
27 'instagram.com',
28 'pinterest.com',
29 'pinterest.fr',
30 'tumblr.com',
31 'deviantart.com',
32 ];
33
34 const MODE_ALL = 'all';
35 const MODE_COMMON = 'common';
36 const MODE_NONE = 'none';
37
38 /**
39 * @var WebThumbnailer instance.
40 */
41 protected $wt;
42
43 /**
44 * @var ConfigManager instance.
45 */
46 protected $conf;
47
48 /**
49 * Thumbnailer constructor.
50 *
51 * @param ConfigManager $conf instance.
52 */
53 public function __construct($conf)
54 {
55 $this->conf = $conf;
56
57 if (! $this->checkRequirements()) {
58 $this->conf->set('thumbnails.enabled', false);
59 $this->conf->write(true);
60 // TODO: create a proper error handling system able to catch exceptions...
61 die(t(
62 'php-gd extension must be loaded to use thumbnails. '
63 .'Thumbnails are now disabled. Please reload the page.'
64 ));
65 }
66
67 $this->wt = new WebThumbnailer();
68 WTConfigManager::addFile('inc/web-thumbnailer.json');
69 $this->wt->maxWidth($this->conf->get('thumbnails.width'))
70 ->maxHeight($this->conf->get('thumbnails.height'))
71 ->crop(true)
72 ->debug($this->conf->get('dev.debug', false));
73 }
74
75 /**
76 * Retrieve a thumbnail for given URL
77 *
78 * @param string $url where to look for a thumbnail.
79 *
80 * @return bool|string The thumbnail relative cache file path, or false if none has been found.
81 */
82 public function get($url)
83 {
84 if ($this->conf->get('thumbnails.mode') === self::MODE_COMMON
85 && ! $this->isCommonMediaOrImage($url)
86 ) {
87 return false;
88 }
89
90 try {
91 return $this->wt->thumbnail($url);
92 } catch (WebThumbnailerException $e) {
93 // Exceptions are only thrown in debug mode.
94 error_log(get_class($e) . ': ' . $e->getMessage());
95 }
96 return false;
97 }
98
99 /**
100 * We check weather the given URL is from a common media domain,
101 * or if the file extension is an image.
102 *
103 * @param string $url to check
104 *
105 * @return bool true if it's an image or from a common media domain, false otherwise.
106 */
107 public function isCommonMediaOrImage($url)
108 {
109 foreach (self::COMMON_MEDIA_DOMAINS as $domain) {
110 if (strpos($url, $domain) !== false) {
111 return true;
112 }
113 }
114
115 if (endsWith($url, '.jpg') || endsWith($url, '.png') || endsWith($url, '.jpeg')) {
116 return true;
117 }
118
119 return false;
120 }
121
122 /**
123 * Make sure that requirements are match to use thumbnails:
124 * - php-gd is loaded
125 */
126 protected function checkRequirements()
127 {
128 return extension_loaded('gd');
129 }
130}
diff --git a/application/Utils.php b/application/Utils.php
index 97b12fcf..925e1a22 100644
--- a/application/Utils.php
+++ b/application/Utils.php
@@ -97,7 +97,7 @@ function escape($input)
97 97
98 if (is_array($input)) { 98 if (is_array($input)) {
99 $out = array(); 99 $out = array();
100 foreach($input as $key => $value) { 100 foreach ($input as $key => $value) {
101 $out[$key] = escape($value); 101 $out[$key] = escape($value);
102 } 102 }
103 return $out; 103 return $out;
@@ -355,10 +355,13 @@ function return_bytes($val)
355 $val = trim($val); 355 $val = trim($val);
356 $last = strtolower($val[strlen($val)-1]); 356 $last = strtolower($val[strlen($val)-1]);
357 $val = intval(substr($val, 0, -1)); 357 $val = intval(substr($val, 0, -1));
358 switch($last) { 358 switch ($last) {
359 case 'g': $val *= 1024; 359 case 'g':
360 case 'm': $val *= 1024; 360 $val *= 1024;
361 case 'k': $val *= 1024; 361 case 'm':
362 $val *= 1024;
363 case 'k':
364 $val *= 1024;
362 } 365 }
363 return $val; 366 return $val;
364} 367}
@@ -452,6 +455,7 @@ function alphabetical_sort(&$data, $reverse = false, $byKeys = false)
452 * 455 *
453 * @return string Text translated. 456 * @return string Text translated.
454 */ 457 */
455function t($text, $nText = '', $nb = 1, $domain = 'shaarli') { 458function t($text, $nText = '', $nb = 1, $domain = 'shaarli')
459{
456 return dn__($domain, $text, $nText, $nb); 460 return dn__($domain, $text, $nText, $nb);
457} 461}
diff --git a/application/api/ApiMiddleware.php b/application/api/ApiMiddleware.php
index ff209393..5ffb8c6d 100644
--- a/application/api/ApiMiddleware.php
+++ b/application/api/ApiMiddleware.php
@@ -1,9 +1,8 @@
1<?php 1<?php
2namespace Shaarli\Api; 2namespace Shaarli\Api;
3 3
4use Shaarli\Api\Exceptions\ApiException;
5use Shaarli\Api\Exceptions\ApiAuthorizationException; 4use Shaarli\Api\Exceptions\ApiAuthorizationException;
6 5use Shaarli\Api\Exceptions\ApiException;
7use Shaarli\Config\ConfigManager; 6use Shaarli\Config\ConfigManager;
8use Slim\Container; 7use Slim\Container;
9use Slim\Http\Request; 8use Slim\Http\Request;
@@ -65,7 +64,7 @@ class ApiMiddleware
65 try { 64 try {
66 $this->checkRequest($request); 65 $this->checkRequest($request);
67 $response = $next($request, $response); 66 $response = $next($request, $response);
68 } catch(ApiException $e) { 67 } catch (ApiException $e) {
69 $e->setResponse($response); 68 $e->setResponse($response);
70 $e->setDebug($this->conf->get('dev.debug', false)); 69 $e->setDebug($this->conf->get('dev.debug', false));
71 $response = $e->getApiResponse(); 70 $response = $e->getApiResponse();
@@ -98,7 +97,8 @@ class ApiMiddleware
98 * 97 *
99 * @throws ApiAuthorizationException The token couldn't be validated. 98 * @throws ApiAuthorizationException The token couldn't be validated.
100 */ 99 */
101 protected function checkToken($request) { 100 protected function checkToken($request)
101 {
102 if (! $request->hasHeader('Authorization')) { 102 if (! $request->hasHeader('Authorization')) {
103 throw new ApiAuthorizationException('JWT token not provided'); 103 throw new ApiAuthorizationException('JWT token not provided');
104 } 104 }
@@ -126,7 +126,7 @@ class ApiMiddleware
126 */ 126 */
127 protected function setLinkDb($conf) 127 protected function setLinkDb($conf)
128 { 128 {
129 $linkDb = new \LinkDB( 129 $linkDb = new \Shaarli\Bookmark\LinkDB(
130 $conf->get('resource.datastore'), 130 $conf->get('resource.datastore'),
131 true, 131 true,
132 $conf->get('privacy.hide_public_links'), 132 $conf->get('privacy.hide_public_links'),
diff --git a/application/api/ApiUtils.php b/application/api/ApiUtils.php
index fc5ecaf1..1824b5d0 100644
--- a/application/api/ApiUtils.php
+++ b/application/api/ApiUtils.php
@@ -1,8 +1,8 @@
1<?php 1<?php
2namespace Shaarli\Api; 2namespace Shaarli\Api;
3 3
4use Shaarli\Base64Url;
5use Shaarli\Api\Exceptions\ApiAuthorizationException; 4use Shaarli\Api\Exceptions\ApiAuthorizationException;
5use Shaarli\Http\Base64Url;
6 6
7/** 7/**
8 * REST API utilities 8 * REST API utilities
@@ -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.
diff --git a/application/api/controllers/ApiController.php b/application/api/controllers/ApiController.php
index 3be85b98..a6e7cbab 100644
--- a/application/api/controllers/ApiController.php
+++ b/application/api/controllers/ApiController.php
@@ -2,8 +2,9 @@
2 2
3namespace Shaarli\Api\Controllers; 3namespace Shaarli\Api\Controllers;
4 4
5use Shaarli\Bookmark\LinkDB;
5use Shaarli\Config\ConfigManager; 6use Shaarli\Config\ConfigManager;
6use \Slim\Container; 7use Slim\Container;
7 8
8/** 9/**
9 * Abstract Class ApiController 10 * Abstract Class ApiController
@@ -25,12 +26,12 @@ abstract class ApiController
25 protected $conf; 26 protected $conf;
26 27
27 /** 28 /**
28 * @var \LinkDB 29 * @var LinkDB
29 */ 30 */
30 protected $linkDb; 31 protected $linkDb;
31 32
32 /** 33 /**
33 * @var \History 34 * @var HistoryController
34 */ 35 */
35 protected $history; 36 protected $history;
36 37
@@ -41,7 +42,7 @@ abstract class ApiController
41 42
42 /** 43 /**
43 * ApiController constructor. 44 * ApiController constructor.
44 * 45 *
45 * Note: enabling debug mode displays JSON with readable formatting. 46 * Note: enabling debug mode displays JSON with readable formatting.
46 * 47 *
47 * @param Container $ci Slim container. 48 * @param Container $ci Slim container.
diff --git a/application/api/controllers/History.php b/application/api/controllers/HistoryController.php
index 5cc453bf..9afcfa26 100644
--- a/application/api/controllers/History.php
+++ b/application/api/controllers/HistoryController.php
@@ -14,7 +14,7 @@ use Slim\Http\Response;
14 * 14 *
15 * @package Shaarli\Api\Controllers 15 * @package Shaarli\Api\Controllers
16 */ 16 */
17class History extends ApiController 17class HistoryController extends ApiController
18{ 18{
19 /** 19 /**
20 * Service providing operation regarding Shaarli datastore and settings. 20 * Service providing operation regarding Shaarli datastore and settings.
@@ -35,8 +35,7 @@ class History extends ApiController
35 $offset = $request->getParam('offset'); 35 $offset = $request->getParam('offset');
36 if (empty($offset)) { 36 if (empty($offset)) {
37 $offset = 0; 37 $offset = 0;
38 } 38 } elseif (ctype_digit($offset)) {
39 elseif (ctype_digit($offset)) {
40 $offset = (int) $offset; 39 $offset = (int) $offset;
41 } else { 40 } else {
42 throw new ApiBadParametersException('Invalid offset'); 41 throw new ApiBadParametersException('Invalid offset');
diff --git a/application/api/controllers/Info.php b/application/api/controllers/Info.php
index 25433f72..f37dcae5 100644
--- a/application/api/controllers/Info.php
+++ b/application/api/controllers/Info.php
@@ -7,7 +7,7 @@ use Slim\Http\Response;
7 7
8/** 8/**
9 * Class Info 9 * Class Info
10 * 10 *
11 * REST API Controller: /info 11 * REST API Controller: /info
12 * 12 *
13 * @package Api\Controllers 13 * @package Api\Controllers
@@ -17,7 +17,7 @@ class Info extends ApiController
17{ 17{
18 /** 18 /**
19 * Service providing various information about Shaarli instance. 19 * Service providing various information about Shaarli instance.
20 * 20 *
21 * @param Request $request Slim request. 21 * @param Request $request Slim request.
22 * @param Response $response Slim response. 22 * @param Response $response Slim response.
23 * 23 *
diff --git a/application/api/controllers/Tags.php b/application/api/controllers/Tags.php
index 6dd78750..82f3ef74 100644
--- a/application/api/controllers/Tags.php
+++ b/application/api/controllers/Tags.php
@@ -4,7 +4,6 @@ namespace Shaarli\Api\Controllers;
4 4
5use Shaarli\Api\ApiUtils; 5use Shaarli\Api\ApiUtils;
6use Shaarli\Api\Exceptions\ApiBadParametersException; 6use Shaarli\Api\Exceptions\ApiBadParametersException;
7use Shaarli\Api\Exceptions\ApiLinkNotFoundException;
8use Shaarli\Api\Exceptions\ApiTagNotFoundException; 7use Shaarli\Api\Exceptions\ApiTagNotFoundException;
9use Slim\Http\Request; 8use Slim\Http\Request;
10use Slim\Http\Response; 9use Slim\Http\Response;
diff --git a/application/api/exceptions/ApiException.php b/application/api/exceptions/ApiException.php
index c8490e0c..d6b66323 100644
--- a/application/api/exceptions/ApiException.php
+++ b/application/api/exceptions/ApiException.php
@@ -10,7 +10,8 @@ use Slim\Http\Response;
10 * Parent Exception related to the API, able to generate a valid Response (ResponseInterface). 10 * Parent Exception related to the API, able to generate a valid Response (ResponseInterface).
11 * Also can include various information in debug mode. 11 * Also can include various information in debug mode.
12 */ 12 */
13abstract class ApiException extends \Exception { 13abstract class ApiException extends \Exception
14{
14 15
15 /** 16 /**
16 * @var Response instance from Slim. 17 * @var Response instance from Slim.
@@ -27,7 +28,7 @@ abstract class ApiException extends \Exception {
27 * 28 *
28 * @return Response Final response to give. 29 * @return Response Final response to give.
29 */ 30 */
30 public abstract function getApiResponse(); 31 abstract public function getApiResponse();
31 32
32 /** 33 /**
33 * Creates ApiResponse body. 34 * Creates ApiResponse body.
@@ -36,7 +37,8 @@ abstract class ApiException extends \Exception {
36 * 37 *
37 * @return array|string response body 38 * @return array|string response body
38 */ 39 */
39 protected function getApiResponseBody() { 40 protected function getApiResponseBody()
41 {
40 if ($this->debug !== true) { 42 if ($this->debug !== true) {
41 return $this->getMessage(); 43 return $this->getMessage();
42 } 44 }
diff --git a/application/api/exceptions/ApiLinkNotFoundException.php b/application/api/exceptions/ApiLinkNotFoundException.php
index de7e14f5..7c2bb56e 100644
--- a/application/api/exceptions/ApiLinkNotFoundException.php
+++ b/application/api/exceptions/ApiLinkNotFoundException.php
@@ -2,9 +2,6 @@
2 2
3namespace Shaarli\Api\Exceptions; 3namespace Shaarli\Api\Exceptions;
4 4
5
6use Slim\Http\Response;
7
8/** 5/**
9 * Class ApiLinkNotFoundException 6 * Class ApiLinkNotFoundException
10 * 7 *
diff --git a/application/api/exceptions/ApiTagNotFoundException.php b/application/api/exceptions/ApiTagNotFoundException.php
index eed5afa5..66ace8bf 100644
--- a/application/api/exceptions/ApiTagNotFoundException.php
+++ b/application/api/exceptions/ApiTagNotFoundException.php
@@ -2,9 +2,6 @@
2 2
3namespace Shaarli\Api\Exceptions; 3namespace Shaarli\Api\Exceptions;
4 4
5
6use Slim\Http\Response;
7
8/** 5/**
9 * Class ApiTagNotFoundException 6 * Class ApiTagNotFoundException
10 * 7 *
diff --git a/application/LinkDB.php b/application/bookmark/LinkDB.php
index cd0f2967..c13a1141 100644
--- a/application/LinkDB.php
+++ b/application/bookmark/LinkDB.php
@@ -1,4 +1,15 @@
1<?php 1<?php
2
3namespace Shaarli\Bookmark;
4
5use ArrayAccess;
6use Countable;
7use DateTime;
8use Iterator;
9use Shaarli\Bookmark\Exception\LinkNotFoundException;
10use Shaarli\Exceptions\IOException;
11use Shaarli\FileUtils;
12
2/** 13/**
3 * Data storage for links. 14 * Data storage for links.
4 * 15 *
@@ -107,8 +118,8 @@ class LinkDB implements Iterator, Countable, ArrayAccess
107 $hidePublicLinks, 118 $hidePublicLinks,
108 $redirector = '', 119 $redirector = '',
109 $redirectorEncode = true 120 $redirectorEncode = true
110 ) 121 ) {
111 { 122
112 $this->datastore = $datastore; 123 $this->datastore = $datastore;
113 $this->loggedIn = $isLoggedIn; 124 $this->loggedIn = $isLoggedIn;
114 $this->hidePublicLinks = $hidePublicLinks; 125 $this->hidePublicLinks = $hidePublicLinks;
@@ -138,7 +149,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess
138 if (!isset($value['id']) || empty($value['url'])) { 149 if (!isset($value['id']) || empty($value['url'])) {
139 die(t('Internal Error: A link should always have an id and URL.')); 150 die(t('Internal Error: A link should always have an id and URL.'));
140 } 151 }
141 if (($offset !== null && ! is_int($offset)) || ! is_int($value['id'])) { 152 if (($offset !== null && !is_int($offset)) || !is_int($value['id'])) {
142 die(t('You must specify an integer as a key.')); 153 die(t('You must specify an integer as a key.'));
143 } 154 }
144 if ($offset !== null && $offset !== $value['id']) { 155 if ($offset !== null && $offset !== $value['id']) {
@@ -248,28 +259,31 @@ class LinkDB implements Iterator, Countable, ArrayAccess
248 $this->links = array(); 259 $this->links = array();
249 $link = array( 260 $link = array(
250 'id' => 1, 261 'id' => 1,
251 'title'=> t('The personal, minimalist, super-fast, database free, bookmarking service'), 262 'title' => t('The personal, minimalist, super-fast, database free, bookmarking service'),
252 'url'=>'https://shaarli.readthedocs.io', 263 'url' => 'https://shaarli.readthedocs.io',
253 'description'=>t('Welcome to Shaarli! This is your first public bookmark. To edit or delete me, you must first login. 264 'description' => t(
265 'Welcome to Shaarli! This is your first public bookmark. '
266 . 'To edit or delete me, you must first login.
254 267
255To learn how to use Shaarli, consult the link "Documentation" at the bottom of this page. 268To learn how to use Shaarli, consult the link "Documentation" at the bottom of this page.
256 269
257You use the community supported version of the original Shaarli project, by Sebastien Sauvage.'), 270You use the community supported version of the original Shaarli project, by Sebastien Sauvage.'
258 'private'=>0, 271 ),
259 'created'=> new DateTime(), 272 'private' => 0,
260 'tags'=>'opensource software' 273 'created' => new DateTime(),
274 'tags' => 'opensource software'
261 ); 275 );
262 $link['shorturl'] = link_small_hash($link['created'], $link['id']); 276 $link['shorturl'] = link_small_hash($link['created'], $link['id']);
263 $this->links[1] = $link; 277 $this->links[1] = $link;
264 278
265 $link = array( 279 $link = array(
266 'id' => 0, 280 'id' => 0,
267 'title'=> t('My secret stuff... - Pastebin.com'), 281 'title' => t('My secret stuff... - Pastebin.com'),
268 'url'=>'http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=', 282 'url' => 'http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=',
269 'description'=> t('Shhhh! I\'m a private link only YOU can see. You can delete me too.'), 283 'description' => t('Shhhh! I\'m a private link only YOU can see. You can delete me too.'),
270 'private'=>1, 284 'private' => 1,
271 'created'=> new DateTime('1 minute ago'), 285 'created' => new DateTime('1 minute ago'),
272 'tags'=>'secretstuff', 286 'tags' => 'secretstuff',
273 ); 287 );
274 $link['shorturl'] = link_small_hash($link['created'], $link['id']); 288 $link['shorturl'] = link_small_hash($link['created'], $link['id']);
275 $this->links[0] = $link; 289 $this->links[0] = $link;
@@ -295,7 +309,7 @@ You use the community supported version of the original Shaarli project, by Seba
295 309
296 $toremove = array(); 310 $toremove = array();
297 foreach ($this->links as $key => &$link) { 311 foreach ($this->links as $key => &$link) {
298 if (! $this->loggedIn && $link['private'] != 0) { 312 if (!$this->loggedIn && $link['private'] != 0) {
299 // Transition for not upgraded databases. 313 // Transition for not upgraded databases.
300 unset($this->links[$key]); 314 unset($this->links[$key]);
301 continue; 315 continue;
@@ -305,7 +319,7 @@ You use the community supported version of the original Shaarli project, by Seba
305 sanitizeLink($link); 319 sanitizeLink($link);
306 320
307 // Remove private tags if the user is not logged in. 321 // Remove private tags if the user is not logged in.
308 if (! $this->loggedIn) { 322 if (!$this->loggedIn) {
309 $link['tags'] = preg_replace('/(^|\s+)\.[^($|\s)]+\s*/', ' ', $link['tags']); 323 $link['tags'] = preg_replace('/(^|\s+)\.[^($|\s)]+\s*/', ' ', $link['tags']);
310 } 324 }
311 325
@@ -317,16 +331,15 @@ You use the community supported version of the original Shaarli project, by Seba
317 } else { 331 } else {
318 $link['real_url'] .= $link['url']; 332 $link['real_url'] .= $link['url'];
319 } 333 }
320 } 334 } else {
321 else {
322 $link['real_url'] = $link['url']; 335 $link['real_url'] = $link['url'];
323 } 336 }
324 337
325 // To be able to load links before running the update, and prepare the update 338 // To be able to load links before running the update, and prepare the update
326 if (! isset($link['created'])) { 339 if (!isset($link['created'])) {
327 $link['id'] = $link['linkdate']; 340 $link['id'] = $link['linkdate'];
328 $link['created'] = DateTime::createFromFormat(self::LINK_DATE_FORMAT, $link['linkdate']); 341 $link['created'] = DateTime::createFromFormat(self::LINK_DATE_FORMAT, $link['linkdate']);
329 if (! empty($link['updated'])) { 342 if (!empty($link['updated'])) {
330 $link['updated'] = DateTime::createFromFormat(self::LINK_DATE_FORMAT, $link['updated']); 343 $link['updated'] = DateTime::createFromFormat(self::LINK_DATE_FORMAT, $link['updated']);
331 } 344 }
332 $link['shorturl'] = smallHash($link['linkdate']); 345 $link['shorturl'] = smallHash($link['linkdate']);
@@ -403,7 +416,8 @@ You use the community supported version of the original Shaarli project, by Seba
403 * 416 *
404 * @return array list of shaare found. 417 * @return array list of shaare found.
405 */ 418 */
406 public function filterDay($request) { 419 public function filterDay($request)
420 {
407 $linkFilter = new LinkFilter($this->links); 421 $linkFilter = new LinkFilter($this->links);
408 return $linkFilter->filter(LinkFilter::$FILTER_DAY, $request); 422 return $linkFilter->filter(LinkFilter::$FILTER_DAY, $request);
409 } 423 }
@@ -411,17 +425,22 @@ You use the community supported version of the original Shaarli project, by Seba
411 /** 425 /**
412 * Filter links according to search parameters. 426 * Filter links according to search parameters.
413 * 427 *
414 * @param array $filterRequest Search request content. Supported keys: 428 * @param array $filterRequest Search request content. Supported keys:
415 * - searchtags: list of tags 429 * - searchtags: list of tags
416 * - searchterm: term search 430 * - searchterm: term search
417 * @param bool $casesensitive Optional: Perform case sensitive filter 431 * @param bool $casesensitive Optional: Perform case sensitive filter
418 * @param string $visibility return only all/private/public links 432 * @param string $visibility return only all/private/public links
419 * @param string $untaggedonly return only untagged links 433 * @param bool $untaggedonly return only untagged links
420 * 434 *
421 * @return array filtered links, all links if no suitable filter was provided. 435 * @return array filtered links, all links if no suitable filter was provided.
422 */ 436 */
423 public function filterSearch($filterRequest = array(), $casesensitive = false, $visibility = 'all', $untaggedonly = false) 437 public function filterSearch(
424 { 438 $filterRequest = array(),
439 $casesensitive = false,
440 $visibility = 'all',
441 $untaggedonly = false
442 ) {
443
425 // Filter link database according to parameters. 444 // Filter link database according to parameters.
426 $searchtags = isset($filterRequest['searchtags']) ? escape($filterRequest['searchtags']) : ''; 445 $searchtags = isset($filterRequest['searchtags']) ? escape($filterRequest['searchtags']) : '';
427 $searchterm = isset($filterRequest['searchterm']) ? escape($filterRequest['searchterm']) : ''; 446 $searchterm = isset($filterRequest['searchterm']) ? escape($filterRequest['searchterm']) : '';
@@ -437,8 +456,8 @@ You use the community supported version of the original Shaarli project, by Seba
437 /** 456 /**
438 * Returns the list tags appearing in the links with the given tags 457 * Returns the list tags appearing in the links with the given tags
439 * 458 *
440 * @param array $filteringTags tags selecting the links to consider 459 * @param array $filteringTags tags selecting the links to consider
441 * @param string $visibility process only all/private/public links 460 * @param string $visibility process only all/private/public links
442 * 461 *
443 * @return array tag => linksCount 462 * @return array tag => linksCount
444 */ 463 */
@@ -492,8 +511,7 @@ You use the community supported version of the original Shaarli project, by Seba
492 $delete = empty($to); 511 $delete = empty($to);
493 // True for case-sensitive tag search. 512 // True for case-sensitive tag search.
494 $linksToAlter = $this->filterSearch(['searchtags' => $from], true); 513 $linksToAlter = $this->filterSearch(['searchtags' => $from], true);
495 foreach($linksToAlter as $key => &$value) 514 foreach ($linksToAlter as $key => &$value) {
496 {
497 $tags = preg_split('/\s+/', trim($value['tags'])); 515 $tags = preg_split('/\s+/', trim($value['tags']));
498 if (($pos = array_search($from, $tags)) !== false) { 516 if (($pos = array_search($from, $tags)) !== false) {
499 if ($delete) { 517 if ($delete) {
@@ -536,7 +554,10 @@ You use the community supported version of the original Shaarli project, by Seba
536 { 554 {
537 $order = $order === 'ASC' ? -1 : 1; 555 $order = $order === 'ASC' ? -1 : 1;
538 // Reorder array by dates. 556 // Reorder array by dates.
539 usort($this->links, function($a, $b) use ($order) { 557 usort($this->links, function ($a, $b) use ($order) {
558 if (isset($a['sticky']) && isset($b['sticky']) && $a['sticky'] !== $b['sticky']) {
559 return $a['sticky'] ? -1 : 1;
560 }
540 return $a['created'] < $b['created'] ? 1 * $order : -1 * $order; 561 return $a['created'] < $b['created'] ? 1 * $order : -1 * $order;
541 }); 562 });
542 563
diff --git a/application/LinkFilter.php b/application/bookmark/LinkFilter.php
index e52239b8..9b966307 100644
--- a/application/LinkFilter.php
+++ b/application/bookmark/LinkFilter.php
@@ -1,5 +1,10 @@
1<?php 1<?php
2 2
3namespace Shaarli\Bookmark;
4
5use Exception;
6use Shaarli\Bookmark\Exception\LinkNotFoundException;
7
3/** 8/**
4 * Class LinkFilter. 9 * Class LinkFilter.
5 * 10 *
@@ -10,22 +15,22 @@ class LinkFilter
10 /** 15 /**
11 * @var string permalinks. 16 * @var string permalinks.
12 */ 17 */
13 public static $FILTER_HASH = 'permalink'; 18 public static $FILTER_HASH = 'permalink';
14 19
15 /** 20 /**
16 * @var string text search. 21 * @var string text search.
17 */ 22 */
18 public static $FILTER_TEXT = 'fulltext'; 23 public static $FILTER_TEXT = 'fulltext';
19 24
20 /** 25 /**
21 * @var string tag filter. 26 * @var string tag filter.
22 */ 27 */
23 public static $FILTER_TAG = 'tags'; 28 public static $FILTER_TAG = 'tags';
24 29
25 /** 30 /**
26 * @var string filter by day. 31 * @var string filter by day.
27 */ 32 */
28 public static $FILTER_DAY = 'FILTER_DAY'; 33 public static $FILTER_DAY = 'FILTER_DAY';
29 34
30 /** 35 /**
31 * @var string Allowed characters for hashtags (regex syntax). 36 * @var string Allowed characters for hashtags (regex syntax).
@@ -58,11 +63,11 @@ class LinkFilter
58 */ 63 */
59 public function filter($type, $request, $casesensitive = false, $visibility = 'all', $untaggedonly = false) 64 public function filter($type, $request, $casesensitive = false, $visibility = 'all', $untaggedonly = false)
60 { 65 {
61 if (! in_array($visibility, ['all', 'public', 'private'])) { 66 if (!in_array($visibility, ['all', 'public', 'private'])) {
62 $visibility = 'all'; 67 $visibility = 'all';
63 } 68 }
64 69
65 switch($type) { 70 switch ($type) {
66 case self::$FILTER_HASH: 71 case self::$FILTER_HASH:
67 return $this->filterSmallHash($request); 72 return $this->filterSmallHash($request);
68 case self::$FILTER_TAG | self::$FILTER_TEXT: // == "vuotext" 73 case self::$FILTER_TAG | self::$FILTER_TEXT: // == "vuotext"
@@ -117,7 +122,7 @@ class LinkFilter
117 foreach ($this->links as $key => $value) { 122 foreach ($this->links as $key => $value) {
118 if ($value['private'] && $visibility === 'private') { 123 if ($value['private'] && $visibility === 'private') {
119 $out[$key] = $value; 124 $out[$key] = $value;
120 } elseif (! $value['private'] && $visibility === 'public') { 125 } elseif (!$value['private'] && $visibility === 'public') {
121 $out[$key] = $value; 126 $out[$key] = $value;
122 } 127 }
123 } 128 }
@@ -132,7 +137,7 @@ class LinkFilter
132 * 137 *
133 * @return array $filtered array containing permalink data. 138 * @return array $filtered array containing permalink data.
134 * 139 *
135 * @throws LinkNotFoundException if the smallhash doesn't match any link. 140 * @throws \Shaarli\Bookmark\Exception\LinkNotFoundException if the smallhash doesn't match any link.
136 */ 141 */
137 private function filterSmallHash($smallHash) 142 private function filterSmallHash($smallHash)
138 { 143 {
@@ -169,7 +174,7 @@ class LinkFilter
169 * - see https://github.com/shaarli/Shaarli/issues/75 for examples 174 * - see https://github.com/shaarli/Shaarli/issues/75 for examples
170 * 175 *
171 * @param string $searchterms search query. 176 * @param string $searchterms search query.
172 * @param string $visibility Optional: return only all/private/public links. 177 * @param string $visibility Optional: return only all/private/public links.
173 * 178 *
174 * @return array search results. 179 * @return array search results.
175 */ 180 */
@@ -205,10 +210,9 @@ class LinkFilter
205 210
206 // Iterate over every stored link. 211 // Iterate over every stored link.
207 foreach ($this->links as $id => $link) { 212 foreach ($this->links as $id => $link) {
208
209 // ignore non private links when 'privatonly' is on. 213 // ignore non private links when 'privatonly' is on.
210 if ($visibility !== 'all') { 214 if ($visibility !== 'all') {
211 if (! $link['private'] && $visibility === 'private') { 215 if (!$link['private'] && $visibility === 'private') {
212 continue; 216 continue;
213 } elseif ($link['private'] && $visibility === 'public') { 217 } elseif ($link['private'] && $visibility === 'public') {
214 continue; 218 continue;
@@ -251,17 +255,19 @@ class LinkFilter
251 255
252 /** 256 /**
253 * generate a regex fragment out of a tag 257 * generate a regex fragment out of a tag
258 *
254 * @param string $tag to to generate regexs from. may start with '-' to negate, contain '*' as wildcard 259 * @param string $tag to to generate regexs from. may start with '-' to negate, contain '*' as wildcard
260 *
255 * @return string generated regex fragment 261 * @return string generated regex fragment
256 */ 262 */
257 private static function tag2regex($tag) 263 private static function tag2regex($tag)
258 { 264 {
259 $len = strlen($tag); 265 $len = strlen($tag);
260 if(!$len || $tag === "-" || $tag === "*"){ 266 if (!$len || $tag === "-" || $tag === "*") {
261 // nothing to search, return empty regex 267 // nothing to search, return empty regex
262 return ''; 268 return '';
263 } 269 }
264 if($tag[0] === "-") { 270 if ($tag[0] === "-") {
265 // query is negated 271 // query is negated
266 $i = 1; // use offset to start after '-' character 272 $i = 1; // use offset to start after '-' character
267 $regex = '(?!'; // create negative lookahead 273 $regex = '(?!'; // create negative lookahead
@@ -271,14 +277,14 @@ class LinkFilter
271 } 277 }
272 $regex .= '.*(?:^| )'; // before tag may only be a space or the beginning 278 $regex .= '.*(?:^| )'; // before tag may only be a space or the beginning
273 // iterate over string, separating it into placeholder and content 279 // iterate over string, separating it into placeholder and content
274 for(; $i < $len; $i++){ 280 for (; $i < $len; $i++) {
275 if($tag[$i] === '*'){ 281 if ($tag[$i] === '*') {
276 // placeholder found 282 // placeholder found
277 $regex .= '[^ ]*?'; 283 $regex .= '[^ ]*?';
278 } else { 284 } else {
279 // regular characters 285 // regular characters
280 $offset = strpos($tag, '*', $i); 286 $offset = strpos($tag, '*', $i);
281 if($offset === false){ 287 if ($offset === false) {
282 // no placeholder found, set offset to end of string 288 // no placeholder found, set offset to end of string
283 $offset = $len; 289 $offset = $len;
284 } 290 }
@@ -310,19 +316,19 @@ class LinkFilter
310 { 316 {
311 // get single tags (we may get passed an array, even though the docs say different) 317 // get single tags (we may get passed an array, even though the docs say different)
312 $inputTags = $tags; 318 $inputTags = $tags;
313 if(!is_array($tags)) { 319 if (!is_array($tags)) {
314 // we got an input string, split tags 320 // we got an input string, split tags
315 $inputTags = preg_split('/(?:\s+)|,/', $inputTags, -1, PREG_SPLIT_NO_EMPTY); 321 $inputTags = preg_split('/(?:\s+)|,/', $inputTags, -1, PREG_SPLIT_NO_EMPTY);
316 } 322 }
317 323
318 if(!count($inputTags)){ 324 if (!count($inputTags)) {
319 // no input tags 325 // no input tags
320 return $this->noFilter($visibility); 326 return $this->noFilter($visibility);
321 } 327 }
322 328
323 // build regex from all tags 329 // build regex from all tags
324 $re = '/^' . implode(array_map("self::tag2regex", $inputTags)) . '.*$/'; 330 $re = '/^' . implode(array_map("self::tag2regex", $inputTags)) . '.*$/';
325 if(!$casesensitive) { 331 if (!$casesensitive) {
326 // make regex case insensitive 332 // make regex case insensitive
327 $re .= 'i'; 333 $re .= 'i';
328 } 334 }
@@ -335,14 +341,14 @@ class LinkFilter
335 // check level of visibility 341 // check level of visibility
336 // ignore non private links when 'privateonly' is on. 342 // ignore non private links when 'privateonly' is on.
337 if ($visibility !== 'all') { 343 if ($visibility !== 'all') {
338 if (! $link['private'] && $visibility === 'private') { 344 if (!$link['private'] && $visibility === 'private') {
339 continue; 345 continue;
340 } elseif ($link['private'] && $visibility === 'public') { 346 } elseif ($link['private'] && $visibility === 'public') {
341 continue; 347 continue;
342 } 348 }
343 } 349 }
344 $search = $link['tags']; // build search string, start with tags of current link 350 $search = $link['tags']; // build search string, start with tags of current link
345 if(strlen(trim($link['description'])) && strpos($link['description'], '#') !== false){ 351 if (strlen(trim($link['description'])) && strpos($link['description'], '#') !== false) {
346 // description given and at least one possible tag found 352 // description given and at least one possible tag found
347 $descTags = array(); 353 $descTags = array();
348 // find all tags in the form of #tag in the description 354 // find all tags in the form of #tag in the description
@@ -351,13 +357,13 @@ class LinkFilter
351 $link['description'], 357 $link['description'],
352 $descTags 358 $descTags
353 ); 359 );
354 if(count($descTags[1])){ 360 if (count($descTags[1])) {
355 // there were some tags in the description, add them to the search string 361 // there were some tags in the description, add them to the search string
356 $search .= ' ' . implode(' ', $descTags[1]); 362 $search .= ' ' . implode(' ', $descTags[1]);
357 } 363 }
358 }; 364 };
359 // match regular expression with search string 365 // match regular expression with search string
360 if(!preg_match($re, $search)){ 366 if (!preg_match($re, $search)) {
361 // this entry does _not_ match our regex 367 // this entry does _not_ match our regex
362 continue; 368 continue;
363 } 369 }
@@ -378,7 +384,7 @@ class LinkFilter
378 $filtered = []; 384 $filtered = [];
379 foreach ($this->links as $key => $link) { 385 foreach ($this->links as $key => $link) {
380 if ($visibility !== 'all') { 386 if ($visibility !== 'all') {
381 if (! $link['private'] && $visibility === 'private') { 387 if (!$link['private'] && $visibility === 'private') {
382 continue; 388 continue;
383 } elseif ($link['private'] && $visibility === 'public') { 389 } elseif ($link['private'] && $visibility === 'public') {
384 continue; 390 continue;
@@ -407,7 +413,7 @@ class LinkFilter
407 */ 413 */
408 public function filterDay($day) 414 public function filterDay($day)
409 { 415 {
410 if (! checkDateFormat('Ymd', $day)) { 416 if (!checkDateFormat('Ymd', $day)) {
411 throw new Exception('Invalid date format'); 417 throw new Exception('Invalid date format');
412 } 418 }
413 419
@@ -441,14 +447,3 @@ class LinkFilter
441 return preg_split('/\s+/', $tagsOut, -1, PREG_SPLIT_NO_EMPTY); 447 return preg_split('/\s+/', $tagsOut, -1, PREG_SPLIT_NO_EMPTY);
442 } 448 }
443} 449}
444
445class LinkNotFoundException extends Exception
446{
447 /**
448 * LinkNotFoundException constructor.
449 */
450 public function __construct()
451 {
452 $this->message = t('The link you are trying to reach does not exist or has been deleted.');
453 }
454}
diff --git a/application/LinkUtils.php b/application/bookmark/LinkUtils.php
index 4df5c0ca..de5b61cb 100644
--- a/application/LinkUtils.php
+++ b/application/bookmark/LinkUtils.php
@@ -1,11 +1,13 @@
1<?php 1<?php
2 2
3use Shaarli\Bookmark\LinkDB;
4
3/** 5/**
4 * Get cURL callback function for CURLOPT_WRITEFUNCTION 6 * Get cURL callback function for CURLOPT_WRITEFUNCTION
5 * 7 *
6 * @param string $charset to extract from the downloaded page (reference) 8 * @param string $charset to extract from the downloaded page (reference)
7 * @param string $title to extract from the downloaded page (reference) 9 * @param string $title to extract from the downloaded page (reference)
8 * @param string $curlGetInfo Optionnaly overrides curl_getinfo function 10 * @param string $curlGetInfo Optionally overrides curl_getinfo function
9 * 11 *
10 * @return Closure 12 * @return Closure
11 */ 13 */
@@ -23,7 +25,7 @@ function get_curl_download_callback(&$charset, &$title, $curlGetInfo = 'curl_get
23 * 25 *
24 * @return int|bool length of $data or false if we need to stop the download 26 * @return int|bool length of $data or false if we need to stop the download
25 */ 27 */
26 return function(&$ch, $data) use ($curlGetInfo, &$charset, &$title, &$isRedirected) { 28 return function (&$ch, $data) use ($curlGetInfo, &$charset, &$title, &$isRedirected) {
27 $responseCode = $curlGetInfo($ch, CURLINFO_RESPONSE_CODE); 29 $responseCode = $curlGetInfo($ch, CURLINFO_RESPONSE_CODE);
28 if (!empty($responseCode) && in_array($responseCode, [301, 302])) { 30 if (!empty($responseCode) && in_array($responseCode, [301, 302])) {
29 $isRedirected = true; 31 $isRedirected = true;
@@ -196,12 +198,13 @@ function space2nbsp($text)
196 * 198 *
197 * @param string $description shaare's description. 199 * @param string $description shaare's description.
198 * @param string $redirector if a redirector is set, use it to gerenate links. 200 * @param string $redirector if a redirector is set, use it to gerenate links.
199 * @param bool $urlEncode Use `urlencode()` on the URL after the redirector or not. 201 * @param bool $urlEncode Use `urlencode()` on the URL after the redirector or not.
200 * @param string $indexUrl URL to Shaarli's index. 202 * @param string $indexUrl URL to Shaarli's index.
201 203
202 * @return string formatted description. 204 * @return string formatted description.
203 */ 205 */
204function format_description($description, $redirector = '', $urlEncode = true, $indexUrl = '') { 206function format_description($description, $redirector = '', $urlEncode = true, $indexUrl = '')
207{
205 return nl2br(space2nbsp(hashtag_autolink(text2clickable($description, $redirector, $urlEncode), $indexUrl))); 208 return nl2br(space2nbsp(hashtag_autolink(text2clickable($description, $redirector, $urlEncode), $indexUrl)));
206} 209}
207 210
diff --git a/application/bookmark/exception/LinkNotFoundException.php b/application/bookmark/exception/LinkNotFoundException.php
new file mode 100644
index 00000000..f9414428
--- /dev/null
+++ b/application/bookmark/exception/LinkNotFoundException.php
@@ -0,0 +1,15 @@
1<?php
2namespace Shaarli\Bookmark\Exception;
3
4use Exception;
5
6class LinkNotFoundException extends Exception
7{
8 /**
9 * LinkNotFoundException constructor.
10 */
11 public function __construct()
12 {
13 $this->message = t('The link you are trying to reach does not exist or has been deleted.');
14 }
15}
diff --git a/application/config/ConfigJson.php b/application/config/ConfigJson.php
index 8c8d5610..4509357c 100644
--- a/application/config/ConfigJson.php
+++ b/application/config/ConfigJson.php
@@ -47,7 +47,7 @@ class ConfigJson implements ConfigIO
47 $print = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 0; 47 $print = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 0;
48 $data = self::getPhpHeaders() . json_encode($conf, $print) . self::getPhpSuffix(); 48 $data = self::getPhpHeaders() . json_encode($conf, $print) . self::getPhpSuffix();
49 if (!file_put_contents($filepath, $data)) { 49 if (!file_put_contents($filepath, $data)) {
50 throw new \IOException( 50 throw new \Shaarli\Exceptions\IOException(
51 $filepath, 51 $filepath,
52 t('Shaarli could not create the config file. '. 52 t('Shaarli could not create the config file. '.
53 'Please make sure Shaarli has the right to write in the folder is it installed in.') 53 'Please make sure Shaarli has the right to write in the folder is it installed in.')
diff --git a/application/config/ConfigManager.php b/application/config/ConfigManager.php
index 82f4a368..e6c35073 100644
--- a/application/config/ConfigManager.php
+++ b/application/config/ConfigManager.php
@@ -148,6 +148,33 @@ class ConfigManager
148 } 148 }
149 149
150 /** 150 /**
151 * Remove a config element from the config file.
152 *
153 * @param string $setting Asked setting, keys separated with dots.
154 * @param bool $write Write the new setting in the config file, default false.
155 * @param bool $isLoggedIn User login state, default false.
156 *
157 * @throws \Exception Invalid
158 */
159 public function remove($setting, $write = false, $isLoggedIn = false)
160 {
161 if (empty($setting) || ! is_string($setting)) {
162 throw new \Exception(t('Invalid setting key parameter. String expected, got: '). gettype($setting));
163 }
164
165 // During the ConfigIO transition, map legacy settings to the new ones.
166 if ($this->configIO instanceof ConfigPhp && isset(ConfigPhp::$LEGACY_KEYS_MAPPING[$setting])) {
167 $setting = ConfigPhp::$LEGACY_KEYS_MAPPING[$setting];
168 }
169
170 $settings = explode('.', $setting);
171 self::removeConfig($settings, $this->loadedConfig);
172 if ($write) {
173 $this->write($isLoggedIn);
174 }
175 }
176
177 /**
151 * Check if a settings exists. 178 * Check if a settings exists.
152 * 179 *
153 * Supports nested settings with dot separated keys. 180 * Supports nested settings with dot separated keys.
@@ -180,7 +207,7 @@ class ConfigManager
180 * 207 *
181 * @throws MissingFieldConfigException: a mandatory field has not been provided in $conf. 208 * @throws MissingFieldConfigException: a mandatory field has not been provided in $conf.
182 * @throws UnauthorizedConfigException: user is not authorize to change configuration. 209 * @throws UnauthorizedConfigException: user is not authorize to change configuration.
183 * @throws \IOException: an error occurred while writing the new config file. 210 * @throws \Shaarli\Exceptions\IOException: an error occurred while writing the new config file.
184 */ 211 */
185 public function write($isLoggedIn) 212 public function write($isLoggedIn)
186 { 213 {
@@ -272,7 +299,7 @@ class ConfigManager
272 * 299 *
273 * @param array $settings Ordered array which contains keys to find. 300 * @param array $settings Ordered array which contains keys to find.
274 * @param mixed $value 301 * @param mixed $value
275 * @param array $conf Loaded settings, then sub-array. 302 * @param array $conf Loaded settings, then sub-array.
276 * 303 *
277 * @return mixed Found setting or NOT_FOUND flag. 304 * @return mixed Found setting or NOT_FOUND flag.
278 */ 305 */
@@ -290,6 +317,27 @@ class ConfigManager
290 } 317 }
291 318
292 /** 319 /**
320 * Recursive function which find asked setting in the loaded config and deletes it.
321 *
322 * @param array $settings Ordered array which contains keys to find.
323 * @param array $conf Loaded settings, then sub-array.
324 *
325 * @return mixed Found setting or NOT_FOUND flag.
326 */
327 protected static function removeConfig($settings, &$conf)
328 {
329 if (!is_array($settings) || count($settings) == 0) {
330 return self::$NOT_FOUND;
331 }
332
333 $setting = array_shift($settings);
334 if (count($settings) > 0) {
335 return self::removeConfig($settings, $conf[$setting]);
336 }
337 unset($conf[$setting]);
338 }
339
340 /**
293 * Set a bunch of default values allowing Shaarli to start without a config file. 341 * Set a bunch of default values allowing Shaarli to start without a config file.
294 */ 342 */
295 protected function setDefaultValues() 343 protected function setDefaultValues()
@@ -333,12 +381,12 @@ class ConfigManager
333 // default state of the 'remember me' checkbox of the login form 381 // default state of the 'remember me' checkbox of the login form
334 $this->setEmpty('privacy.remember_user_default', true); 382 $this->setEmpty('privacy.remember_user_default', true);
335 383
336 $this->setEmpty('thumbnail.enable_thumbnails', true);
337 $this->setEmpty('thumbnail.enable_localcache', true);
338
339 $this->setEmpty('redirector.url', ''); 384 $this->setEmpty('redirector.url', '');
340 $this->setEmpty('redirector.encode_url', true); 385 $this->setEmpty('redirector.encode_url', true);
341 386
387 $this->setEmpty('thumbnails.width', '125');
388 $this->setEmpty('thumbnails.height', '90');
389
342 $this->setEmpty('translation.language', 'auto'); 390 $this->setEmpty('translation.language', 'auto');
343 $this->setEmpty('translation.mode', 'php'); 391 $this->setEmpty('translation.mode', 'php');
344 $this->setEmpty('translation.extensions', []); 392 $this->setEmpty('translation.extensions', []);
diff --git a/application/config/ConfigPhp.php b/application/config/ConfigPhp.php
index 8add8bcd..cad34594 100644
--- a/application/config/ConfigPhp.php
+++ b/application/config/ConfigPhp.php
@@ -27,7 +27,7 @@ class ConfigPhp implements ConfigIO
27 /** 27 /**
28 * Map legacy config keys with the new ones. 28 * Map legacy config keys with the new ones.
29 * If ConfigPhp is used, getting <newkey> will actually look for <legacykey>. 29 * If ConfigPhp is used, getting <newkey> will actually look for <legacykey>.
30 * The Updater will use this array to transform keys when switching to JSON. 30 * The updater will use this array to transform keys when switching to JSON.
31 * 31 *
32 * @var array current key => legacy key. 32 * @var array current key => legacy key.
33 */ 33 */
@@ -104,19 +104,27 @@ class ConfigPhp implements ConfigIO
104 104
105 // Store all $conf['config'] 105 // Store all $conf['config']
106 foreach ($conf['config'] as $key => $value) { 106 foreach ($conf['config'] as $key => $value) {
107 $configStr .= '$GLOBALS[\'config\'][\''. $key .'\'] = '.var_export($conf['config'][$key], true).';'. PHP_EOL; 107 $configStr .= '$GLOBALS[\'config\'][\''
108 . $key
109 .'\'] = '
110 .var_export($conf['config'][$key], true).';'
111 . PHP_EOL;
108 } 112 }
109 113
110 if (isset($conf['plugins'])) { 114 if (isset($conf['plugins'])) {
111 foreach ($conf['plugins'] as $key => $value) { 115 foreach ($conf['plugins'] as $key => $value) {
112 $configStr .= '$GLOBALS[\'plugins\'][\''. $key .'\'] = '.var_export($conf['plugins'][$key], true).';'. PHP_EOL; 116 $configStr .= '$GLOBALS[\'plugins\'][\''
117 . $key
118 .'\'] = '
119 .var_export($conf['plugins'][$key], true).';'
120 . PHP_EOL;
113 } 121 }
114 } 122 }
115 123
116 if (!file_put_contents($filepath, $configStr) 124 if (!file_put_contents($filepath, $configStr)
117 || strcmp(file_get_contents($filepath), $configStr) != 0 125 || strcmp(file_get_contents($filepath), $configStr) != 0
118 ) { 126 ) {
119 throw new \IOException( 127 throw new \Shaarli\Exceptions\IOException(
120 $filepath, 128 $filepath,
121 t('Shaarli could not create the config file. '. 129 t('Shaarli could not create the config file. '.
122 'Please make sure Shaarli has the right to write in the folder is it installed in.') 130 'Please make sure Shaarli has the right to write in the folder is it installed in.')
diff --git a/application/config/ConfigPlugin.php b/application/config/ConfigPlugin.php
index b3d9752b..dbb24937 100644
--- a/application/config/ConfigPlugin.php
+++ b/application/config/ConfigPlugin.php
@@ -34,8 +34,7 @@ function save_plugin_config($formData)
34 // If there is no order, it means a disabled plugin has been enabled. 34 // If there is no order, it means a disabled plugin has been enabled.
35 if (isset($formData['order_' . $key])) { 35 if (isset($formData['order_' . $key])) {
36 $plugins[(int) $formData['order_' . $key]] = $key; 36 $plugins[(int) $formData['order_' . $key]] = $key;
37 } 37 } else {
38 else {
39 $newEnabledPlugins[] = $key; 38 $newEnabledPlugins[] = $key;
40 } 39 }
41 } 40 }
diff --git a/application/exceptions/IOException.php b/application/exceptions/IOException.php
index 18e46b77..2aa25e5c 100644
--- a/application/exceptions/IOException.php
+++ b/application/exceptions/IOException.php
@@ -1,4 +1,7 @@
1<?php 1<?php
2namespace Shaarli\Exceptions;
3
4use Exception;
2 5
3/** 6/**
4 * Exception class thrown when a filesystem access failure happens 7 * Exception class thrown when a filesystem access failure happens
@@ -17,6 +20,6 @@ class IOException extends Exception
17 { 20 {
18 $this->path = $path; 21 $this->path = $path;
19 $this->message = empty($message) ? t('Error accessing') : $message; 22 $this->message = empty($message) ? t('Error accessing') : $message;
20 $this->message .= ' "' . $this->path .'"'; 23 $this->message .= ' "' . $this->path . '"';
21 } 24 }
22} 25}
diff --git a/application/Cache.php b/application/feed/Cache.php
index e5d43e61..e5d43e61 100644
--- a/application/Cache.php
+++ b/application/feed/Cache.php
diff --git a/application/CachedPage.php b/application/feed/CachedPage.php
index e11cc52d..d809bdd9 100644
--- a/application/CachedPage.php
+++ b/application/feed/CachedPage.php
@@ -1,4 +1,7 @@
1<?php 1<?php
2
3namespace Shaarli\Feed;
4
2/** 5/**
3 * Simple cache system, mainly for the RSS/ATOM feeds 6 * Simple cache system, mainly for the RSS/ATOM feeds
4 */ 7 */
@@ -24,7 +27,7 @@ class CachedPage
24 { 27 {
25 // TODO: check write access to the cache directory 28 // TODO: check write access to the cache directory
26 $this->cacheDir = $cacheDir; 29 $this->cacheDir = $cacheDir;
27 $this->filename = $this->cacheDir.'/'.sha1($url).'.cache'; 30 $this->filename = $this->cacheDir . '/' . sha1($url) . '.cache';
28 $this->shouldBeCached = $shouldBeCached; 31 $this->shouldBeCached = $shouldBeCached;
29 } 32 }
30 33
diff --git a/application/FeedBuilder.php b/application/feed/FeedBuilder.php
index ebae18b4..b66f2f91 100644
--- a/application/FeedBuilder.php
+++ b/application/feed/FeedBuilder.php
@@ -1,4 +1,7 @@
1<?php 1<?php
2namespace Shaarli\Feed;
3
4use DateTime;
2 5
3/** 6/**
4 * FeedBuilder class. 7 * FeedBuilder class.
@@ -28,7 +31,7 @@ class FeedBuilder
28 public static $DEFAULT_NB_LINKS = 50; 31 public static $DEFAULT_NB_LINKS = 50;
29 32
30 /** 33 /**
31 * @var LinkDB instance. 34 * @var \Shaarli\Bookmark\LinkDB instance.
32 */ 35 */
33 protected $linkDB; 36 protected $linkDB;
34 37
@@ -38,12 +41,12 @@ class FeedBuilder
38 protected $feedType; 41 protected $feedType;
39 42
40 /** 43 /**
41 * @var array $_SERVER. 44 * @var array $_SERVER
42 */ 45 */
43 protected $serverInfo; 46 protected $serverInfo;
44 47
45 /** 48 /**
46 * @var array $_GET. 49 * @var array $_GET
47 */ 50 */
48 protected $userInput; 51 protected $userInput;
49 52
@@ -75,11 +78,12 @@ class FeedBuilder
75 /** 78 /**
76 * Feed constructor. 79 * Feed constructor.
77 * 80 *
78 * @param LinkDB $linkDB LinkDB instance. 81 * @param \Shaarli\Bookmark\LinkDB $linkDB LinkDB instance.
79 * @param string $feedType Type of feed. 82 * @param string $feedType Type of feed.
80 * @param array $serverInfo $_SERVER. 83 * @param array $serverInfo $_SERVER.
81 * @param array $userInput $_GET. 84 * @param array $userInput $_GET.
82 * @param boolean $isLoggedIn True if the user is currently logged in, false otherwise. 85 * @param boolean $isLoggedIn True if the user is currently logged in,
86 * false otherwise.
83 */ 87 */
84 public function __construct($linkDB, $feedType, $serverInfo, $userInput, $isLoggedIn) 88 public function __construct($linkDB, $feedType, $serverInfo, $userInput, $isLoggedIn)
85 { 89 {
@@ -124,7 +128,7 @@ class FeedBuilder
124 $data['show_dates'] = !$this->hideDates || $this->isLoggedIn; 128 $data['show_dates'] = !$this->hideDates || $this->isLoggedIn;
125 // Remove leading slash from REQUEST_URI. 129 // Remove leading slash from REQUEST_URI.
126 $data['self_link'] = escape(server_url($this->serverInfo)) 130 $data['self_link'] = escape(server_url($this->serverInfo))
127 . escape($this->serverInfo['REQUEST_URI']); 131 . escape($this->serverInfo['REQUEST_URI']);
128 $data['index_url'] = $pageaddr; 132 $data['index_url'] = $pageaddr;
129 $data['usepermalinks'] = $this->usePermalinks === true; 133 $data['usepermalinks'] = $this->usePermalinks === true;
130 $data['links'] = $linkDisplayed; 134 $data['links'] = $linkDisplayed;
@@ -142,18 +146,18 @@ class FeedBuilder
142 */ 146 */
143 protected function buildItem($link, $pageaddr) 147 protected function buildItem($link, $pageaddr)
144 { 148 {
145 $link['guid'] = $pageaddr .'?'. $link['shorturl']; 149 $link['guid'] = $pageaddr . '?' . $link['shorturl'];
146 // Check for both signs of a note: starting with ? and 7 chars long. 150 // Check for both signs of a note: starting with ? and 7 chars long.
147 if ($link['url'][0] === '?' && strlen($link['url']) === 7) { 151 if ($link['url'][0] === '?' && strlen($link['url']) === 7) {
148 $link['url'] = $pageaddr . $link['url']; 152 $link['url'] = $pageaddr . $link['url'];
149 } 153 }
150 if ($this->usePermalinks === true) { 154 if ($this->usePermalinks === true) {
151 $permalink = '<a href="'. $link['url'] .'" title="'. t('Direct link') .'">'. t('Direct link') .'</a>'; 155 $permalink = '<a href="' . $link['url'] . '" title="' . t('Direct link') . '">' . t('Direct link') . '</a>';
152 } else { 156 } else {
153 $permalink = '<a href="'. $link['guid'] .'" title="'. t('Permalink') .'">'. t('Permalink') .'</a>'; 157 $permalink = '<a href="' . $link['guid'] . '" title="' . t('Permalink') . '">' . t('Permalink') . '</a>';
154 } 158 }
155 $link['description'] = format_description($link['description'], '', false, $pageaddr); 159 $link['description'] = format_description($link['description'], '', false, $pageaddr);
156 $link['description'] .= PHP_EOL .'<br>&#8212; '. $permalink; 160 $link['description'] .= PHP_EOL . '<br>&#8212; ' . $permalink;
157 161
158 $pubDate = $link['created']; 162 $pubDate = $link['created'];
159 $link['pub_iso_date'] = $this->getIsoDate($pubDate); 163 $link['pub_iso_date'] = $this->getIsoDate($pubDate);
@@ -163,7 +167,7 @@ class FeedBuilder
163 $upDate = $link['updated']; 167 $upDate = $link['updated'];
164 $link['up_iso_date'] = $this->getIsoDate($upDate, DateTime::ATOM); 168 $link['up_iso_date'] = $this->getIsoDate($upDate, DateTime::ATOM);
165 } else { 169 } else {
166 $link['up_iso_date'] = $this->getIsoDate($pubDate, DateTime::ATOM);; 170 $link['up_iso_date'] = $this->getIsoDate($pubDate, DateTime::ATOM);
167 } 171 }
168 172
169 // Save the more recent item. 173 // Save the more recent item.
@@ -222,11 +226,11 @@ class FeedBuilder
222 public function getTypeLanguage() 226 public function getTypeLanguage()
223 { 227 {
224 // Use the locale do define the language, if available. 228 // Use the locale do define the language, if available.
225 if (! empty($this->locale) && preg_match('/^\w{2}[_\-]\w{2}/', $this->locale)) { 229 if (!empty($this->locale) && preg_match('/^\w{2}[_\-]\w{2}/', $this->locale)) {
226 $length = ($this->feedType == self::$FEED_RSS) ? 5 : 2; 230 $length = ($this->feedType === self::$FEED_RSS) ? 5 : 2;
227 return str_replace('_', '-', substr($this->locale, 0, $length)); 231 return str_replace('_', '-', substr($this->locale, 0, $length));
228 } 232 }
229 return ($this->feedType == self::$FEED_RSS) ? 'en-en' : 'en'; 233 return ($this->feedType === self::$FEED_RSS) ? 'en-en' : 'en';
230 } 234 }
231 235
232 /** 236 /**
@@ -261,7 +265,6 @@ class FeedBuilder
261 } 265 }
262 if ($this->feedType == self::$FEED_RSS) { 266 if ($this->feedType == self::$FEED_RSS) {
263 return $date->format(DateTime::RSS); 267 return $date->format(DateTime::RSS);
264
265 } 268 }
266 return $date->format(DateTime::ATOM); 269 return $date->format(DateTime::ATOM);
267 } 270 }
@@ -287,7 +290,7 @@ class FeedBuilder
287 } 290 }
288 291
289 $intNb = intval($this->userInput['nb']); 292 $intNb = intval($this->userInput['nb']);
290 if (! is_int($intNb) || $intNb == 0) { 293 if (!is_int($intNb) || $intNb == 0) {
291 return self::$DEFAULT_NB_LINKS; 294 return self::$DEFAULT_NB_LINKS;
292 } 295 }
293 296
diff --git a/application/Base64Url.php b/application/http/Base64Url.php
index 61590e43..33fa7c1f 100644
--- a/application/Base64Url.php
+++ b/application/http/Base64Url.php
@@ -1,7 +1,6 @@
1<?php 1<?php
2 2
3namespace Shaarli; 3namespace Shaarli\Http;
4
5 4
6/** 5/**
7 * URL-safe Base64 operations 6 * URL-safe Base64 operations
@@ -17,7 +16,8 @@ class Base64Url
17 * 16 *
18 * @return string Base64Url-encoded data 17 * @return string Base64Url-encoded data
19 */ 18 */
20 public static function encode($data) { 19 public static function encode($data)
20 {
21 return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); 21 return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
22 } 22 }
23 23
@@ -28,7 +28,8 @@ class Base64Url
28 * 28 *
29 * @return string Decoded data 29 * @return string Decoded data
30 */ 30 */
31 public static function decode($data) { 31 public static function decode($data)
32 {
32 return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT)); 33 return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT));
33 } 34 }
34} 35}
diff --git a/application/HttpUtils.php b/application/http/HttpUtils.php
index e9282506..2ea9195d 100644
--- a/application/HttpUtils.php
+++ b/application/http/HttpUtils.php
@@ -1,4 +1,7 @@
1<?php 1<?php
2
3use Shaarli\Http\Url;
4
2/** 5/**
3 * GET an HTTP URL to retrieve its content 6 * GET an HTTP URL to retrieve its content
4 * Uses the cURL library or a fallback method 7 * Uses the cURL library or a fallback method
@@ -7,7 +10,8 @@
7 * @param int $timeout network timeout (in seconds) 10 * @param int $timeout network timeout (in seconds)
8 * @param int $maxBytes maximum downloaded bytes (default: 4 MiB) 11 * @param int $maxBytes maximum downloaded bytes (default: 4 MiB)
9 * @param callable|string $curlWriteFunction Optional callback called during the download (cURL CURLOPT_WRITEFUNCTION). 12 * @param callable|string $curlWriteFunction Optional callback called during the download (cURL CURLOPT_WRITEFUNCTION).
10 * Can be used to add download conditions on the headers (response code, content type, etc.). 13 * Can be used to add download conditions on the
14 * headers (response code, content type, etc.).
11 * 15 *
12 * @return array HTTP response headers, downloaded content 16 * @return array HTTP response headers, downloaded content
13 * 17 *
@@ -37,7 +41,7 @@ function get_http_response($url, $timeout = 30, $maxBytes = 4194304, $curlWriteF
37 $cleanUrl = $urlObj->idnToAscii(); 41 $cleanUrl = $urlObj->idnToAscii();
38 42
39 if (!filter_var($cleanUrl, FILTER_VALIDATE_URL) || !$urlObj->isHttp()) { 43 if (!filter_var($cleanUrl, FILTER_VALIDATE_URL) || !$urlObj->isHttp()) {
40 return array(array(0 => 'Invalid HTTP Url'), false); 44 return array(array(0 => 'Invalid HTTP UrlUtils'), false);
41 } 45 }
42 46
43 $userAgent = 47 $userAgent =
@@ -64,29 +68,30 @@ function get_http_response($url, $timeout = 30, $maxBytes = 4194304, $curlWriteF
64 } 68 }
65 69
66 // General cURL settings 70 // General cURL settings
67 curl_setopt($ch, CURLOPT_AUTOREFERER, true); 71 curl_setopt($ch, CURLOPT_AUTOREFERER, true);
68 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); 72 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
69 curl_setopt($ch, CURLOPT_HEADER, true); 73 curl_setopt($ch, CURLOPT_HEADER, true);
70 curl_setopt( 74 curl_setopt(
71 $ch, 75 $ch,
72 CURLOPT_HTTPHEADER, 76 CURLOPT_HTTPHEADER,
73 array('Accept-Language: ' . $acceptLanguage) 77 array('Accept-Language: ' . $acceptLanguage)
74 ); 78 );
75 curl_setopt($ch, CURLOPT_MAXREDIRS, $maxRedirs); 79 curl_setopt($ch, CURLOPT_MAXREDIRS, $maxRedirs);
76 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 80 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
77 curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); 81 curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
78 curl_setopt($ch, CURLOPT_USERAGENT, $userAgent); 82 curl_setopt($ch, CURLOPT_USERAGENT, $userAgent);
79 83
80 if (is_callable($curlWriteFunction)) { 84 if (is_callable($curlWriteFunction)) {
81 curl_setopt($ch, CURLOPT_WRITEFUNCTION, $curlWriteFunction); 85 curl_setopt($ch, CURLOPT_WRITEFUNCTION, $curlWriteFunction);
82 } 86 }
83 87
84 // Max download size management 88 // Max download size management
85 curl_setopt($ch, CURLOPT_BUFFERSIZE, 1024*16); 89 curl_setopt($ch, CURLOPT_BUFFERSIZE, 1024*16);
86 curl_setopt($ch, CURLOPT_NOPROGRESS, false); 90 curl_setopt($ch, CURLOPT_NOPROGRESS, false);
87 curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, 91 curl_setopt(
88 function($arg0, $arg1, $arg2, $arg3, $arg4 = 0) use ($maxBytes) 92 $ch,
89 { 93 CURLOPT_PROGRESSFUNCTION,
94 function ($arg0, $arg1, $arg2, $arg3, $arg4 = 0) use ($maxBytes) {
90 if (version_compare(phpversion(), '5.5', '<')) { 95 if (version_compare(phpversion(), '5.5', '<')) {
91 // PHP version lower than 5.5 96 // PHP version lower than 5.5
92 // Callback has 4 arguments 97 // Callback has 4 arguments
@@ -232,7 +237,6 @@ function get_redirected_headers($url, $redirectionLimit = 3)
232 && !empty($headers) 237 && !empty($headers)
233 && (strpos($headers[0], '301') !== false || strpos($headers[0], '302') !== false) 238 && (strpos($headers[0], '301') !== false || strpos($headers[0], '302') !== false)
234 && !empty($headers['Location'])) { 239 && !empty($headers['Location'])) {
235
236 $redirection = is_array($headers['Location']) ? end($headers['Location']) : $headers['Location']; 240 $redirection = is_array($headers['Location']) ? end($headers['Location']) : $headers['Location'];
237 if ($redirection != $url) { 241 if ($redirection != $url) {
238 $redirection = getAbsoluteUrl($url, $redirection); 242 $redirection = getAbsoluteUrl($url, $redirection);
diff --git a/application/Url.php b/application/http/Url.php
index 6b9870f0..90444a2f 100644
--- a/application/Url.php
+++ b/application/http/Url.php
@@ -1,91 +1,6 @@
1<?php 1<?php
2/**
3 * Converts an array-represented URL to a string
4 *
5 * Source: http://php.net/manual/en/function.parse-url.php#106731
6 *
7 * @see http://php.net/manual/en/function.parse-url.php
8 *
9 * @param array $parsedUrl an array-represented URL
10 *
11 * @return string the string representation of the URL
12 */
13function unparse_url($parsedUrl)
14{
15 $scheme = isset($parsedUrl['scheme']) ? $parsedUrl['scheme'].'://' : '';
16 $host = isset($parsedUrl['host']) ? $parsedUrl['host'] : '';
17 $port = isset($parsedUrl['port']) ? ':'.$parsedUrl['port'] : '';
18 $user = isset($parsedUrl['user']) ? $parsedUrl['user'] : '';
19 $pass = isset($parsedUrl['pass']) ? ':'.$parsedUrl['pass'] : '';
20 $pass = ($user || $pass) ? "$pass@" : '';
21 $path = isset($parsedUrl['path']) ? $parsedUrl['path'] : '';
22 $query = isset($parsedUrl['query']) ? '?'.$parsedUrl['query'] : '';
23 $fragment = isset($parsedUrl['fragment']) ? '#'.$parsedUrl['fragment'] : '';
24
25 return "$scheme$user$pass$host$port$path$query$fragment";
26}
27
28/**
29 * Removes undesired query parameters and fragments
30 *
31 * @param string url Url to be cleaned
32 *
33 * @return string the string representation of this URL after cleanup
34 */
35function cleanup_url($url)
36{
37 $obj_url = new Url($url);
38 return $obj_url->cleanup();
39}
40 2
41/** 3namespace Shaarli\Http;
42 * Get URL scheme.
43 *
44 * @param string url Url for which the scheme is requested
45 *
46 * @return mixed the URL scheme or false if none is provided.
47 */
48function get_url_scheme($url)
49{
50 $obj_url = new Url($url);
51 return $obj_url->getScheme();
52}
53
54/**
55 * Adds a trailing slash at the end of URL if necessary.
56 *
57 * @param string $url URL to check/edit.
58 *
59 * @return string $url URL with a end trailing slash.
60 */
61function add_trailing_slash($url)
62{
63 return $url . (!endsWith($url, '/') ? '/' : '');
64}
65
66/**
67 * Replace not whitelisted protocols by 'http://' from given URL.
68 *
69 * @param string $url URL to clean
70 * @param array $protocols List of allowed protocols (aside from http(s)).
71 *
72 * @return string URL with allowed protocol
73 */
74function whitelist_protocols($url, $protocols)
75{
76 if (startsWith($url, '?') || startsWith($url, '/')) {
77 return $url;
78 }
79 $protocols = array_merge(['http', 'https'], $protocols);
80 $protocol = preg_match('#^(\w+):/?/?#', $url, $match);
81 // Protocol not allowed: we remove it and replace it with http
82 if ($protocol === 1 && ! in_array($match[1], $protocols)) {
83 $url = str_replace($match[0], 'http://', $url);
84 } elseif ($protocol !== 1) {
85 $url = 'http://' . $url;
86 }
87 return $url;
88}
89 4
90/** 5/**
91 * URL representation and cleanup utilities 6 * URL representation and cleanup utilities
@@ -182,7 +97,7 @@ class Url
182 } 97 }
183 return $input; 98 return $input;
184 } 99 }
185 100
186 /** 101 /**
187 * Returns a string representation of this URL 102 * Returns a string representation of this URL
188 */ 103 */
@@ -196,7 +111,7 @@ class Url
196 */ 111 */
197 protected function cleanupQuery() 112 protected function cleanupQuery()
198 { 113 {
199 if (! isset($this->parts['query'])) { 114 if (!isset($this->parts['query'])) {
200 return; 115 return;
201 } 116 }
202 117
@@ -217,14 +132,14 @@ class Url
217 } 132 }
218 133
219 $this->parts['query'] = implode('&', $queryParams); 134 $this->parts['query'] = implode('&', $queryParams);
220 } 135 }
221 136
222 /** 137 /**
223 * Removes undesired fragments 138 * Removes undesired fragments
224 */ 139 */
225 protected function cleanupFragment() 140 protected function cleanupFragment()
226 { 141 {
227 if (! isset($this->parts['fragment'])) { 142 if (!isset($this->parts['fragment'])) {
228 return; 143 return;
229 } 144 }
230 145
@@ -257,7 +172,7 @@ class Url
257 public function idnToAscii() 172 public function idnToAscii()
258 { 173 {
259 $out = $this->cleanup(); 174 $out = $this->cleanup();
260 if (! function_exists('idn_to_ascii') || ! isset($this->parts['host'])) { 175 if (!function_exists('idn_to_ascii') || !isset($this->parts['host'])) {
261 return $out; 176 return $out;
262 } 177 }
263 $asciiHost = idn_to_ascii($this->parts['host'], 0, INTL_IDNA_VARIANT_UTS46); 178 $asciiHost = idn_to_ascii($this->parts['host'], 0, INTL_IDNA_VARIANT_UTS46);
@@ -269,7 +184,8 @@ class Url
269 * 184 *
270 * @return string the URL scheme or false if none is provided. 185 * @return string the URL scheme or false if none is provided.
271 */ 186 */
272 public function getScheme() { 187 public function getScheme()
188 {
273 if (!isset($this->parts['scheme'])) { 189 if (!isset($this->parts['scheme'])) {
274 return false; 190 return false;
275 } 191 }
@@ -281,7 +197,8 @@ class Url
281 * 197 *
282 * @return string the URL host or false if none is provided. 198 * @return string the URL host or false if none is provided.
283 */ 199 */
284 public function getHost() { 200 public function getHost()
201 {
285 if (empty($this->parts['host'])) { 202 if (empty($this->parts['host'])) {
286 return false; 203 return false;
287 } 204 }
@@ -289,11 +206,12 @@ class Url
289 } 206 }
290 207
291 /** 208 /**
292 * Test if the Url is an HTTP one. 209 * Test if the UrlUtils is an HTTP one.
293 * 210 *
294 * @return true is HTTP, false otherwise. 211 * @return true is HTTP, false otherwise.
295 */ 212 */
296 public function isHttp() { 213 public function isHttp()
214 {
297 return strpos(strtolower($this->parts['scheme']), 'http') !== false; 215 return strpos(strtolower($this->parts['scheme']), 'http') !== false;
298 } 216 }
299} 217}
diff --git a/application/http/UrlUtils.php b/application/http/UrlUtils.php
new file mode 100644
index 00000000..4bc84b82
--- /dev/null
+++ b/application/http/UrlUtils.php
@@ -0,0 +1,88 @@
1<?php
2/**
3 * Converts an array-represented URL to a string
4 *
5 * Source: http://php.net/manual/en/function.parse-url.php#106731
6 *
7 * @see http://php.net/manual/en/function.parse-url.php
8 *
9 * @param array $parsedUrl an array-represented URL
10 *
11 * @return string the string representation of the URL
12 */
13function unparse_url($parsedUrl)
14{
15 $scheme = isset($parsedUrl['scheme']) ? $parsedUrl['scheme'].'://' : '';
16 $host = isset($parsedUrl['host']) ? $parsedUrl['host'] : '';
17 $port = isset($parsedUrl['port']) ? ':'.$parsedUrl['port'] : '';
18 $user = isset($parsedUrl['user']) ? $parsedUrl['user'] : '';
19 $pass = isset($parsedUrl['pass']) ? ':'.$parsedUrl['pass'] : '';
20 $pass = ($user || $pass) ? "$pass@" : '';
21 $path = isset($parsedUrl['path']) ? $parsedUrl['path'] : '';
22 $query = isset($parsedUrl['query']) ? '?'.$parsedUrl['query'] : '';
23 $fragment = isset($parsedUrl['fragment']) ? '#'.$parsedUrl['fragment'] : '';
24
25 return "$scheme$user$pass$host$port$path$query$fragment";
26}
27
28/**
29 * Removes undesired query parameters and fragments
30 *
31 * @param string url UrlUtils to be cleaned
32 *
33 * @return string the string representation of this URL after cleanup
34 */
35function cleanup_url($url)
36{
37 $obj_url = new \Shaarli\Http\Url($url);
38 return $obj_url->cleanup();
39}
40
41/**
42 * Get URL scheme.
43 *
44 * @param string url UrlUtils for which the scheme is requested
45 *
46 * @return mixed the URL scheme or false if none is provided.
47 */
48function get_url_scheme($url)
49{
50 $obj_url = new \Shaarli\Http\Url($url);
51 return $obj_url->getScheme();
52}
53
54/**
55 * Adds a trailing slash at the end of URL if necessary.
56 *
57 * @param string $url URL to check/edit.
58 *
59 * @return string $url URL with a end trailing slash.
60 */
61function add_trailing_slash($url)
62{
63 return $url . (!endsWith($url, '/') ? '/' : '');
64}
65
66/**
67 * Replace not whitelisted protocols by 'http://' from given URL.
68 *
69 * @param string $url URL to clean
70 * @param array $protocols List of allowed protocols (aside from http(s)).
71 *
72 * @return string URL with allowed protocol
73 */
74function whitelist_protocols($url, $protocols)
75{
76 if (startsWith($url, '?') || startsWith($url, '/')) {
77 return $url;
78 }
79 $protocols = array_merge(['http', 'https'], $protocols);
80 $protocol = preg_match('#^(\w+):/?/?#', $url, $match);
81 // Protocol not allowed: we remove it and replace it with http
82 if ($protocol === 1 && ! in_array($match[1], $protocols)) {
83 $url = str_replace($match[0], 'http://', $url);
84 } elseif ($protocol !== 1) {
85 $url = 'http://' . $url;
86 }
87 return $url;
88}
diff --git a/application/NetscapeBookmarkUtils.php b/application/netscape/NetscapeBookmarkUtils.php
index b4d16d00..2fb1a4a6 100644
--- a/application/NetscapeBookmarkUtils.php
+++ b/application/netscape/NetscapeBookmarkUtils.php
@@ -1,9 +1,16 @@
1<?php 1<?php
2 2
3namespace Shaarli\Netscape;
4
5use DateTime;
6use DateTimeZone;
7use Exception;
8use Katzgrau\KLogger\Logger;
3use Psr\Log\LogLevel; 9use Psr\Log\LogLevel;
10use Shaarli\Bookmark\LinkDB;
4use Shaarli\Config\ConfigManager; 11use Shaarli\Config\ConfigManager;
12use Shaarli\History;
5use Shaarli\NetscapeBookmarkParser\NetscapeBookmarkParser; 13use Shaarli\NetscapeBookmarkParser\NetscapeBookmarkParser;
6use Katzgrau\KLogger\Logger;
7 14
8/** 15/**
9 * Utilities to import and export bookmarks using the Netscape format 16 * Utilities to import and export bookmarks using the Netscape format
@@ -31,8 +38,8 @@ class NetscapeBookmarkUtils
31 public static function filterAndFormat($linkDb, $selection, $prependNoteUrl, $indexUrl) 38 public static function filterAndFormat($linkDb, $selection, $prependNoteUrl, $indexUrl)
32 { 39 {
33 // see tpl/export.html for possible values 40 // see tpl/export.html for possible values
34 if (! in_array($selection, array('all', 'public', 'private'))) { 41 if (!in_array($selection, array('all', 'public', 'private'))) {
35 throw new Exception(t('Invalid export selection:') .' "'.$selection.'"'); 42 throw new Exception(t('Invalid export selection:') . ' "' . $selection . '"');
36 } 43 }
37 44
38 $bookmarkLinks = array(); 45 $bookmarkLinks = array();
@@ -72,18 +79,20 @@ class NetscapeBookmarkUtils
72 private static function importStatus( 79 private static function importStatus(
73 $filename, 80 $filename,
74 $filesize, 81 $filesize,
75 $importCount=0, 82 $importCount = 0,
76 $overwriteCount=0, 83 $overwriteCount = 0,
77 $skipCount=0, 84 $skipCount = 0,
78 $duration=0 85 $duration = 0
79 ) 86 ) {
80 {
81 $status = sprintf(t('File %s (%d bytes) '), $filename, $filesize); 87 $status = sprintf(t('File %s (%d bytes) '), $filename, $filesize);
82 if ($importCount == 0 && $overwriteCount == 0 && $skipCount == 0) { 88 if ($importCount == 0 && $overwriteCount == 0 && $skipCount == 0) {
83 $status .= t('has an unknown file format. Nothing was imported.'); 89 $status .= t('has an unknown file format. Nothing was imported.');
84 } else { 90 } else {
85 $status .= vsprintf( 91 $status .= vsprintf(
86 t('was successfully processed in %d seconds: %d links imported, %d links overwritten, %d links skipped.'), 92 t(
93 'was successfully processed in %d seconds: '
94 . '%d links imported, %d links overwritten, %d links skipped.'
95 ),
87 [$duration, $importCount, $overwriteCount, $skipCount] 96 [$duration, $importCount, $overwriteCount, $skipCount]
88 ); 97 );
89 } 98 }
@@ -93,11 +102,11 @@ class NetscapeBookmarkUtils
93 /** 102 /**
94 * Imports Web bookmarks from an uploaded Netscape bookmark dump 103 * Imports Web bookmarks from an uploaded Netscape bookmark dump
95 * 104 *
96 * @param array $post Server $_POST parameters 105 * @param array $post Server $_POST parameters
97 * @param array $files Server $_FILES parameters 106 * @param array $files Server $_FILES parameters
98 * @param LinkDB $linkDb Loaded LinkDB instance 107 * @param LinkDB $linkDb Loaded LinkDB instance
99 * @param ConfigManager $conf instance 108 * @param ConfigManager $conf instance
100 * @param History $history History instance 109 * @param History $history History instance
101 * 110 *
102 * @return string Summary of the bookmark import status 111 * @return string Summary of the bookmark import status
103 */ 112 */
@@ -113,7 +122,7 @@ class NetscapeBookmarkUtils
113 } 122 }
114 123
115 // Overwrite existing links? 124 // Overwrite existing links?
116 $overwrite = ! empty($post['overwrite']); 125 $overwrite = !empty($post['overwrite']);
117 126
118 // Add tags to all imported links? 127 // Add tags to all imported links?
119 if (empty($post['default_tags'])) { 128 if (empty($post['default_tags'])) {
@@ -136,7 +145,7 @@ class NetscapeBookmarkUtils
136 ); 145 );
137 $logger = new Logger( 146 $logger = new Logger(
138 $conf->get('resource.data_dir'), 147 $conf->get('resource.data_dir'),
139 ! $conf->get('dev.debug') ? LogLevel::INFO : LogLevel::DEBUG, 148 !$conf->get('dev.debug') ? LogLevel::INFO : LogLevel::DEBUG,
140 [ 149 [
141 'prefix' => 'import.', 150 'prefix' => 'import.',
142 'extension' => 'log', 151 'extension' => 'log',
@@ -191,7 +200,7 @@ class NetscapeBookmarkUtils
191 } 200 }
192 201
193 // Add a new link - @ used for UNIX timestamps 202 // Add a new link - @ used for UNIX timestamps
194 $newLinkDate = new DateTime('@'.strval($bkm['time'])); 203 $newLinkDate = new DateTime('@' . strval($bkm['time']));
195 $newLinkDate->setTimezone(new DateTimeZone(date_default_timezone_get())); 204 $newLinkDate->setTimezone(new DateTimeZone(date_default_timezone_get()));
196 $newLink['created'] = $newLinkDate; 205 $newLink['created'] = $newLinkDate;
197 $newLink['id'] = $linkDb->getNextId(); 206 $newLink['id'] = $linkDb->getNextId();
diff --git a/application/PluginManager.php b/application/plugin/PluginManager.php
index cf603845..f7b24a8e 100644
--- a/application/PluginManager.php
+++ b/application/plugin/PluginManager.php
@@ -1,4 +1,8 @@
1<?php 1<?php
2namespace Shaarli\Plugin;
3
4use Shaarli\Config\ConfigManager;
5use Shaarli\Plugin\Exception\PluginFileNotFoundException;
2 6
3/** 7/**
4 * Class PluginManager 8 * Class PluginManager
@@ -9,12 +13,14 @@ class PluginManager
9{ 13{
10 /** 14 /**
11 * List of authorized plugins from configuration file. 15 * List of authorized plugins from configuration file.
16 *
12 * @var array $authorizedPlugins 17 * @var array $authorizedPlugins
13 */ 18 */
14 private $authorizedPlugins; 19 private $authorizedPlugins;
15 20
16 /** 21 /**
17 * List of loaded plugins. 22 * List of loaded plugins.
23 *
18 * @var array $loadedPlugins 24 * @var array $loadedPlugins
19 */ 25 */
20 private $loadedPlugins = array(); 26 private $loadedPlugins = array();
@@ -31,12 +37,14 @@ class PluginManager
31 37
32 /** 38 /**
33 * Plugins subdirectory. 39 * Plugins subdirectory.
40 *
34 * @var string $PLUGINS_PATH 41 * @var string $PLUGINS_PATH
35 */ 42 */
36 public static $PLUGINS_PATH = 'plugins'; 43 public static $PLUGINS_PATH = 'plugins';
37 44
38 /** 45 /**
39 * Plugins meta files extension. 46 * Plugins meta files extension.
47 *
40 * @var string $META_EXT 48 * @var string $META_EXT
41 */ 49 */
42 public static $META_EXT = 'meta'; 50 public static $META_EXT = 'meta';
@@ -75,8 +83,7 @@ class PluginManager
75 83
76 try { 84 try {
77 $this->loadPlugin($dirs[$index], $plugin); 85 $this->loadPlugin($dirs[$index], $plugin);
78 } 86 } catch (PluginFileNotFoundException $e) {
79 catch (PluginFileNotFoundException $e) {
80 error_log($e->getMessage()); 87 error_log($e->getMessage());
81 } 88 }
82 } 89 }
@@ -85,9 +92,9 @@ class PluginManager
85 /** 92 /**
86 * Execute all plugins registered hook. 93 * Execute all plugins registered hook.
87 * 94 *
88 * @param string $hook name of the hook to trigger. 95 * @param string $hook name of the hook to trigger.
89 * @param array $data list of data to manipulate passed by reference. 96 * @param array $data list of data to manipulate passed by reference.
90 * @param array $params additional parameters such as page target. 97 * @param array $params additional parameters such as page target.
91 * 98 *
92 * @return void 99 * @return void
93 */ 100 */
@@ -119,7 +126,7 @@ class PluginManager
119 * @param string $pluginName plugin's name. 126 * @param string $pluginName plugin's name.
120 * 127 *
121 * @return void 128 * @return void
122 * @throws PluginFileNotFoundException - plugin files not found. 129 * @throws \Shaarli\Plugin\Exception\PluginFileNotFoundException - plugin files not found.
123 */ 130 */
124 private function loadPlugin($dir, $pluginName) 131 private function loadPlugin($dir, $pluginName)
125 { 132 {
@@ -205,8 +212,8 @@ class PluginManager
205 212
206 $metaData[$plugin]['parameters'][$param]['value'] = ''; 213 $metaData[$plugin]['parameters'][$param]['value'] = '';
207 // Optional parameter description in parameter.PARAM_NAME= 214 // Optional parameter description in parameter.PARAM_NAME=
208 if (isset($metaData[$plugin]['parameter.'. $param])) { 215 if (isset($metaData[$plugin]['parameter.' . $param])) {
209 $metaData[$plugin]['parameters'][$param]['desc'] = t($metaData[$plugin]['parameter.'. $param]); 216 $metaData[$plugin]['parameters'][$param]['desc'] = t($metaData[$plugin]['parameter.' . $param]);
210 } 217 }
211 } 218 }
212 } 219 }
@@ -224,22 +231,3 @@ class PluginManager
224 return $this->errors; 231 return $this->errors;
225 } 232 }
226} 233}
227
228/**
229 * Class PluginFileNotFoundException
230 *
231 * Raise when plugin files can't be found.
232 */
233class PluginFileNotFoundException extends Exception
234{
235 /**
236 * Construct exception with plugin name.
237 * Generate message.
238 *
239 * @param string $pluginName name of the plugin not found
240 */
241 public function __construct($pluginName)
242 {
243 $this->message = sprintf(t('Plugin "%s" files not found.'), $pluginName);
244 }
245}
diff --git a/application/plugin/exception/PluginFileNotFoundException.php b/application/plugin/exception/PluginFileNotFoundException.php
new file mode 100644
index 00000000..e5386f02
--- /dev/null
+++ b/application/plugin/exception/PluginFileNotFoundException.php
@@ -0,0 +1,23 @@
1<?php
2namespace Shaarli\Plugin\Exception;
3
4use Exception;
5
6/**
7 * Class PluginFileNotFoundException
8 *
9 * Raise when plugin files can't be found.
10 */
11class PluginFileNotFoundException extends Exception
12{
13 /**
14 * Construct exception with plugin name.
15 * Generate message.
16 *
17 * @param string $pluginName name of the plugin not found
18 */
19 public function __construct($pluginName)
20 {
21 $this->message = sprintf(t('Plugin "%s" files not found.'), $pluginName);
22 }
23}
diff --git a/application/PageBuilder.php b/application/render/PageBuilder.php
index a4483870..0569b67f 100644
--- a/application/PageBuilder.php
+++ b/application/render/PageBuilder.php
@@ -1,6 +1,13 @@
1<?php 1<?php
2 2
3namespace Shaarli\Render;
4
5use Exception;
6use RainTPL;
7use Shaarli\ApplicationUtils;
8use Shaarli\Bookmark\LinkDB;
3use Shaarli\Config\ConfigManager; 9use Shaarli\Config\ConfigManager;
10use Shaarli\Thumbnailer;
4 11
5/** 12/**
6 * This class is in charge of building the final page. 13 * This class is in charge of building the final page.
@@ -22,25 +29,40 @@ class PageBuilder
22 protected $conf; 29 protected $conf;
23 30
24 /** 31 /**
32 * @var array $_SESSION
33 */
34 protected $session;
35
36 /**
25 * @var LinkDB $linkDB instance. 37 * @var LinkDB $linkDB instance.
26 */ 38 */
27 protected $linkDB; 39 protected $linkDB;
28 40
29 /** @var bool $isLoggedIn Whether the user is logged in **/ 41 /**
42 * @var null|string XSRF token
43 */
44 protected $token;
45
46 /**
47 * @var bool $isLoggedIn Whether the user is logged in
48 */
30 protected $isLoggedIn = false; 49 protected $isLoggedIn = false;
31 50
32 /** 51 /**
33 * PageBuilder constructor. 52 * PageBuilder constructor.
34 * $tpl is initialized at false for lazy loading. 53 * $tpl is initialized at false for lazy loading.
35 * 54 *
36 * @param ConfigManager $conf Configuration Manager instance (reference). 55 * @param ConfigManager $conf Configuration Manager instance (reference).
37 * @param LinkDB $linkDB instance. 56 * @param array $session $_SESSION array
38 * @param string $token Session token 57 * @param LinkDB $linkDB instance.
58 * @param string $token Session token
59 * @param bool $isLoggedIn
39 */ 60 */
40 public function __construct(&$conf, $linkDB = null, $token = null, $isLoggedIn = false) 61 public function __construct(&$conf, $session, $linkDB = null, $token = null, $isLoggedIn = false)
41 { 62 {
42 $this->tpl = false; 63 $this->tpl = false;
43 $this->conf = $conf; 64 $this->conf = $conf;
65 $this->session = $session;
44 $this->linkDB = $linkDB; 66 $this->linkDB = $linkDB;
45 $this->token = $token; 67 $this->token = $token;
46 $this->isLoggedIn = $isLoggedIn; 68 $this->isLoggedIn = $isLoggedIn;
@@ -64,7 +86,6 @@ class PageBuilder
64 ); 86 );
65 $this->tpl->assign('newVersion', escape($version)); 87 $this->tpl->assign('newVersion', escape($version));
66 $this->tpl->assign('versionError', ''); 88 $this->tpl->assign('versionError', '');
67
68 } catch (Exception $exc) { 89 } catch (Exception $exc) {
69 logm($this->conf->get('resource.log'), $_SERVER['REMOTE_ADDR'], $exc->getMessage()); 90 logm($this->conf->get('resource.log'), $_SERVER['REMOTE_ADDR'], $exc->getMessage());
70 $this->tpl->assign('newVersion', ''); 91 $this->tpl->assign('newVersion', '');
@@ -87,8 +108,8 @@ class PageBuilder
87 'version_hash', 108 'version_hash',
88 ApplicationUtils::getVersionHash(SHAARLI_VERSION, $this->conf->get('credentials.salt')) 109 ApplicationUtils::getVersionHash(SHAARLI_VERSION, $this->conf->get('credentials.salt'))
89 ); 110 );
90 $this->tpl->assign('scripturl', index_url($_SERVER)); 111 $this->tpl->assign('index_url', index_url($_SERVER));
91 $visibility = ! empty($_SESSION['visibility']) ? $_SESSION['visibility'] : ''; 112 $visibility = !empty($_SESSION['visibility']) ? $_SESSION['visibility'] : '';
92 $this->tpl->assign('visibility', $visibility); 113 $this->tpl->assign('visibility', $visibility);
93 $this->tpl->assign('untaggedonly', !empty($_SESSION['untaggedonly'])); 114 $this->tpl->assign('untaggedonly', !empty($_SESSION['untaggedonly']));
94 $this->tpl->assign('pagetitle', $this->conf->get('general.title', 'Shaarli')); 115 $this->tpl->assign('pagetitle', $this->conf->get('general.title', 'Shaarli'));
@@ -105,6 +126,19 @@ class PageBuilder
105 if ($this->linkDB !== null) { 126 if ($this->linkDB !== null) {
106 $this->tpl->assign('tags', $this->linkDB->linksCountPerTag()); 127 $this->tpl->assign('tags', $this->linkDB->linksCountPerTag());
107 } 128 }
129
130 $this->tpl->assign(
131 'thumbnails_enabled',
132 $this->conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) !== Thumbnailer::MODE_NONE
133 );
134 $this->tpl->assign('thumbnails_width', $this->conf->get('thumbnails.width'));
135 $this->tpl->assign('thumbnails_height', $this->conf->get('thumbnails.height'));
136
137 if (!empty($_SESSION['warnings'])) {
138 $this->tpl->assign('global_warnings', $_SESSION['warnings']);
139 unset($_SESSION['warnings']);
140 }
141
108 // To be removed with a proper theme configuration. 142 // To be removed with a proper theme configuration.
109 $this->tpl->assign('conf', $this->conf); 143 $this->tpl->assign('conf', $this->conf);
110 } 144 }
@@ -136,7 +170,7 @@ class PageBuilder
136 $this->initialize(); 170 $this->initialize();
137 } 171 }
138 172
139 if (empty($data) || !is_array($data)){ 173 if (empty($data) || !is_array($data)) {
140 return false; 174 return false;
141 } 175 }
142 176
@@ -163,16 +197,16 @@ class PageBuilder
163 197
164 /** 198 /**
165 * Render a 404 page (uses the template : tpl/404.tpl) 199 * Render a 404 page (uses the template : tpl/404.tpl)
166 * usage : $PAGE->render404('The link was deleted') 200 * usage: $PAGE->render404('The link was deleted')
167 * 201 *
168 * @param string $message A messate to display what is not found 202 * @param string $message A message to display what is not found
169 */ 203 */
170 public function render404($message = '') 204 public function render404($message = '')
171 { 205 {
172 if (empty($message)) { 206 if (empty($message)) {
173 $message = t('The page you are trying to reach does not exist or has been deleted.'); 207 $message = t('The page you are trying to reach does not exist or has been deleted.');
174 } 208 }
175 header($_SERVER['SERVER_PROTOCOL'] .' '. t('404 Not Found')); 209 header($_SERVER['SERVER_PROTOCOL'] . ' ' . t('404 Not Found'));
176 $this->tpl->assign('error_message', $message); 210 $this->tpl->assign('error_message', $message);
177 $this->renderPage('404'); 211 $this->renderPage('404');
178 } 212 }
diff --git a/application/ThemeUtils.php b/application/render/ThemeUtils.php
index 16f2f6a2..86096c64 100644
--- a/application/ThemeUtils.php
+++ b/application/render/ThemeUtils.php
@@ -1,6 +1,6 @@
1<?php 1<?php
2 2
3namespace Shaarli; 3namespace Shaarli\Render;
4 4
5/** 5/**
6 * Class ThemeUtils 6 * Class ThemeUtils
diff --git a/application/security/LoginManager.php b/application/security/LoginManager.php
index 5a58926d..1ff3d0be 100644
--- a/application/security/LoginManager.php
+++ b/application/security/LoginManager.php
@@ -98,7 +98,6 @@ class LoginManager
98 // The user client has a valid stay-signed-in cookie 98 // The user client has a valid stay-signed-in cookie
99 // Session information is updated with the current client information 99 // Session information is updated with the current client information
100 $this->sessionManager->storeLoginInfo($clientIpId); 100 $this->sessionManager->storeLoginInfo($clientIpId);
101
102 } elseif ($this->sessionManager->hasSessionExpired() 101 } elseif ($this->sessionManager->hasSessionExpired()
103 || $this->sessionManager->hasClientIpChanged($clientIpId) 102 || $this->sessionManager->hasClientIpChanged($clientIpId)
104 ) { 103 ) {
diff --git a/application/Updater.php b/application/updater/Updater.php
index dece2c02..f12e3516 100644
--- a/application/Updater.php
+++ b/application/updater/Updater.php
@@ -1,10 +1,24 @@
1<?php 1<?php
2
3namespace Shaarli\Updater;
4
5use Exception;
6use RainTPL;
7use ReflectionClass;
8use ReflectionException;
9use ReflectionMethod;
10use Shaarli\ApplicationUtils;
11use Shaarli\Bookmark\LinkDB;
12use Shaarli\Bookmark\LinkFilter;
2use Shaarli\Config\ConfigJson; 13use Shaarli\Config\ConfigJson;
3use Shaarli\Config\ConfigPhp;
4use Shaarli\Config\ConfigManager; 14use Shaarli\Config\ConfigManager;
15use Shaarli\Config\ConfigPhp;
16use Shaarli\Exceptions\IOException;
17use Shaarli\Thumbnailer;
18use Shaarli\Updater\Exception\UpdaterException;
5 19
6/** 20/**
7 * Class Updater. 21 * Class updater.
8 * Used to update stuff when a new Shaarli's version is reached. 22 * Used to update stuff when a new Shaarli's version is reached.
9 * Update methods are ran only once, and the stored in a JSON file. 23 * Update methods are ran only once, and the stored in a JSON file.
10 */ 24 */
@@ -31,6 +45,11 @@ class Updater
31 protected $isLoggedIn; 45 protected $isLoggedIn;
32 46
33 /** 47 /**
48 * @var array $_SESSION
49 */
50 protected $session;
51
52 /**
34 * @var ReflectionMethod[] List of current class methods. 53 * @var ReflectionMethod[] List of current class methods.
35 */ 54 */
36 protected $methods; 55 protected $methods;
@@ -42,13 +61,17 @@ class Updater
42 * @param LinkDB $linkDB LinkDB instance. 61 * @param LinkDB $linkDB LinkDB instance.
43 * @param ConfigManager $conf Configuration Manager instance. 62 * @param ConfigManager $conf Configuration Manager instance.
44 * @param boolean $isLoggedIn True if the user is logged in. 63 * @param boolean $isLoggedIn True if the user is logged in.
64 * @param array $session $_SESSION (by reference)
65 *
66 * @throws ReflectionException
45 */ 67 */
46 public function __construct($doneUpdates, $linkDB, $conf, $isLoggedIn) 68 public function __construct($doneUpdates, $linkDB, $conf, $isLoggedIn, &$session = [])
47 { 69 {
48 $this->doneUpdates = $doneUpdates; 70 $this->doneUpdates = $doneUpdates;
49 $this->linkDB = $linkDB; 71 $this->linkDB = $linkDB;
50 $this->conf = $conf; 72 $this->conf = $conf;
51 $this->isLoggedIn = $isLoggedIn; 73 $this->isLoggedIn = $isLoggedIn;
74 $this->session = &$session;
52 75
53 // Retrieve all update methods. 76 // Retrieve all update methods.
54 $class = new ReflectionClass($this); 77 $class = new ReflectionClass($this);
@@ -73,12 +96,12 @@ class Updater
73 } 96 }
74 97
75 if ($this->methods === null) { 98 if ($this->methods === null) {
76 throw new UpdaterException(t('Couldn\'t retrieve Updater class methods.')); 99 throw new UpdaterException(t('Couldn\'t retrieve updater class methods.'));
77 } 100 }
78 101
79 foreach ($this->methods as $method) { 102 foreach ($this->methods as $method) {
80 // Not an update method or already done, pass. 103 // Not an update method or already done, pass.
81 if (! startsWith($method->getName(), 'updateMethod') 104 if (!startsWith($method->getName(), 'updateMethod')
82 || in_array($method->getName(), $this->doneUpdates) 105 || in_array($method->getName(), $this->doneUpdates)
83 ) { 106 ) {
84 continue; 107 continue;
@@ -129,7 +152,7 @@ class Updater
129 } 152 }
130 } 153 }
131 $this->conf->write($this->isLoggedIn); 154 $this->conf->write($this->isLoggedIn);
132 unlink($this->conf->get('resource.data_dir').'/options.php'); 155 unlink($this->conf->get('resource.data_dir') . '/options.php');
133 } 156 }
134 157
135 return true; 158 return true;
@@ -164,16 +187,16 @@ class Updater
164 $subConfig = array('config', 'plugins'); 187 $subConfig = array('config', 'plugins');
165 foreach ($subConfig as $sub) { 188 foreach ($subConfig as $sub) {
166 foreach ($oldConfig[$sub] as $key => $value) { 189 foreach ($oldConfig[$sub] as $key => $value) {
167 if (isset($legacyMap[$sub .'.'. $key])) { 190 if (isset($legacyMap[$sub . '.' . $key])) {
168 $configKey = $legacyMap[$sub .'.'. $key]; 191 $configKey = $legacyMap[$sub . '.' . $key];
169 } else { 192 } else {
170 $configKey = $sub .'.'. $key; 193 $configKey = $sub . '.' . $key;
171 } 194 }
172 $this->conf->set($configKey, $value); 195 $this->conf->set($configKey, $value);
173 } 196 }
174 } 197 }
175 198
176 try{ 199 try {
177 $this->conf->write($this->isLoggedIn); 200 $this->conf->write($this->isLoggedIn);
178 return true; 201 return true;
179 } catch (IOException $e) { 202 } catch (IOException $e) {
@@ -223,7 +246,7 @@ class Updater
223 return true; 246 return true;
224 } 247 }
225 248
226 $save = $this->conf->get('resource.data_dir') .'/datastore.'. date('YmdHis') .'.php'; 249 $save = $this->conf->get('resource.data_dir') . '/datastore.' . date('YmdHis') . '.php';
227 copy($this->conf->get('resource.datastore'), $save); 250 copy($this->conf->get('resource.datastore'), $save);
228 251
229 $links = array(); 252 $links = array();
@@ -297,7 +320,7 @@ class Updater
297 // We run the update only if this folder still contains the template files. 320 // We run the update only if this folder still contains the template files.
298 $tplDir = $this->conf->get('resource.raintpl_tpl'); 321 $tplDir = $this->conf->get('resource.raintpl_tpl');
299 $tplFile = $tplDir . '/linklist.html'; 322 $tplFile = $tplDir . '/linklist.html';
300 if (! file_exists($tplFile)) { 323 if (!file_exists($tplFile)) {
301 return true; 324 return true;
302 } 325 }
303 326
@@ -321,7 +344,7 @@ class Updater
321 */ 344 */
322 public function updateMethodMoveUserCss() 345 public function updateMethodMoveUserCss()
323 { 346 {
324 if (! is_file('inc/user.css')) { 347 if (!is_file('inc/user.css')) {
325 return true; 348 return true;
326 } 349 }
327 350
@@ -357,11 +380,11 @@ class Updater
357 */ 380 */
358 public function updateMethodPiwikUrl() 381 public function updateMethodPiwikUrl()
359 { 382 {
360 if (! $this->conf->exists('plugins.PIWIK_URL') || startsWith($this->conf->get('plugins.PIWIK_URL'), 'http')) { 383 if (!$this->conf->exists('plugins.PIWIK_URL') || startsWith($this->conf->get('plugins.PIWIK_URL'), 'http')) {
361 return true; 384 return true;
362 } 385 }
363 386
364 $this->conf->set('plugins.PIWIK_URL', 'http://'. $this->conf->get('plugins.PIWIK_URL')); 387 $this->conf->set('plugins.PIWIK_URL', 'http://' . $this->conf->get('plugins.PIWIK_URL'));
365 $this->conf->write($this->isLoggedIn); 388 $this->conf->write($this->isLoggedIn);
366 389
367 return true; 390 return true;
@@ -471,109 +494,60 @@ class Updater
471 return true; 494 return true;
472 } 495 }
473 496
474 if (! $this->conf->exists('general.download_max_size')) { 497 if (!$this->conf->exists('general.download_max_size')) {
475 $this->conf->set('general.download_max_size', 1024*1024*4); 498 $this->conf->set('general.download_max_size', 1024 * 1024 * 4);
476 } 499 }
477 500
478 if (! $this->conf->exists('general.download_timeout')) { 501 if (!$this->conf->exists('general.download_timeout')) {
479 $this->conf->set('general.download_timeout', 30); 502 $this->conf->set('general.download_timeout', 30);
480 } 503 }
481 504
482 $this->conf->write($this->isLoggedIn); 505 $this->conf->write($this->isLoggedIn);
483
484 return true; 506 return true;
485 } 507 }
486}
487
488/**
489 * Class UpdaterException.
490 */
491class UpdaterException extends Exception
492{
493 /**
494 * @var string Method where the error occurred.
495 */
496 protected $method;
497
498 /**
499 * @var Exception The parent exception.
500 */
501 protected $previous;
502
503 /**
504 * Constructor.
505 *
506 * @param string $message Force the error message if set.
507 * @param string $method Method where the error occurred.
508 * @param Exception|bool $previous Parent exception.
509 */
510 public function __construct($message = '', $method = '', $previous = false)
511 {
512 $this->method = $method;
513 $this->previous = $previous;
514 $this->message = $this->buildMessage($message);
515 }
516 508
517 /** 509 /**
518 * Build the exception error message. 510 * * Move thumbnails management to WebThumbnailer, coming with new settings.
519 *
520 * @param string $message Optional given error message.
521 *
522 * @return string The built error message.
523 */ 511 */
524 private function buildMessage($message) 512 public function updateMethodWebThumbnailer()
525 { 513 {
526 $out = ''; 514 if ($this->conf->exists('thumbnails.mode')) {
527 if (! empty($message)) { 515 return true;
528 $out .= $message . PHP_EOL;
529 } 516 }
530 517
531 if (! empty($this->method)) { 518 $thumbnailsEnabled = extension_loaded('gd') && $this->conf->get('thumbnail.enable_thumbnails', true);
532 $out .= t('An error occurred while running the update ') . $this->method . PHP_EOL; 519 $this->conf->set('thumbnails.mode', $thumbnailsEnabled ? Thumbnailer::MODE_ALL : Thumbnailer::MODE_NONE);
533 } 520 $this->conf->set('thumbnails.width', 125);
521 $this->conf->set('thumbnails.height', 90);
522 $this->conf->remove('thumbnail');
523 $this->conf->write(true);
534 524
535 if (! empty($this->previous)) { 525 if ($thumbnailsEnabled) {
536 $out .= ' '. $this->previous->getMessage(); 526 $this->session['warnings'][] = t(
527 'You have enabled or changed thumbnails mode. <a href="?do=thumbs_update">Please synchronize them</a>.'
528 );
537 } 529 }
538 530
539 return $out; 531 return true;
540 } 532 }
541}
542 533
543/** 534 /**
544 * Read the updates file, and return already done updates. 535 * Set sticky = false on all links
545 * 536 *
546 * @param string $updatesFilepath Updates file path. 537 * @return bool true if the update is successful, false otherwise.
547 * 538 */
548 * @return array Already done update methods. 539 public function updateMethodSetSticky()
549 */ 540 {
550function read_updates_file($updatesFilepath) 541 foreach ($this->linkDB as $key => $link) {
551{ 542 if (isset($link['sticky'])) {
552 if (! empty($updatesFilepath) && is_file($updatesFilepath)) { 543 return true;
553 $content = file_get_contents($updatesFilepath); 544 }
554 if (! empty($content)) { 545 $link['sticky'] = false;
555 return explode(';', $content); 546 $this->linkDB[$key] = $link;
556 } 547 }
557 }
558 return array();
559}
560 548
561/** 549 $this->linkDB->save($this->conf->get('resource.page_cache'));
562 * Write updates file.
563 *
564 * @param string $updatesFilepath Updates file path.
565 * @param array $updates Updates array to write.
566 *
567 * @throws Exception Couldn't write version number.
568 */
569function write_updates_file($updatesFilepath, $updates)
570{
571 if (empty($updatesFilepath)) {
572 throw new Exception(t('Updates file path is not set, can\'t write updates.'));
573 }
574 550
575 $res = file_put_contents($updatesFilepath, implode(';', $updates)); 551 return true;
576 if ($res === false) {
577 throw new Exception(t('Unable to write updates in '. $updatesFilepath . '.'));
578 } 552 }
579} 553}
diff --git a/application/updater/UpdaterUtils.php b/application/updater/UpdaterUtils.php
new file mode 100644
index 00000000..34d4f422
--- /dev/null
+++ b/application/updater/UpdaterUtils.php
@@ -0,0 +1,39 @@
1<?php
2
3/**
4 * Read the updates file, and return already done updates.
5 *
6 * @param string $updatesFilepath Updates file path.
7 *
8 * @return array Already done update methods.
9 */
10function read_updates_file($updatesFilepath)
11{
12 if (! empty($updatesFilepath) && is_file($updatesFilepath)) {
13 $content = file_get_contents($updatesFilepath);
14 if (! empty($content)) {
15 return explode(';', $content);
16 }
17 }
18 return array();
19}
20
21/**
22 * Write updates file.
23 *
24 * @param string $updatesFilepath Updates file path.
25 * @param array $updates Updates array to write.
26 *
27 * @throws Exception Couldn't write version number.
28 */
29function write_updates_file($updatesFilepath, $updates)
30{
31 if (empty($updatesFilepath)) {
32 throw new Exception(t('Updates file path is not set, can\'t write updates.'));
33 }
34
35 $res = file_put_contents($updatesFilepath, implode(';', $updates));
36 if ($res === false) {
37 throw new Exception(t('Unable to write updates in '. $updatesFilepath . '.'));
38 }
39}
diff --git a/application/updater/exception/UpdaterException.php b/application/updater/exception/UpdaterException.php
new file mode 100644
index 00000000..20aceccf
--- /dev/null
+++ b/application/updater/exception/UpdaterException.php
@@ -0,0 +1,60 @@
1<?php
2
3namespace Shaarli\Updater\Exception;
4
5use Exception;
6
7/**
8 * Class UpdaterException.
9 */
10class UpdaterException extends Exception
11{
12 /**
13 * @var string Method where the error occurred.
14 */
15 protected $method;
16
17 /**
18 * @var Exception The parent exception.
19 */
20 protected $previous;
21
22 /**
23 * Constructor.
24 *
25 * @param string $message Force the error message if set.
26 * @param string $method Method where the error occurred.
27 * @param Exception|bool $previous Parent exception.
28 */
29 public function __construct($message = '', $method = '', $previous = false)
30 {
31 $this->method = $method;
32 $this->previous = $previous;
33 $this->message = $this->buildMessage($message);
34 }
35
36 /**
37 * Build the exception error message.
38 *
39 * @param string $message Optional given error message.
40 *
41 * @return string The built error message.
42 */
43 private function buildMessage($message)
44 {
45 $out = '';
46 if (!empty($message)) {
47 $out .= $message . PHP_EOL;
48 }
49
50 if (!empty($this->method)) {
51 $out .= t('An error occurred while running the update ') . $this->method . PHP_EOL;
52 }
53
54 if (!empty($this->previous)) {
55 $out .= ' ' . $this->previous->getMessage();
56 }
57
58 return $out;
59 }
60}