diff options
-rw-r--r-- | application/FeedBuilder.php | 279 | ||||
-rw-r--r-- | application/LinkDB.php | 64 | ||||
-rw-r--r-- | application/LinkFilter.php | 14 | ||||
-rw-r--r-- | application/Router.php | 14 | ||||
-rw-r--r-- | application/Updater.php | 2 | ||||
-rw-r--r-- | application/Utils.php | 16 | ||||
-rw-r--r-- | index.php | 394 | ||||
-rw-r--r-- | plugins/demo_plugin/demo_plugin.php | 27 | ||||
-rw-r--r-- | plugins/markdown/README.md | 34 | ||||
-rw-r--r-- | plugins/markdown/markdown.php | 30 | ||||
-rw-r--r-- | tests/FeedBuilderTest.php | 212 | ||||
-rw-r--r-- | tests/LinkDBTest.php | 56 | ||||
-rw-r--r-- | tests/LinkFilterTest.php | 17 | ||||
-rw-r--r-- | tests/Updater/UpdaterTest.php | 4 | ||||
-rw-r--r-- | tests/plugins/PluginMarkdownTest.php | 37 | ||||
-rw-r--r-- | tests/utils/ReferenceLinkDB.php | 20 | ||||
-rw-r--r-- | tpl/configure.html | 7 | ||||
-rw-r--r-- | tpl/feed.atom.html | 40 | ||||
-rw-r--r-- | tpl/feed.rss.html | 34 |
19 files changed, 949 insertions, 352 deletions
diff --git a/application/FeedBuilder.php b/application/FeedBuilder.php new file mode 100644 index 00000000..ddefe6ce --- /dev/null +++ b/application/FeedBuilder.php | |||
@@ -0,0 +1,279 @@ | |||
1 | <?php | ||
2 | |||
3 | /** | ||
4 | * FeedBuilder class. | ||
5 | * | ||
6 | * Used to build ATOM and RSS feeds data. | ||
7 | */ | ||
8 | class FeedBuilder | ||
9 | { | ||
10 | /** | ||
11 | * @var string Constant: RSS feed type. | ||
12 | */ | ||
13 | public static $FEED_RSS = 'rss'; | ||
14 | |||
15 | /** | ||
16 | * @var string Constant: ATOM feed type. | ||
17 | */ | ||
18 | public static $FEED_ATOM = 'atom'; | ||
19 | |||
20 | /** | ||
21 | * @var string Default language if the locale isn't set. | ||
22 | */ | ||
23 | public static $DEFAULT_LANGUAGE = 'en-en'; | ||
24 | |||
25 | /** | ||
26 | * @var int Number of links to display in a feed by default. | ||
27 | */ | ||
28 | public static $DEFAULT_NB_LINKS = 50; | ||
29 | |||
30 | /** | ||
31 | * @var LinkDB instance. | ||
32 | */ | ||
33 | protected $linkDB; | ||
34 | |||
35 | /** | ||
36 | * @var string RSS or ATOM feed. | ||
37 | */ | ||
38 | protected $feedType; | ||
39 | |||
40 | /** | ||
41 | * @var array $_SERVER. | ||
42 | */ | ||
43 | protected $serverInfo; | ||
44 | |||
45 | /** | ||
46 | * @var array $_GET. | ||
47 | */ | ||
48 | protected $userInput; | ||
49 | |||
50 | /** | ||
51 | * @var boolean True if the user is currently logged in, false otherwise. | ||
52 | */ | ||
53 | protected $isLoggedIn; | ||
54 | |||
55 | /** | ||
56 | * @var boolean Use permalinks instead of direct links if true. | ||
57 | */ | ||
58 | protected $usePermalinks; | ||
59 | |||
60 | /** | ||
61 | * @var boolean true to hide dates in feeds. | ||
62 | */ | ||
63 | protected $hideDates; | ||
64 | |||
65 | /** | ||
66 | * @var string PubSub hub URL. | ||
67 | */ | ||
68 | protected $pubsubhubUrl; | ||
69 | |||
70 | /** | ||
71 | * @var string server locale. | ||
72 | */ | ||
73 | protected $locale; | ||
74 | |||
75 | /** | ||
76 | * @var DateTime Latest item date. | ||
77 | */ | ||
78 | protected $latestDate; | ||
79 | |||
80 | /** | ||
81 | * Feed constructor. | ||
82 | * | ||
83 | * @param LinkDB $linkDB LinkDB instance. | ||
84 | * @param string $feedType Type of feed. | ||
85 | * @param array $serverInfo $_SERVER. | ||
86 | * @param array $userInput $_GET. | ||
87 | * @param boolean $isLoggedIn True if the user is currently logged in, false otherwise. | ||
88 | */ | ||
89 | public function __construct($linkDB, $feedType, $serverInfo, $userInput, $isLoggedIn) | ||
90 | { | ||
91 | $this->linkDB = $linkDB; | ||
92 | $this->feedType = $feedType; | ||
93 | $this->serverInfo = $serverInfo; | ||
94 | $this->userInput = $userInput; | ||
95 | $this->isLoggedIn = $isLoggedIn; | ||
96 | } | ||
97 | |||
98 | /** | ||
99 | * Build data for feed templates. | ||
100 | * | ||
101 | * @return array Formatted data for feeds templates. | ||
102 | */ | ||
103 | public function buildData() | ||
104 | { | ||
105 | // Optionally filter the results: | ||
106 | $linksToDisplay = $this->linkDB->filterSearch($this->userInput); | ||
107 | |||
108 | $nblinksToDisplay = $this->getNbLinks(count($linksToDisplay)); | ||
109 | |||
110 | // Can't use array_keys() because $link is a LinkDB instance and not a real array. | ||
111 | $keys = array(); | ||
112 | foreach ($linksToDisplay as $key => $value) { | ||
113 | $keys[] = $key; | ||
114 | } | ||
115 | |||
116 | $pageaddr = escape(index_url($this->serverInfo)); | ||
117 | $linkDisplayed = array(); | ||
118 | for ($i = 0; $i < $nblinksToDisplay && $i < count($keys); $i++) { | ||
119 | $linkDisplayed[$keys[$i]] = $this->buildItem($linksToDisplay[$keys[$i]], $pageaddr); | ||
120 | } | ||
121 | |||
122 | $data['language'] = $this->getTypeLanguage(); | ||
123 | $data['pubsubhub_url'] = $this->pubsubhubUrl; | ||
124 | $data['last_update'] = $this->getLatestDateFormatted(); | ||
125 | $data['show_dates'] = !$this->hideDates || $this->isLoggedIn; | ||
126 | // Remove leading slash from REQUEST_URI. | ||
127 | $data['self_link'] = $pageaddr . escape(ltrim($this->serverInfo['REQUEST_URI'], '/')); | ||
128 | $data['index_url'] = $pageaddr; | ||
129 | $data['usepermalinks'] = $this->usePermalinks === true; | ||
130 | $data['links'] = $linkDisplayed; | ||
131 | |||
132 | return $data; | ||
133 | } | ||
134 | |||
135 | /** | ||
136 | * Build a feed item (one per shaare). | ||
137 | * | ||
138 | * @param array $link Single link array extracted from LinkDB. | ||
139 | * @param string $pageaddr Index URL. | ||
140 | * | ||
141 | * @return array Link array with feed attributes. | ||
142 | */ | ||
143 | protected function buildItem($link, $pageaddr) | ||
144 | { | ||
145 | $link['guid'] = $pageaddr .'?'. smallHash($link['linkdate']); | ||
146 | // Check for both signs of a note: starting with ? and 7 chars long. | ||
147 | if ($link['url'][0] === '?' && strlen($link['url']) === 7) { | ||
148 | $link['url'] = $pageaddr . $link['url']; | ||
149 | } | ||
150 | if ($this->usePermalinks === true) { | ||
151 | $permalink = '<a href="'. $link['url'] .'" title="Direct link">Direct link</a>'; | ||
152 | } else { | ||
153 | $permalink = '<a href="'. $link['guid'] .'" title="Permalink">Permalink</a>'; | ||
154 | } | ||
155 | $link['description'] = format_description($link['description']) . PHP_EOL .'<br>— '. $permalink; | ||
156 | |||
157 | $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); | ||
158 | |||
159 | if ($this->feedType == self::$FEED_RSS) { | ||
160 | $link['iso_date'] = $date->format(DateTime::RSS); | ||
161 | } else { | ||
162 | $link['iso_date'] = $date->format(DateTime::ATOM); | ||
163 | } | ||
164 | |||
165 | // Save the more recent item. | ||
166 | if (empty($this->latestDate) || $this->latestDate < $date) { | ||
167 | $this->latestDate = $date; | ||
168 | } | ||
169 | |||
170 | $taglist = array_filter(explode(' ', $link['tags']), 'strlen'); | ||
171 | uasort($taglist, 'strcasecmp'); | ||
172 | $link['taglist'] = $taglist; | ||
173 | |||
174 | return $link; | ||
175 | } | ||
176 | |||
177 | /** | ||
178 | * Assign PubSub hub URL. | ||
179 | * | ||
180 | * @param string $pubsubhubUrl PubSub hub url. | ||
181 | */ | ||
182 | public function setPubsubhubUrl($pubsubhubUrl) | ||
183 | { | ||
184 | $this->pubsubhubUrl = $pubsubhubUrl; | ||
185 | } | ||
186 | |||
187 | /** | ||
188 | * Set this to true to use permalinks instead of direct links. | ||
189 | * | ||
190 | * @param boolean $usePermalinks true to force permalinks. | ||
191 | */ | ||
192 | public function setUsePermalinks($usePermalinks) | ||
193 | { | ||
194 | $this->usePermalinks = $usePermalinks; | ||
195 | } | ||
196 | |||
197 | /** | ||
198 | * Set this to true to hide timestamps in feeds. | ||
199 | * | ||
200 | * @param boolean $hideDates true to enable. | ||
201 | */ | ||
202 | public function setHideDates($hideDates) | ||
203 | { | ||
204 | $this->hideDates = $hideDates; | ||
205 | } | ||
206 | |||
207 | /** | ||
208 | * Set the locale. Used to show feed language. | ||
209 | * | ||
210 | * @param string $locale The locale (eg. 'fr_FR.UTF8'). | ||
211 | */ | ||
212 | public function setLocale($locale) | ||
213 | { | ||
214 | $this->locale = strtolower($locale); | ||
215 | } | ||
216 | |||
217 | /** | ||
218 | * Get the language according to the feed type, based on the locale: | ||
219 | * | ||
220 | * - RSS format: en-us (default: 'en-en'). | ||
221 | * - ATOM format: fr (default: 'en'). | ||
222 | * | ||
223 | * @return string The language. | ||
224 | */ | ||
225 | public function getTypeLanguage() | ||
226 | { | ||
227 | // Use the locale do define the language, if available. | ||
228 | if (! empty($this->locale) && preg_match('/^\w{2}[_\-]\w{2}/', $this->locale)) { | ||
229 | $length = ($this->feedType == self::$FEED_RSS) ? 5 : 2; | ||
230 | return str_replace('_', '-', substr($this->locale, 0, $length)); | ||
231 | } | ||
232 | return ($this->feedType == self::$FEED_RSS) ? 'en-en' : 'en'; | ||
233 | } | ||
234 | |||
235 | /** | ||
236 | * Format the latest item date found according to the feed type. | ||
237 | * | ||
238 | * Return an empty string if invalid DateTime is passed. | ||
239 | * | ||
240 | * @return string Formatted date. | ||
241 | */ | ||
242 | protected function getLatestDateFormatted() | ||
243 | { | ||
244 | if (empty($this->latestDate) || !$this->latestDate instanceof DateTime) { | ||
245 | return ''; | ||
246 | } | ||
247 | |||
248 | $type = ($this->feedType == self::$FEED_RSS) ? DateTime::RSS : DateTime::ATOM; | ||
249 | return $this->latestDate->format($type); | ||
250 | } | ||
251 | |||
252 | /** | ||
253 | * Returns the number of link to display according to 'nb' user input parameter. | ||
254 | * | ||
255 | * If 'nb' not set or invalid, default value: $DEFAULT_NB_LINKS. | ||
256 | * If 'nb' is set to 'all', display all filtered links (max parameter). | ||
257 | * | ||
258 | * @param int $max maximum number of links to display. | ||
259 | * | ||
260 | * @return int number of links to display. | ||
261 | */ | ||
262 | public function getNbLinks($max) | ||
263 | { | ||
264 | if (empty($this->userInput['nb'])) { | ||
265 | return self::$DEFAULT_NB_LINKS; | ||
266 | } | ||
267 | |||
268 | if ($this->userInput['nb'] == 'all') { | ||
269 | return $max; | ||
270 | } | ||
271 | |||
272 | $intNb = intval($this->userInput['nb']); | ||
273 | if (! is_int($intNb) || $intNb == 0) { | ||
274 | return self::$DEFAULT_NB_LINKS; | ||
275 | } | ||
276 | |||
277 | return $intNb; | ||
278 | } | ||
279 | } | ||
diff --git a/application/LinkDB.php b/application/LinkDB.php index 1b505620..a62341fc 100644 --- a/application/LinkDB.php +++ b/application/LinkDB.php | |||
@@ -341,17 +341,71 @@ You use the community supported version of the original Shaarli project, by Seba | |||
341 | } | 341 | } |
342 | 342 | ||
343 | /** | 343 | /** |
344 | * Filter links. | 344 | * Returns the shaare corresponding to a smallHash. |
345 | * | 345 | * |
346 | * @param string $type Type of filter. | 346 | * @param string $request QUERY_STRING server parameter. |
347 | * @param mixed $request Search request, string or array. | 347 | * |
348 | * @return array $filtered array containing permalink data. | ||
349 | * | ||
350 | * @throws LinkNotFoundException if the smallhash is malformed or doesn't match any link. | ||
351 | */ | ||
352 | public function filterHash($request) | ||
353 | { | ||
354 | $request = substr($request, 0, 6); | ||
355 | $linkFilter = new LinkFilter($this->_links); | ||
356 | return $linkFilter->filter(LinkFilter::$FILTER_HASH, $request); | ||
357 | } | ||
358 | |||
359 | /** | ||
360 | * Returns the list of articles for a given day. | ||
361 | * | ||
362 | * @param string $request day to filter. Format: YYYYMMDD. | ||
363 | * | ||
364 | * @return array list of shaare found. | ||
365 | */ | ||
366 | public function filterDay($request) { | ||
367 | $linkFilter = new LinkFilter($this->_links); | ||
368 | return $linkFilter->filter(LinkFilter::$FILTER_DAY, $request); | ||
369 | } | ||
370 | |||
371 | /** | ||
372 | * Filter links according to search parameters. | ||
373 | * | ||
374 | * @param array $filterRequest Search request content. Supported keys: | ||
375 | * - searchtags: list of tags | ||
376 | * - searchterm: term search | ||
348 | * @param bool $casesensitive Optional: Perform case sensitive filter | 377 | * @param bool $casesensitive Optional: Perform case sensitive filter |
349 | * @param bool $privateonly Optional: Returns private links only if true. | 378 | * @param bool $privateonly Optional: Returns private links only if true. |
350 | * | 379 | * |
351 | * @return array filtered links | 380 | * @return array filtered links, all links if no suitable filter was provided. |
352 | */ | 381 | */ |
353 | public function filter($type = '', $request = '', $casesensitive = false, $privateonly = false) | 382 | public function filterSearch($filterRequest = array(), $casesensitive = false, $privateonly = false) |
354 | { | 383 | { |
384 | // Filter link database according to parameters. | ||
385 | $searchtags = !empty($filterRequest['searchtags']) ? escape($filterRequest['searchtags']) : ''; | ||
386 | $searchterm = !empty($filterRequest['searchterm']) ? escape($filterRequest['searchterm']) : ''; | ||
387 | |||
388 | // Search tags + fullsearch. | ||
389 | if (empty($type) && ! empty($searchtags) && ! empty($searchterm)) { | ||
390 | $type = LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT; | ||
391 | $request = array($searchtags, $searchterm); | ||
392 | } | ||
393 | // Search by tags. | ||
394 | elseif (! empty($searchtags)) { | ||
395 | $type = LinkFilter::$FILTER_TAG; | ||
396 | $request = $searchtags; | ||
397 | } | ||
398 | // Fulltext search. | ||
399 | elseif (! empty($searchterm)) { | ||
400 | $type = LinkFilter::$FILTER_TEXT; | ||
401 | $request = $searchterm; | ||
402 | } | ||
403 | // Otherwise, display without filtering. | ||
404 | else { | ||
405 | $type = ''; | ||
406 | $request = ''; | ||
407 | } | ||
408 | |||
355 | $linkFilter = new LinkFilter($this->_links); | 409 | $linkFilter = new LinkFilter($this->_links); |
356 | return $linkFilter->filter($type, $request, $casesensitive, $privateonly); | 410 | return $linkFilter->filter($type, $request, $casesensitive, $privateonly); |
357 | } | 411 | } |
diff --git a/application/LinkFilter.php b/application/LinkFilter.php index 3fd803cb..5e0d8015 100644 --- a/application/LinkFilter.php +++ b/application/LinkFilter.php | |||
@@ -44,7 +44,7 @@ class LinkFilter | |||
44 | * Filter links according to parameters. | 44 | * Filter links according to parameters. |
45 | * | 45 | * |
46 | * @param string $type Type of filter (eg. tags, permalink, etc.). | 46 | * @param string $type Type of filter (eg. tags, permalink, etc.). |
47 | * @param string $request Filter content. | 47 | * @param mixed $request Filter content. |
48 | * @param bool $casesensitive Optional: Perform case sensitive filter if true. | 48 | * @param bool $casesensitive Optional: Perform case sensitive filter if true. |
49 | * @param bool $privateonly Optional: Only returns private links if true. | 49 | * @param bool $privateonly Optional: Only returns private links if true. |
50 | * | 50 | * |
@@ -110,6 +110,8 @@ class LinkFilter | |||
110 | * @param string $smallHash permalink hash. | 110 | * @param string $smallHash permalink hash. |
111 | * | 111 | * |
112 | * @return array $filtered array containing permalink data. | 112 | * @return array $filtered array containing permalink data. |
113 | * | ||
114 | * @throws LinkNotFoundException if the smallhash doesn't match any link. | ||
113 | */ | 115 | */ |
114 | private function filterSmallHash($smallHash) | 116 | private function filterSmallHash($smallHash) |
115 | { | 117 | { |
@@ -121,6 +123,11 @@ class LinkFilter | |||
121 | return $filtered; | 123 | return $filtered; |
122 | } | 124 | } |
123 | } | 125 | } |
126 | |||
127 | if (empty($filtered)) { | ||
128 | throw new LinkNotFoundException(); | ||
129 | } | ||
130 | |||
124 | return $filtered; | 131 | return $filtered; |
125 | } | 132 | } |
126 | 133 | ||
@@ -318,3 +325,8 @@ class LinkFilter | |||
318 | return array_filter(explode(' ', trim($tagsOut)), 'strlen'); | 325 | return array_filter(explode(' ', trim($tagsOut)), 'strlen'); |
319 | } | 326 | } |
320 | } | 327 | } |
328 | |||
329 | class LinkNotFoundException extends Exception | ||
330 | { | ||
331 | protected $message = 'The link you are trying to reach does not exist or has been deleted.'; | ||
332 | } | ||
diff --git a/application/Router.php b/application/Router.php index 6185f08e..a1e594a0 100644 --- a/application/Router.php +++ b/application/Router.php | |||
@@ -15,6 +15,10 @@ class Router | |||
15 | 15 | ||
16 | public static $PAGE_DAILY = 'daily'; | 16 | public static $PAGE_DAILY = 'daily'; |
17 | 17 | ||
18 | public static $PAGE_FEED_ATOM = 'atom'; | ||
19 | |||
20 | public static $PAGE_FEED_RSS = 'rss'; | ||
21 | |||
18 | public static $PAGE_TOOLS = 'tools'; | 22 | public static $PAGE_TOOLS = 'tools'; |
19 | 23 | ||
20 | public static $PAGE_CHANGEPASSWORD = 'changepasswd'; | 24 | public static $PAGE_CHANGEPASSWORD = 'changepasswd'; |
@@ -49,7 +53,7 @@ class Router | |||
49 | * @param array $get $_SERVER['GET']. | 53 | * @param array $get $_SERVER['GET']. |
50 | * @param bool $loggedIn true if authenticated user. | 54 | * @param bool $loggedIn true if authenticated user. |
51 | * | 55 | * |
52 | * @return self::page found. | 56 | * @return string page found. |
53 | */ | 57 | */ |
54 | public static function findPage($query, $get, $loggedIn) | 58 | public static function findPage($query, $get, $loggedIn) |
55 | { | 59 | { |
@@ -79,6 +83,14 @@ class Router | |||
79 | return self::$PAGE_DAILY; | 83 | return self::$PAGE_DAILY; |
80 | } | 84 | } |
81 | 85 | ||
86 | if (startsWith($query, 'do='. self::$PAGE_FEED_ATOM)) { | ||
87 | return self::$PAGE_FEED_ATOM; | ||
88 | } | ||
89 | |||
90 | if (startsWith($query, 'do='. self::$PAGE_FEED_RSS)) { | ||
91 | return self::$PAGE_FEED_RSS; | ||
92 | } | ||
93 | |||
82 | // At this point, only loggedin pages. | 94 | // At this point, only loggedin pages. |
83 | if (!$loggedIn) { | 95 | if (!$loggedIn) { |
84 | return self::$PAGE_LINKLIST; | 96 | return self::$PAGE_LINKLIST; |
diff --git a/application/Updater.php b/application/Updater.php index 773a1ffa..58c13c07 100644 --- a/application/Updater.php +++ b/application/Updater.php | |||
@@ -137,7 +137,7 @@ class Updater | |||
137 | */ | 137 | */ |
138 | public function updateMethodRenameDashTags() | 138 | public function updateMethodRenameDashTags() |
139 | { | 139 | { |
140 | $linklist = $this->linkDB->filter(); | 140 | $linklist = $this->linkDB->filterSearch(); |
141 | foreach ($linklist as $link) { | 141 | foreach ($linklist as $link) { |
142 | $link['tags'] = preg_replace('/(^| )\-/', '$1', $link['tags']); | 142 | $link['tags'] = preg_replace('/(^| )\-/', '$1', $link['tags']); |
143 | $link['tags'] = implode(' ', array_unique(LinkFilter::tagsStrToArray($link['tags'], true))); | 143 | $link['tags'] = implode(' ', array_unique(LinkFilter::tagsStrToArray($link['tags'], true))); |
diff --git a/application/Utils.php b/application/Utils.php index 3d819716..5b8ca508 100644 --- a/application/Utils.php +++ b/application/Utils.php | |||
@@ -63,14 +63,22 @@ function endsWith($haystack, $needle, $case=true) | |||
63 | 63 | ||
64 | /** | 64 | /** |
65 | * Htmlspecialchars wrapper | 65 | * Htmlspecialchars wrapper |
66 | * Support multidimensional array of strings. | ||
66 | * | 67 | * |
67 | * @param string $str the string to escape. | 68 | * @param mixed $input Data to escape: a single string or an array of strings. |
68 | * | 69 | * |
69 | * @return string escaped. | 70 | * @return string escaped. |
70 | */ | 71 | */ |
71 | function escape($str) | 72 | function escape($input) |
72 | { | 73 | { |
73 | return htmlspecialchars($str, ENT_COMPAT, 'UTF-8', false); | 74 | if (is_array($input)) { |
75 | $out = array(); | ||
76 | foreach($input as $key => $value) { | ||
77 | $out[$key] = escape($value); | ||
78 | } | ||
79 | return $out; | ||
80 | } | ||
81 | return htmlspecialchars($input, ENT_COMPAT, 'UTF-8', false); | ||
74 | } | 82 | } |
75 | 83 | ||
76 | /** | 84 | /** |
@@ -226,7 +234,7 @@ function space2nbsp($text) | |||
226 | * | 234 | * |
227 | * @return string formatted description. | 235 | * @return string formatted description. |
228 | */ | 236 | */ |
229 | function format_description($description, $redirector) { | 237 | function format_description($description, $redirector = false) { |
230 | return nl2br(space2nbsp(text2clickable($description, $redirector))); | 238 | return nl2br(space2nbsp(text2clickable($description, $redirector))); |
231 | } | 239 | } |
232 | 240 | ||
@@ -154,6 +154,7 @@ if (is_file($GLOBALS['config']['CONFIG_FILE'])) { | |||
154 | require_once 'application/ApplicationUtils.php'; | 154 | require_once 'application/ApplicationUtils.php'; |
155 | require_once 'application/Cache.php'; | 155 | require_once 'application/Cache.php'; |
156 | require_once 'application/CachedPage.php'; | 156 | require_once 'application/CachedPage.php'; |
157 | require_once 'application/FeedBuilder.php'; | ||
157 | require_once 'application/FileUtils.php'; | 158 | require_once 'application/FileUtils.php'; |
158 | require_once 'application/HttpUtils.php'; | 159 | require_once 'application/HttpUtils.php'; |
159 | require_once 'application/LinkDB.php'; | 160 | require_once 'application/LinkDB.php'; |
@@ -483,7 +484,7 @@ if (isset($_POST['login'])) | |||
483 | if (isset($_POST['returnurl'])) { | 484 | if (isset($_POST['returnurl'])) { |
484 | // Prevent loops over login screen. | 485 | // Prevent loops over login screen. |
485 | if (strpos($_POST['returnurl'], 'do=login') === false) { | 486 | if (strpos($_POST['returnurl'], 'do=login') === false) { |
486 | header('Location: '. escape($_POST['returnurl'])); | 487 | header('Location: '. generateLocation($_POST['returnurl'], $_SERVER['HTTP_HOST'])); |
487 | exit; | 488 | exit; |
488 | } | 489 | } |
489 | } | 490 | } |
@@ -637,6 +638,29 @@ class pageBuilder | |||
637 | $this->tpl->assign($what,$where); | 638 | $this->tpl->assign($what,$where); |
638 | } | 639 | } |
639 | 640 | ||
641 | /** | ||
642 | * Assign an array of data to the template builder. | ||
643 | * | ||
644 | * @param array $data Data to assign. | ||
645 | * | ||
646 | * @return false if invalid data. | ||
647 | */ | ||
648 | public function assignAll($data) | ||
649 | { | ||
650 | // Lazy initialization | ||
651 | if ($this->tpl === false) { | ||
652 | $this->initialize(); | ||
653 | } | ||
654 | |||
655 | if (empty($data) || !is_array($data)){ | ||
656 | return false; | ||
657 | } | ||
658 | |||
659 | foreach ($data as $key => $value) { | ||
660 | $this->assign($key, $value); | ||
661 | } | ||
662 | } | ||
663 | |||
640 | // Render a specific page (using a template). | 664 | // Render a specific page (using a template). |
641 | // e.g. pb.renderPage('picwall') | 665 | // e.g. pb.renderPage('picwall') |
642 | public function renderPage($page) | 666 | public function renderPage($page) |
@@ -659,232 +683,6 @@ class pageBuilder | |||
659 | } | 683 | } |
660 | 684 | ||
661 | // ------------------------------------------------------------------------------------------ | 685 | // ------------------------------------------------------------------------------------------ |
662 | // Output the last N links in RSS 2.0 format. | ||
663 | function showRSS() | ||
664 | { | ||
665 | header('Content-Type: application/rss+xml; charset=utf-8'); | ||
666 | |||
667 | // $usepermalink : If true, use permalink instead of final link. | ||
668 | // User just has to add 'permalink' in URL parameters. e.g. http://mysite.com/shaarli/?do=rss&permalinks | ||
669 | // Also enabled through a config option | ||
670 | $usepermalinks = isset($_GET['permalinks']) || !$GLOBALS['config']['ENABLE_RSS_PERMALINKS']; | ||
671 | |||
672 | // Cache system | ||
673 | $query = $_SERVER["QUERY_STRING"]; | ||
674 | $cache = new CachedPage( | ||
675 | $GLOBALS['config']['PAGECACHE'], | ||
676 | page_url($_SERVER), | ||
677 | startsWith($query,'do=rss') && !isLoggedIn() | ||
678 | ); | ||
679 | $cached = $cache->cachedVersion(); | ||
680 | if (! empty($cached)) { | ||
681 | echo $cached; | ||
682 | exit; | ||
683 | } | ||
684 | |||
685 | // If cached was not found (or not usable), then read the database and build the response: | ||
686 | $LINKSDB = new LinkDB( | ||
687 | $GLOBALS['config']['DATASTORE'], | ||
688 | isLoggedIn(), | ||
689 | $GLOBALS['config']['HIDE_PUBLIC_LINKS'], | ||
690 | $GLOBALS['redirector'] | ||
691 | ); | ||
692 | // Read links from database (and filter private links if user it not logged in). | ||
693 | |||
694 | // Optionally filter the results: | ||
695 | $searchtags = !empty($_GET['searchtags']) ? escape($_GET['searchtags']) : ''; | ||
696 | $searchterm = !empty($_GET['searchterm']) ? escape($_GET['searchterm']) : ''; | ||
697 | if (! empty($searchtags) && ! empty($searchterm)) { | ||
698 | $linksToDisplay = $LINKSDB->filter( | ||
699 | LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT, | ||
700 | array($searchtags, $searchterm) | ||
701 | ); | ||
702 | } | ||
703 | elseif ($searchtags) { | ||
704 | $linksToDisplay = $LINKSDB->filter(LinkFilter::$FILTER_TAG, $searchtags); | ||
705 | } | ||
706 | elseif ($searchterm) { | ||
707 | $linksToDisplay = $LINKSDB->filter(LinkFilter::$FILTER_TEXT, $searchterm); | ||
708 | } | ||
709 | else { | ||
710 | $linksToDisplay = $LINKSDB; | ||
711 | } | ||
712 | |||
713 | $nblinksToDisplay = 50; // Number of links to display. | ||
714 | // In URL, you can specificy the number of links. Example: nb=200 or nb=all for all links. | ||
715 | if (!empty($_GET['nb'])) { | ||
716 | $nblinksToDisplay = $_GET['nb'] == 'all' ? count($linksToDisplay) : max(intval($_GET['nb']), 1); | ||
717 | } | ||
718 | |||
719 | $pageaddr = escape(index_url($_SERVER)); | ||
720 | echo '<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">'; | ||
721 | echo '<channel><title>'.$GLOBALS['title'].'</title><link>'.$pageaddr.'</link>'; | ||
722 | echo '<description>Shared links</description><language>en-en</language><copyright>'.$pageaddr.'</copyright>'."\n\n"; | ||
723 | if (!empty($GLOBALS['config']['PUBSUBHUB_URL'])) | ||
724 | { | ||
725 | echo '<!-- PubSubHubbub Discovery -->'; | ||
726 | echo '<link rel="hub" href="'.escape($GLOBALS['config']['PUBSUBHUB_URL']).'" xmlns="http://www.w3.org/2005/Atom" />'; | ||
727 | echo '<link rel="self" href="'.$pageaddr.'?do=rss" xmlns="http://www.w3.org/2005/Atom" />'; | ||
728 | echo '<!-- End Of PubSubHubbub Discovery -->'; | ||
729 | } | ||
730 | $i=0; | ||
731 | $keys=array(); foreach($linksToDisplay as $key=>$value) { $keys[]=$key; } // No, I can't use array_keys(). | ||
732 | while ($i<$nblinksToDisplay && $i<count($keys)) | ||
733 | { | ||
734 | $link = $linksToDisplay[$keys[$i]]; | ||
735 | $guid = $pageaddr.'?'.smallHash($link['linkdate']); | ||
736 | $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); | ||
737 | $absurl = $link['url']; | ||
738 | if (startsWith($absurl,'?')) $absurl=$pageaddr.$absurl; // make permalink URL absolute | ||
739 | if ($usepermalinks===true) | ||
740 | echo '<item><title>'.$link['title'].'</title><guid isPermaLink="true">'.$guid.'</guid><link>'.$guid.'</link>'; | ||
741 | else | ||
742 | echo '<item><title>'.$link['title'].'</title><guid isPermaLink="false">'.$guid.'</guid><link>'.$absurl.'</link>'; | ||
743 | if (!$GLOBALS['config']['HIDE_TIMESTAMPS'] || isLoggedIn()) { | ||
744 | echo '<pubDate>'.escape($date->format(DateTime::RSS))."</pubDate>\n"; | ||
745 | } | ||
746 | if ($link['tags']!='') // Adding tags to each RSS entry (as mentioned in RSS specification) | ||
747 | { | ||
748 | foreach(explode(' ',$link['tags']) as $tag) { echo '<category domain="'.$pageaddr.'">'.$tag.'</category>'."\n"; } | ||
749 | } | ||
750 | |||
751 | // Add permalink in description | ||
752 | $descriptionlink = '(<a href="'.$guid.'">Permalink</a>)'; | ||
753 | // If user wants permalinks first, put the final link in description | ||
754 | if ($usepermalinks===true) $descriptionlink = '(<a href="'.$absurl.'">Link</a>)'; | ||
755 | if (strlen($link['description'])>0) $descriptionlink = '<br>'.$descriptionlink; | ||
756 | echo '<description><![CDATA['. | ||
757 | format_description($link['description'], $GLOBALS['redirector']) . | ||
758 | $descriptionlink . ']]></description>' . "\n</item>\n"; | ||
759 | $i++; | ||
760 | } | ||
761 | echo '</channel></rss><!-- Cached version of '.escape(page_url($_SERVER)).' -->'; | ||
762 | |||
763 | $cache->cache(ob_get_contents()); | ||
764 | ob_end_flush(); | ||
765 | exit; | ||
766 | } | ||
767 | |||
768 | // ------------------------------------------------------------------------------------------ | ||
769 | // Output the last N links in ATOM format. | ||
770 | function showATOM() | ||
771 | { | ||
772 | header('Content-Type: application/atom+xml; charset=utf-8'); | ||
773 | |||
774 | // $usepermalink : If true, use permalink instead of final link. | ||
775 | // User just has to add 'permalink' in URL parameters. e.g. http://mysite.com/shaarli/?do=atom&permalinks | ||
776 | $usepermalinks = isset($_GET['permalinks']) || !$GLOBALS['config']['ENABLE_RSS_PERMALINKS']; | ||
777 | |||
778 | // Cache system | ||
779 | $query = $_SERVER["QUERY_STRING"]; | ||
780 | $cache = new CachedPage( | ||
781 | $GLOBALS['config']['PAGECACHE'], | ||
782 | page_url($_SERVER), | ||
783 | startsWith($query,'do=atom') && !isLoggedIn() | ||
784 | ); | ||
785 | $cached = $cache->cachedVersion(); | ||
786 | if (!empty($cached)) { | ||
787 | echo $cached; | ||
788 | exit; | ||
789 | } | ||
790 | |||
791 | // If cached was not found (or not usable), then read the database and build the response: | ||
792 | // Read links from database (and filter private links if used it not logged in). | ||
793 | $LINKSDB = new LinkDB( | ||
794 | $GLOBALS['config']['DATASTORE'], | ||
795 | isLoggedIn(), | ||
796 | $GLOBALS['config']['HIDE_PUBLIC_LINKS'], | ||
797 | $GLOBALS['redirector'] | ||
798 | ); | ||
799 | |||
800 | // Optionally filter the results: | ||
801 | $searchtags = !empty($_GET['searchtags']) ? escape($_GET['searchtags']) : ''; | ||
802 | $searchterm = !empty($_GET['searchterm']) ? escape($_GET['searchterm']) : ''; | ||
803 | if (! empty($searchtags) && ! empty($searchterm)) { | ||
804 | $linksToDisplay = $LINKSDB->filter( | ||
805 | LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT, | ||
806 | array($searchtags, $searchterm) | ||
807 | ); | ||
808 | } | ||
809 | elseif ($searchtags) { | ||
810 | $linksToDisplay = $LINKSDB->filter(LinkFilter::$FILTER_TAG, $searchtags); | ||
811 | } | ||
812 | elseif ($searchterm) { | ||
813 | $linksToDisplay = $LINKSDB->filter(LinkFilter::$FILTER_TEXT, $searchterm); | ||
814 | } | ||
815 | else { | ||
816 | $linksToDisplay = $LINKSDB; | ||
817 | } | ||
818 | |||
819 | $nblinksToDisplay = 50; // Number of links to display. | ||
820 | // In URL, you can specificy the number of links. Example: nb=200 or nb=all for all links. | ||
821 | if (!empty($_GET['nb'])) { | ||
822 | $nblinksToDisplay = $_GET['nb']=='all' ? count($linksToDisplay) : max(intval($_GET['nb']), 1); | ||
823 | } | ||
824 | |||
825 | $pageaddr=escape(index_url($_SERVER)); | ||
826 | $latestDate = ''; | ||
827 | $entries=''; | ||
828 | $i=0; | ||
829 | $keys=array(); foreach($linksToDisplay as $key=>$value) { $keys[]=$key; } // No, I can't use array_keys(). | ||
830 | while ($i<$nblinksToDisplay && $i<count($keys)) | ||
831 | { | ||
832 | $link = $linksToDisplay[$keys[$i]]; | ||
833 | $guid = $pageaddr.'?'.smallHash($link['linkdate']); | ||
834 | $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); | ||
835 | $iso8601date = $date->format(DateTime::ISO8601); | ||
836 | $latestDate = max($latestDate, $iso8601date); | ||
837 | $absurl = $link['url']; | ||
838 | if (startsWith($absurl,'?')) $absurl=$pageaddr.$absurl; // make permalink URL absolute | ||
839 | $entries.='<entry><title>'.$link['title'].'</title>'; | ||
840 | if ($usepermalinks===true) | ||
841 | $entries.='<link href="'.$guid.'" /><id>'.$guid.'</id>'; | ||
842 | else | ||
843 | $entries.='<link href="'.$absurl.'" /><id>'.$guid.'</id>'; | ||
844 | |||
845 | if (!$GLOBALS['config']['HIDE_TIMESTAMPS'] || isLoggedIn()) { | ||
846 | $entries.='<updated>'.escape($iso8601date).'</updated>'; | ||
847 | } | ||
848 | |||
849 | // Add permalink in description | ||
850 | $descriptionlink = '(<a href="'.$guid.'">Permalink</a>)'; | ||
851 | // If user wants permalinks first, put the final link in description | ||
852 | if ($usepermalinks===true) $descriptionlink = '(<a href="'.$absurl.'">Link</a>)'; | ||
853 | if (strlen($link['description'])>0) $descriptionlink = '<br>'.$descriptionlink; | ||
854 | |||
855 | $entries .= '<content type="html"><![CDATA['. | ||
856 | format_description($link['description'], $GLOBALS['redirector']) . | ||
857 | $descriptionlink . "]]></content>\n"; | ||
858 | if ($link['tags']!='') // Adding tags to each ATOM entry (as mentioned in ATOM specification) | ||
859 | { | ||
860 | foreach(explode(' ',$link['tags']) as $tag) | ||
861 | { $entries.='<category scheme="'.$pageaddr.'" term="'.$tag.'" />'."\n"; } | ||
862 | } | ||
863 | $entries.="</entry>\n"; | ||
864 | $i++; | ||
865 | } | ||
866 | $feed='<?xml version="1.0" encoding="UTF-8"?><feed xmlns="http://www.w3.org/2005/Atom">'; | ||
867 | $feed.='<title>'.$GLOBALS['title'].'</title>'; | ||
868 | if (!$GLOBALS['config']['HIDE_TIMESTAMPS'] || isLoggedIn()) $feed.='<updated>'.escape($latestDate).'</updated>'; | ||
869 | $feed.='<link rel="self" href="'.escape(server_url($_SERVER).$_SERVER["REQUEST_URI"]).'" />'; | ||
870 | if (!empty($GLOBALS['config']['PUBSUBHUB_URL'])) | ||
871 | { | ||
872 | $feed.='<!-- PubSubHubbub Discovery -->'; | ||
873 | $feed.='<link rel="hub" href="'.escape($GLOBALS['config']['PUBSUBHUB_URL']).'" />'; | ||
874 | $feed.='<!-- End Of PubSubHubbub Discovery -->'; | ||
875 | } | ||
876 | $feed.='<author><name>'.$pageaddr.'</name><uri>'.$pageaddr.'</uri></author>'; | ||
877 | $feed.='<id>'.$pageaddr.'</id>'."\n\n"; // Yes, I know I should use a real IRI (RFC3987), but the site URL will do. | ||
878 | $feed.=$entries; | ||
879 | $feed.='</feed><!-- Cached version of '.escape(page_url($_SERVER)).' -->'; | ||
880 | echo $feed; | ||
881 | |||
882 | $cache->cache(ob_get_contents()); | ||
883 | ob_end_flush(); | ||
884 | exit; | ||
885 | } | ||
886 | |||
887 | // ------------------------------------------------------------------------------------------ | ||
888 | // Daily RSS feed: 1 RSS entry per day giving all the links on that day. | 686 | // Daily RSS feed: 1 RSS entry per day giving all the links on that day. |
889 | // Gives the last 7 days (which have links). | 687 | // Gives the last 7 days (which have links). |
890 | // This RSS feed cannot be filtered. | 688 | // This RSS feed cannot be filtered. |
@@ -1018,7 +816,7 @@ function showDaily($pageBuilder) | |||
1018 | } | 816 | } |
1019 | 817 | ||
1020 | try { | 818 | try { |
1021 | $linksToDisplay = $LINKSDB->filter(LinkFilter::$FILTER_DAY, $day); | 819 | $linksToDisplay = $LINKSDB->filterDay($day); |
1022 | } catch (Exception $exc) { | 820 | } catch (Exception $exc) { |
1023 | error_log($exc); | 821 | error_log($exc); |
1024 | $linksToDisplay = array(); | 822 | $linksToDisplay = array(); |
@@ -1164,24 +962,7 @@ function renderPage() | |||
1164 | if ($targetPage == Router::$PAGE_PICWALL) | 962 | if ($targetPage == Router::$PAGE_PICWALL) |
1165 | { | 963 | { |
1166 | // Optionally filter the results: | 964 | // Optionally filter the results: |
1167 | $searchtags = !empty($_GET['searchtags']) ? escape($_GET['searchtags']) : ''; | 965 | $links = $LINKSDB->filterSearch($_GET); |
1168 | $searchterm = !empty($_GET['searchterm']) ? escape($_GET['searchterm']) : ''; | ||
1169 | if (! empty($searchtags) && ! empty($searchterm)) { | ||
1170 | $links = $LINKSDB->filter( | ||
1171 | LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT, | ||
1172 | array($searchtags, $searchterm) | ||
1173 | ); | ||
1174 | } | ||
1175 | elseif ($searchtags) { | ||
1176 | $links = $LINKSDB->filter(LinkFilter::$FILTER_TAG, $searchtags); | ||
1177 | } | ||
1178 | elseif ($searchterm) { | ||
1179 | $links = $LINKSDB->filter(LinkFilter::$FILTER_TEXT, $searchterm); | ||
1180 | } | ||
1181 | else { | ||
1182 | $links = $LINKSDB; | ||
1183 | } | ||
1184 | |||
1185 | $linksToDisplay = array(); | 966 | $linksToDisplay = array(); |
1186 | 967 | ||
1187 | // Get only links which have a thumbnail. | 968 | // Get only links which have a thumbnail. |
@@ -1260,6 +1041,49 @@ function renderPage() | |||
1260 | showDaily($PAGE); | 1041 | showDaily($PAGE); |
1261 | } | 1042 | } |
1262 | 1043 | ||
1044 | // ATOM and RSS feed. | ||
1045 | if ($targetPage == Router::$PAGE_FEED_ATOM || $targetPage == Router::$PAGE_FEED_RSS) { | ||
1046 | $feedType = $targetPage == Router::$PAGE_FEED_RSS ? FeedBuilder::$FEED_RSS : FeedBuilder::$FEED_ATOM; | ||
1047 | header('Content-Type: application/'. $feedType .'+xml; charset=utf-8'); | ||
1048 | |||
1049 | // Cache system | ||
1050 | $query = $_SERVER['QUERY_STRING']; | ||
1051 | $cache = new CachedPage( | ||
1052 | $GLOBALS['config']['PAGECACHE'], | ||
1053 | page_url($_SERVER), | ||
1054 | startsWith($query,'do='. $targetPage) && !isLoggedIn() | ||
1055 | ); | ||
1056 | $cached = $cache->cachedVersion(); | ||
1057 | if (!empty($cached)) { | ||
1058 | echo $cached; | ||
1059 | exit; | ||
1060 | } | ||
1061 | |||
1062 | // Generate data. | ||
1063 | $feedGenerator = new FeedBuilder($LINKSDB, $feedType, $_SERVER, $_GET, isLoggedIn()); | ||
1064 | $feedGenerator->setLocale(strtolower(setlocale(LC_COLLATE, 0))); | ||
1065 | $feedGenerator->setHideDates($GLOBALS['config']['HIDE_TIMESTAMPS'] && !isLoggedIn()); | ||
1066 | $feedGenerator->setUsePermalinks(isset($_GET['permalinks']) || !$GLOBALS['config']['ENABLE_RSS_PERMALINKS']); | ||
1067 | if (!empty($GLOBALS['config']['PUBSUBHUB_URL'])) { | ||
1068 | $feedGenerator->setPubsubhubUrl($GLOBALS['config']['PUBSUBHUB_URL']); | ||
1069 | } | ||
1070 | $data = $feedGenerator->buildData(); | ||
1071 | |||
1072 | // Process plugin hook. | ||
1073 | $pluginManager = PluginManager::getInstance(); | ||
1074 | $pluginManager->executeHooks('render_feed', $data, array( | ||
1075 | 'loggedin' => isLoggedIn(), | ||
1076 | 'target' => $targetPage, | ||
1077 | )); | ||
1078 | |||
1079 | // Render the template. | ||
1080 | $PAGE->assignAll($data); | ||
1081 | $PAGE->renderPage('feed.'. $feedType); | ||
1082 | $cache->cache(ob_get_contents()); | ||
1083 | ob_end_flush(); | ||
1084 | exit; | ||
1085 | } | ||
1086 | |||
1263 | // Display openseach plugin (XML) | 1087 | // Display openseach plugin (XML) |
1264 | if ($targetPage == Router::$PAGE_OPENSEARCH) { | 1088 | if ($targetPage == Router::$PAGE_OPENSEARCH) { |
1265 | header('Content-Type: application/xml; charset=utf-8'); | 1089 | header('Content-Type: application/xml; charset=utf-8'); |
@@ -1511,9 +1335,9 @@ function renderPage() | |||
1511 | 1335 | ||
1512 | // Delete a tag: | 1336 | // Delete a tag: |
1513 | if (isset($_POST['deletetag']) && !empty($_POST['fromtag'])) { | 1337 | if (isset($_POST['deletetag']) && !empty($_POST['fromtag'])) { |
1514 | $needle=trim($_POST['fromtag']); | 1338 | $needle = trim($_POST['fromtag']); |
1515 | // True for case-sensitive tag search. | 1339 | // True for case-sensitive tag search. |
1516 | $linksToAlter = $LINKSDB->filter(LinkFilter::$FILTER_TAG, $needle, true); | 1340 | $linksToAlter = $LINKSDB->filterSearch(array('searchtags' => $needle), true); |
1517 | foreach($linksToAlter as $key=>$value) | 1341 | foreach($linksToAlter as $key=>$value) |
1518 | { | 1342 | { |
1519 | $tags = explode(' ',trim($value['tags'])); | 1343 | $tags = explode(' ',trim($value['tags'])); |
@@ -1528,9 +1352,9 @@ function renderPage() | |||
1528 | 1352 | ||
1529 | // Rename a tag: | 1353 | // Rename a tag: |
1530 | if (isset($_POST['renametag']) && !empty($_POST['fromtag']) && !empty($_POST['totag'])) { | 1354 | if (isset($_POST['renametag']) && !empty($_POST['fromtag']) && !empty($_POST['totag'])) { |
1531 | $needle=trim($_POST['fromtag']); | 1355 | $needle = trim($_POST['fromtag']); |
1532 | // True for case-sensitive tag search. | 1356 | // True for case-sensitive tag search. |
1533 | $linksToAlter = $LINKSDB->filter(LinkFilter::$FILTER_TAG, $needle, true); | 1357 | $linksToAlter = $LINKSDB->filterSearch(array('searchtags' => $needle), true); |
1534 | foreach($linksToAlter as $key=>$value) | 1358 | foreach($linksToAlter as $key=>$value) |
1535 | { | 1359 | { |
1536 | $tags = explode(' ',trim($value['tags'])); | 1360 | $tags = explode(' ',trim($value['tags'])); |
@@ -1966,60 +1790,32 @@ function importFile() | |||
1966 | } | 1790 | } |
1967 | } | 1791 | } |
1968 | 1792 | ||
1969 | // ----------------------------------------------------------------------------------------------- | 1793 | /** |
1970 | // Template for the list of links (<div id="linklist">) | 1794 | * Template for the list of links (<div id="linklist">) |
1971 | // This function fills all the necessary fields in the $PAGE for the template 'linklist.html' | 1795 | * This function fills all the necessary fields in the $PAGE for the template 'linklist.html' |
1796 | * | ||
1797 | * @param pageBuilder $PAGE pageBuilder instance. | ||
1798 | * @param LinkDB $LINKSDB LinkDB instance. | ||
1799 | */ | ||
1972 | function buildLinkList($PAGE,$LINKSDB) | 1800 | function buildLinkList($PAGE,$LINKSDB) |
1973 | { | 1801 | { |
1974 | // Filter link database according to parameters. | 1802 | // Used in templates |
1975 | $searchtags = !empty($_GET['searchtags']) ? escape($_GET['searchtags']) : ''; | 1803 | $searchtags = !empty($_GET['searchtags']) ? escape($_GET['searchtags']) : ''; |
1976 | $searchterm = !empty($_GET['searchterm']) ? escape(trim($_GET['searchterm'])) : ''; | 1804 | $searchterm = !empty($_GET['searchterm']) ? escape($_GET['searchterm']) : ''; |
1977 | $privateonly = !empty($_SESSION['privateonly']) ? true : false; | ||
1978 | |||
1979 | // Search tags + fullsearch. | ||
1980 | if (! empty($searchtags) && ! empty($searchterm)) { | ||
1981 | $linksToDisplay = $LINKSDB->filter( | ||
1982 | LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT, | ||
1983 | array($searchtags, $searchterm), | ||
1984 | false, | ||
1985 | $privateonly | ||
1986 | ); | ||
1987 | } | ||
1988 | // Search by tags. | ||
1989 | elseif (! empty($searchtags)) { | ||
1990 | $linksToDisplay = $LINKSDB->filter( | ||
1991 | LinkFilter::$FILTER_TAG, | ||
1992 | $searchtags, | ||
1993 | false, | ||
1994 | $privateonly | ||
1995 | ); | ||
1996 | } | ||
1997 | // Fulltext search. | ||
1998 | elseif (! empty($searchterm)) { | ||
1999 | $linksToDisplay = $LINKSDB->filter( | ||
2000 | LinkFilter::$FILTER_TEXT, | ||
2001 | $searchterm, | ||
2002 | false, | ||
2003 | $privateonly | ||
2004 | ); | ||
2005 | } | ||
2006 | // Detect smallHashes in URL. | ||
2007 | elseif (! empty($_SERVER['QUERY_STRING']) | ||
2008 | && preg_match('/[a-zA-Z0-9-_@]{6}(&.+?)?/', $_SERVER['QUERY_STRING']) | ||
2009 | ) { | ||
2010 | $linksToDisplay = $LINKSDB->filter( | ||
2011 | LinkFilter::$FILTER_HASH, | ||
2012 | substr(trim($_SERVER["QUERY_STRING"], '/'), 0, 6) | ||
2013 | ); | ||
2014 | 1805 | ||
2015 | if (count($linksToDisplay) == 0) { | 1806 | // Smallhash filter |
2016 | $PAGE->render404('The link you are trying to reach does not exist or has been deleted.'); | 1807 | if (! empty($_SERVER['QUERY_STRING']) |
1808 | && preg_match('/^[a-zA-Z0-9-_@]{6}($|&|#)/', $_SERVER['QUERY_STRING'])) { | ||
1809 | try { | ||
1810 | $linksToDisplay = $LINKSDB->filterHash($_SERVER['QUERY_STRING']); | ||
1811 | } catch (LinkNotFoundException $e) { | ||
1812 | $PAGE->render404($e->getMessage()); | ||
2017 | exit; | 1813 | exit; |
2018 | } | 1814 | } |
2019 | } | 1815 | } else { |
2020 | // Otherwise, display without filtering. | 1816 | // Filter links according search parameters. |
2021 | else { | 1817 | $privateonly = !empty($_SESSION['privateonly']); |
2022 | $linksToDisplay = $LINKSDB->filter('', '', false, $privateonly); | 1818 | $linksToDisplay = $LINKSDB->filterSearch($_GET, false, $privateonly); |
2023 | } | 1819 | } |
2024 | 1820 | ||
2025 | // ---- Handle paging. | 1821 | // ---- Handle paging. |
@@ -2584,8 +2380,6 @@ function resizeImage($filepath) | |||
2584 | } | 2380 | } |
2585 | 2381 | ||
2586 | if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=genthumbnail')) { genThumbnail(); exit; } // Thumbnail generation/cache does not need the link database. | 2382 | if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=genthumbnail')) { genThumbnail(); exit; } // Thumbnail generation/cache does not need the link database. |
2587 | if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=rss')) { showRSS(); exit; } | ||
2588 | if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=atom')) { showATOM(); exit; } | ||
2589 | if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=dailyrss')) { showDailyRSS(); exit; } | 2383 | if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=dailyrss')) { showDailyRSS(); exit; } |
2590 | if (!isset($_SESSION['LINKS_PER_PAGE'])) $_SESSION['LINKS_PER_PAGE']=$GLOBALS['config']['LINKS_PER_PAGE']; | 2384 | if (!isset($_SESSION['LINKS_PER_PAGE'])) $_SESSION['LINKS_PER_PAGE']=$GLOBALS['config']['LINKS_PER_PAGE']; |
2591 | renderPage(); | 2385 | renderPage(); |
diff --git a/plugins/demo_plugin/demo_plugin.php b/plugins/demo_plugin/demo_plugin.php index f5f028e0..18834e53 100644 --- a/plugins/demo_plugin/demo_plugin.php +++ b/plugins/demo_plugin/demo_plugin.php | |||
@@ -322,4 +322,29 @@ function hook_demo_plugin_delete_link($data) | |||
322 | if (strpos($data['url'], 'youtube.com') !== false) { | 322 | if (strpos($data['url'], 'youtube.com') !== false) { |
323 | exit('You can not delete a YouTube link. Don\'t ask.'); | 323 | exit('You can not delete a YouTube link. Don\'t ask.'); |
324 | } | 324 | } |
325 | } \ No newline at end of file | 325 | } |
326 | |||
327 | /** | ||
328 | * Execute render_feed hook. | ||
329 | * Called with ATOM and RSS feed. | ||
330 | * | ||
331 | * Special data keys: | ||
332 | * - _PAGE_: current page | ||
333 | * - _LOGGEDIN_: true/false | ||
334 | * | ||
335 | * @param array $data data passed to plugin | ||
336 | * | ||
337 | * @return array altered $data. | ||
338 | */ | ||
339 | function hook_demo_plugin_render_feed($data) | ||
340 | { | ||
341 | foreach ($data['links'] as &$link) { | ||
342 | if ($data['_PAGE_'] == Router::$PAGE_FEED_ATOM) { | ||
343 | $link['description'] .= ' - ATOM Feed' ; | ||
344 | } | ||
345 | elseif ($data['_PAGE_'] == Router::$PAGE_FEED_RSS) { | ||
346 | $link['description'] .= ' - RSS Feed'; | ||
347 | } | ||
348 | } | ||
349 | return $data; | ||
350 | } | ||
diff --git a/plugins/markdown/README.md b/plugins/markdown/README.md index defaacd1..4f021871 100644 --- a/plugins/markdown/README.md +++ b/plugins/markdown/README.md | |||
@@ -2,25 +2,31 @@ | |||
2 | 2 | ||
3 | Convert all your shaares description to HTML formatted Markdown. | 3 | Convert all your shaares description to HTML formatted Markdown. |
4 | 4 | ||
5 | Read more about Markdown syntax here. | 5 | [Read more about Markdown syntax](http://daringfireball.net/projects/markdown/syntax). |
6 | |||
7 | Markdown processing is done with [Parsedown library](https://github.com/erusev/parsedown). | ||
6 | 8 | ||
7 | ### Installation | 9 | ### Installation |
8 | 10 | ||
9 | Clone this repository inside your `tpl/plugins/` directory, or download the archive and unpack it there. | 11 | As a default plugin, it should already be in `tpl/plugins/` directory. |
12 | If not, download and unpack it there. | ||
13 | |||
10 | The directory structure should look like: | 14 | The directory structure should look like: |
11 | 15 | ||
12 | ``` | 16 | ``` |
13 | ??? plugins | 17 | --- plugins |
14 | ??? markdown | 18 | |--- markdown |
15 | ??? help.html | 19 | |--- help.html |
16 | ??? markdown.css | 20 | |--- markdown.css |
17 | ??? markdown.meta | 21 | |--- markdown.meta |
18 | ??? markdown.php | 22 | |--- markdown.php |
19 | ??? Parsedown.php | 23 | |--- Parsedown.php |
20 | ??? README.md | 24 | |--- README.md |
21 | ``` | 25 | ``` |
22 | 26 | ||
23 | To enable the plugin, add `markdown` to your list of enabled plugins in `data/config.php` | 27 | To enable the plugin, just check it in the plugin administration page. |
28 | |||
29 | You can also add `markdown` to your list of enabled plugins in `data/config.php` | ||
24 | (`ENABLED_PLUGINS` array). | 30 | (`ENABLED_PLUGINS` array). |
25 | 31 | ||
26 | This should look like: | 32 | This should look like: |
@@ -29,6 +35,12 @@ This should look like: | |||
29 | $GLOBALS['config']['ENABLED_PLUGINS'] = array('qrcode', 'any_other_plugin', 'markdown') | 35 | $GLOBALS['config']['ENABLED_PLUGINS'] = array('qrcode', 'any_other_plugin', 'markdown') |
30 | ``` | 36 | ``` |
31 | 37 | ||
38 | ### No Markdown tag | ||
39 | |||
40 | If the tag `.nomarkdown` is set for a shaare, it won't be converted to Markdown syntax. | ||
41 | |||
42 | > Note: it's a private tag (leading dot), so it won't be displayed to visitors. | ||
43 | |||
32 | ### Known issue | 44 | ### Known issue |
33 | 45 | ||
34 | #### Redirector | 46 | #### Redirector |
diff --git a/plugins/markdown/markdown.php b/plugins/markdown/markdown.php index 5a702c7b..544ed22e 100644 --- a/plugins/markdown/markdown.php +++ b/plugins/markdown/markdown.php | |||
@@ -8,6 +8,12 @@ | |||
8 | 8 | ||
9 | require_once 'Parsedown.php'; | 9 | require_once 'Parsedown.php'; |
10 | 10 | ||
11 | /* | ||
12 | * If this tag is used on a shaare, the description won't be processed by Parsedown. | ||
13 | * Using a private tag so it won't appear for visitors. | ||
14 | */ | ||
15 | define('NO_MD_TAG', '.nomarkdown'); | ||
16 | |||
11 | /** | 17 | /** |
12 | * Parse linklist descriptions. | 18 | * Parse linklist descriptions. |
13 | * | 19 | * |
@@ -18,6 +24,9 @@ require_once 'Parsedown.php'; | |||
18 | function hook_markdown_render_linklist($data) | 24 | function hook_markdown_render_linklist($data) |
19 | { | 25 | { |
20 | foreach ($data['links'] as &$value) { | 26 | foreach ($data['links'] as &$value) { |
27 | if (!empty($value['tags']) && noMarkdownTag($value['tags'])) { | ||
28 | continue; | ||
29 | } | ||
21 | $value['description'] = process_markdown($value['description']); | 30 | $value['description'] = process_markdown($value['description']); |
22 | } | 31 | } |
23 | 32 | ||
@@ -36,6 +45,9 @@ function hook_markdown_render_daily($data) | |||
36 | // Manipulate columns data | 45 | // Manipulate columns data |
37 | foreach ($data['cols'] as &$value) { | 46 | foreach ($data['cols'] as &$value) { |
38 | foreach ($value as &$value2) { | 47 | foreach ($value as &$value2) { |
48 | if (!empty($value2['tags']) && noMarkdownTag($value2['tags'])) { | ||
49 | continue; | ||
50 | } | ||
39 | $value2['formatedDescription'] = process_markdown($value2['formatedDescription']); | 51 | $value2['formatedDescription'] = process_markdown($value2['formatedDescription']); |
40 | } | 52 | } |
41 | } | 53 | } |
@@ -44,6 +56,18 @@ function hook_markdown_render_daily($data) | |||
44 | } | 56 | } |
45 | 57 | ||
46 | /** | 58 | /** |
59 | * Check if noMarkdown is set in tags. | ||
60 | * | ||
61 | * @param string $tags tag list | ||
62 | * | ||
63 | * @return bool true if markdown should be disabled on this link. | ||
64 | */ | ||
65 | function noMarkdownTag($tags) | ||
66 | { | ||
67 | return strpos($tags, NO_MD_TAG) !== false; | ||
68 | } | ||
69 | |||
70 | /** | ||
47 | * When link list is displayed, include markdown CSS. | 71 | * When link list is displayed, include markdown CSS. |
48 | * | 72 | * |
49 | * @param array $data includes data. | 73 | * @param array $data includes data. |
@@ -75,6 +99,12 @@ function hook_markdown_render_editlink($data) | |||
75 | { | 99 | { |
76 | // Load help HTML into a string | 100 | // Load help HTML into a string |
77 | $data['edit_link_plugin'][] = file_get_contents(PluginManager::$PLUGINS_PATH .'/markdown/help.html'); | 101 | $data['edit_link_plugin'][] = file_get_contents(PluginManager::$PLUGINS_PATH .'/markdown/help.html'); |
102 | |||
103 | // Add no markdown 'meta-tag' in tag list if it was never used, for autocompletion. | ||
104 | if (! in_array(NO_MD_TAG, $data['tags'])) { | ||
105 | $data['tags'][NO_MD_TAG] = 0; | ||
106 | } | ||
107 | |||
78 | return $data; | 108 | return $data; |
79 | } | 109 | } |
80 | 110 | ||
diff --git a/tests/FeedBuilderTest.php b/tests/FeedBuilderTest.php new file mode 100644 index 00000000..069b1581 --- /dev/null +++ b/tests/FeedBuilderTest.php | |||
@@ -0,0 +1,212 @@ | |||
1 | <?php | ||
2 | |||
3 | require_once 'application/FeedBuilder.php'; | ||
4 | require_once 'application/LinkDB.php'; | ||
5 | |||
6 | /** | ||
7 | * FeedBuilderTest class. | ||
8 | * | ||
9 | * Unit tests for FeedBuilder. | ||
10 | */ | ||
11 | class FeedBuilderTest extends PHPUnit_Framework_TestCase | ||
12 | { | ||
13 | /** | ||
14 | * @var string locale Basque (Spain). | ||
15 | */ | ||
16 | public static $LOCALE = 'eu_ES'; | ||
17 | |||
18 | /** | ||
19 | * @var string language in RSS format. | ||
20 | */ | ||
21 | public static $RSS_LANGUAGE = 'eu-es'; | ||
22 | |||
23 | /** | ||
24 | * @var string language in ATOM format. | ||
25 | */ | ||
26 | public static $ATOM_LANGUAGUE = 'eu'; | ||
27 | |||
28 | protected static $testDatastore = 'sandbox/datastore.php'; | ||
29 | |||
30 | public static $linkDB; | ||
31 | |||
32 | public static $serverInfo; | ||
33 | |||
34 | /** | ||
35 | * Called before every test method. | ||
36 | */ | ||
37 | public static function setUpBeforeClass() | ||
38 | { | ||
39 | $refLinkDB = new ReferenceLinkDB(); | ||
40 | $refLinkDB->write(self::$testDatastore); | ||
41 | self::$linkDB = new LinkDB(self::$testDatastore, true, false); | ||
42 | self::$serverInfo = array( | ||
43 | 'HTTPS' => 'Off', | ||
44 | 'SERVER_NAME' => 'host.tld', | ||
45 | 'SERVER_PORT' => '80', | ||
46 | 'SCRIPT_NAME' => '/index.php', | ||
47 | 'REQUEST_URI' => '/index.php?do=feed', | ||
48 | ); | ||
49 | } | ||
50 | |||
51 | /** | ||
52 | * Test GetTypeLanguage(). | ||
53 | */ | ||
54 | public function testGetTypeLanguage() | ||
55 | { | ||
56 | $feedBuilder = new FeedBuilder(null, FeedBuilder::$FEED_ATOM, null, null, false); | ||
57 | $feedBuilder->setLocale(self::$LOCALE); | ||
58 | $this->assertEquals(self::$ATOM_LANGUAGUE, $feedBuilder->getTypeLanguage()); | ||
59 | $feedBuilder = new FeedBuilder(null, FeedBuilder::$FEED_RSS, null, null, false); | ||
60 | $feedBuilder->setLocale(self::$LOCALE); | ||
61 | $this->assertEquals(self::$RSS_LANGUAGE, $feedBuilder->getTypeLanguage()); | ||
62 | $feedBuilder = new FeedBuilder(null, FeedBuilder::$FEED_ATOM, null, null, false); | ||
63 | $this->assertEquals('en', $feedBuilder->getTypeLanguage()); | ||
64 | $feedBuilder = new FeedBuilder(null, FeedBuilder::$FEED_RSS, null, null, false); | ||
65 | $this->assertEquals('en-en', $feedBuilder->getTypeLanguage()); | ||
66 | } | ||
67 | |||
68 | /** | ||
69 | * Test buildData with RSS feed. | ||
70 | */ | ||
71 | public function testRSSBuildData() | ||
72 | { | ||
73 | $feedBuilder = new FeedBuilder(self::$linkDB, FeedBuilder::$FEED_RSS, self::$serverInfo, null, false); | ||
74 | $feedBuilder->setLocale(self::$LOCALE); | ||
75 | $data = $feedBuilder->buildData(); | ||
76 | // Test headers (RSS) | ||
77 | $this->assertEquals(self::$RSS_LANGUAGE, $data['language']); | ||
78 | $this->assertEmpty($data['pubsubhub_url']); | ||
79 | $this->assertEquals('Tue, 10 Mar 2015 11:46:51 +0100', $data['last_update']); | ||
80 | $this->assertEquals(true, $data['show_dates']); | ||
81 | $this->assertEquals('http://host.tld/index.php?do=feed', $data['self_link']); | ||
82 | $this->assertEquals('http://host.tld/', $data['index_url']); | ||
83 | $this->assertFalse($data['usepermalinks']); | ||
84 | $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links'])); | ||
85 | |||
86 | // Test first link (note link) | ||
87 | $link = array_shift($data['links']); | ||
88 | $this->assertEquals('20150310_114651', $link['linkdate']); | ||
89 | $this->assertEquals('http://host.tld/?WDWyig', $link['guid']); | ||
90 | $this->assertEquals('http://host.tld/?WDWyig', $link['url']); | ||
91 | $this->assertEquals('Tue, 10 Mar 2015 11:46:51 +0100', $link['iso_date']); | ||
92 | $this->assertContains('Stallman has a beard', $link['description']); | ||
93 | $this->assertContains('Permalink', $link['description']); | ||
94 | $this->assertContains('http://host.tld/?WDWyig', $link['description']); | ||
95 | $this->assertEquals(1, count($link['taglist'])); | ||
96 | $this->assertEquals('stuff', $link['taglist'][0]); | ||
97 | |||
98 | // Test URL with external link. | ||
99 | $this->assertEquals('https://static.fsf.org/nosvn/faif-2.0.pdf', $data['links']['20150310_114633']['url']); | ||
100 | |||
101 | // Test multitags. | ||
102 | $this->assertEquals(5, count($data['links']['20141125_084734']['taglist'])); | ||
103 | $this->assertEquals('css', $data['links']['20141125_084734']['taglist'][0]); | ||
104 | } | ||
105 | |||
106 | /** | ||
107 | * Test buildData with ATOM feed (test only specific to ATOM). | ||
108 | */ | ||
109 | public function testAtomBuildData() | ||
110 | { | ||
111 | $feedBuilder = new FeedBuilder(self::$linkDB, FeedBuilder::$FEED_ATOM, self::$serverInfo, null, false); | ||
112 | $feedBuilder->setLocale(self::$LOCALE); | ||
113 | $data = $feedBuilder->buildData(); | ||
114 | $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links'])); | ||
115 | $link = array_shift($data['links']); | ||
116 | $this->assertEquals('2015-03-10T11:46:51+01:00', $link['iso_date']); | ||
117 | } | ||
118 | |||
119 | /** | ||
120 | * Test buildData with search criteria. | ||
121 | */ | ||
122 | public function testBuildDataFiltered() | ||
123 | { | ||
124 | $criteria = array( | ||
125 | 'searchtags' => 'stuff', | ||
126 | 'searchterm' => 'beard', | ||
127 | ); | ||
128 | $feedBuilder = new FeedBuilder(self::$linkDB, FeedBuilder::$FEED_ATOM, self::$serverInfo, $criteria, false); | ||
129 | $feedBuilder->setLocale(self::$LOCALE); | ||
130 | $data = $feedBuilder->buildData(); | ||
131 | $this->assertEquals(1, count($data['links'])); | ||
132 | $link = array_shift($data['links']); | ||
133 | $this->assertEquals('20150310_114651', $link['linkdate']); | ||
134 | } | ||
135 | |||
136 | /** | ||
137 | * Test buildData with nb limit. | ||
138 | */ | ||
139 | public function testBuildDataCount() | ||
140 | { | ||
141 | $criteria = array( | ||
142 | 'nb' => '1', | ||
143 | ); | ||
144 | $feedBuilder = new FeedBuilder(self::$linkDB, FeedBuilder::$FEED_ATOM, self::$serverInfo, $criteria, false); | ||
145 | $feedBuilder->setLocale(self::$LOCALE); | ||
146 | $data = $feedBuilder->buildData(); | ||
147 | $this->assertEquals(1, count($data['links'])); | ||
148 | $link = array_shift($data['links']); | ||
149 | $this->assertEquals('20150310_114651', $link['linkdate']); | ||
150 | } | ||
151 | |||
152 | /** | ||
153 | * Test buildData with permalinks on. | ||
154 | */ | ||
155 | public function testBuildDataPermalinks() | ||
156 | { | ||
157 | $feedBuilder = new FeedBuilder(self::$linkDB, FeedBuilder::$FEED_ATOM, self::$serverInfo, null, false); | ||
158 | $feedBuilder->setLocale(self::$LOCALE); | ||
159 | $feedBuilder->setUsePermalinks(true); | ||
160 | $data = $feedBuilder->buildData(); | ||
161 | $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links'])); | ||
162 | $this->assertTrue($data['usepermalinks']); | ||
163 | // First link is a permalink | ||
164 | $link = array_shift($data['links']); | ||
165 | $this->assertEquals('20150310_114651', $link['linkdate']); | ||
166 | $this->assertEquals('http://host.tld/?WDWyig', $link['guid']); | ||
167 | $this->assertEquals('http://host.tld/?WDWyig', $link['url']); | ||
168 | $this->assertContains('Direct link', $link['description']); | ||
169 | $this->assertContains('http://host.tld/?WDWyig', $link['description']); | ||
170 | // Second link is a direct link | ||
171 | $link = array_shift($data['links']); | ||
172 | $this->assertEquals('20150310_114633', $link['linkdate']); | ||
173 | $this->assertEquals('http://host.tld/?kLHmZg', $link['guid']); | ||
174 | $this->assertEquals('https://static.fsf.org/nosvn/faif-2.0.pdf', $link['url']); | ||
175 | $this->assertContains('Direct link', $link['description']); | ||
176 | $this->assertContains('https://static.fsf.org/nosvn/faif-2.0.pdf', $link['description']); | ||
177 | } | ||
178 | |||
179 | /** | ||
180 | * Test buildData with hide dates settings. | ||
181 | */ | ||
182 | public function testBuildDataHideDates() | ||
183 | { | ||
184 | $feedBuilder = new FeedBuilder(self::$linkDB, FeedBuilder::$FEED_ATOM, self::$serverInfo, null, false); | ||
185 | $feedBuilder->setLocale(self::$LOCALE); | ||
186 | $feedBuilder->setHideDates(true); | ||
187 | $data = $feedBuilder->buildData(); | ||
188 | $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links'])); | ||
189 | $this->assertFalse($data['show_dates']); | ||
190 | |||
191 | // Show dates while logged in | ||
192 | $feedBuilder = new FeedBuilder(self::$linkDB, FeedBuilder::$FEED_ATOM, self::$serverInfo, null, true); | ||
193 | $feedBuilder->setLocale(self::$LOCALE); | ||
194 | $feedBuilder->setHideDates(true); | ||
195 | $data = $feedBuilder->buildData(); | ||
196 | $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links'])); | ||
197 | $this->assertTrue($data['show_dates']); | ||
198 | } | ||
199 | |||
200 | /** | ||
201 | * Test buildData with hide dates settings. | ||
202 | */ | ||
203 | public function testBuildDataPubsubhub() | ||
204 | { | ||
205 | $feedBuilder = new FeedBuilder(self::$linkDB, FeedBuilder::$FEED_ATOM, self::$serverInfo, null, false); | ||
206 | $feedBuilder->setLocale(self::$LOCALE); | ||
207 | $feedBuilder->setPubsubhubUrl('http://pubsubhub.io'); | ||
208 | $data = $feedBuilder->buildData(); | ||
209 | $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links'])); | ||
210 | $this->assertEquals('http://pubsubhub.io', $data['pubsubhub_url']); | ||
211 | } | ||
212 | } | ||
diff --git a/tests/LinkDBTest.php b/tests/LinkDBTest.php index b6a273b3..52d31400 100644 --- a/tests/LinkDBTest.php +++ b/tests/LinkDBTest.php | |||
@@ -17,8 +17,20 @@ class LinkDBTest extends PHPUnit_Framework_TestCase | |||
17 | { | 17 | { |
18 | // datastore to test write operations | 18 | // datastore to test write operations |
19 | protected static $testDatastore = 'sandbox/datastore.php'; | 19 | protected static $testDatastore = 'sandbox/datastore.php'; |
20 | |||
21 | /** | ||
22 | * @var ReferenceLinkDB instance. | ||
23 | */ | ||
20 | protected static $refDB = null; | 24 | protected static $refDB = null; |
25 | |||
26 | /** | ||
27 | * @var LinkDB public LinkDB instance. | ||
28 | */ | ||
21 | protected static $publicLinkDB = null; | 29 | protected static $publicLinkDB = null; |
30 | |||
31 | /** | ||
32 | * @var LinkDB private LinkDB instance. | ||
33 | */ | ||
22 | protected static $privateLinkDB = null; | 34 | protected static $privateLinkDB = null; |
23 | 35 | ||
24 | /** | 36 | /** |
@@ -335,9 +347,10 @@ class LinkDBTest extends PHPUnit_Framework_TestCase | |||
335 | public function testFilterString() | 347 | public function testFilterString() |
336 | { | 348 | { |
337 | $tags = 'dev cartoon'; | 349 | $tags = 'dev cartoon'; |
350 | $request = array('searchtags' => $tags); | ||
338 | $this->assertEquals( | 351 | $this->assertEquals( |
339 | 2, | 352 | 2, |
340 | count(self::$privateLinkDB->filter(LinkFilter::$FILTER_TAG, $tags, true, false)) | 353 | count(self::$privateLinkDB->filterSearch($request, true, false)) |
341 | ); | 354 | ); |
342 | } | 355 | } |
343 | 356 | ||
@@ -347,9 +360,10 @@ class LinkDBTest extends PHPUnit_Framework_TestCase | |||
347 | public function testFilterArray() | 360 | public function testFilterArray() |
348 | { | 361 | { |
349 | $tags = array('dev', 'cartoon'); | 362 | $tags = array('dev', 'cartoon'); |
363 | $request = array('searchtags' => $tags); | ||
350 | $this->assertEquals( | 364 | $this->assertEquals( |
351 | 2, | 365 | 2, |
352 | count(self::$privateLinkDB->filter(LinkFilter::$FILTER_TAG, $tags, true, false)) | 366 | count(self::$privateLinkDB->filterSearch($request, true, false)) |
353 | ); | 367 | ); |
354 | } | 368 | } |
355 | 369 | ||
@@ -360,14 +374,48 @@ class LinkDBTest extends PHPUnit_Framework_TestCase | |||
360 | public function testHiddenTags() | 374 | public function testHiddenTags() |
361 | { | 375 | { |
362 | $tags = '.hidden'; | 376 | $tags = '.hidden'; |
377 | $request = array('searchtags' => $tags); | ||
363 | $this->assertEquals( | 378 | $this->assertEquals( |
364 | 1, | 379 | 1, |
365 | count(self::$privateLinkDB->filter(LinkFilter::$FILTER_TAG, $tags, true, false)) | 380 | count(self::$privateLinkDB->filterSearch($request, true, false)) |
366 | ); | 381 | ); |
367 | 382 | ||
368 | $this->assertEquals( | 383 | $this->assertEquals( |
369 | 0, | 384 | 0, |
370 | count(self::$publicLinkDB->filter(LinkFilter::$FILTER_TAG, $tags, true, false)) | 385 | count(self::$publicLinkDB->filterSearch($request, true, false)) |
371 | ); | 386 | ); |
372 | } | 387 | } |
388 | |||
389 | /** | ||
390 | * Test filterHash() with a valid smallhash. | ||
391 | */ | ||
392 | public function testFilterHashValid() | ||
393 | { | ||
394 | $request = smallHash('20150310_114651'); | ||
395 | $this->assertEquals( | ||
396 | 1, | ||
397 | count(self::$publicLinkDB->filterHash($request)) | ||
398 | ); | ||
399 | } | ||
400 | |||
401 | /** | ||
402 | * Test filterHash() with an invalid smallhash. | ||
403 | * | ||
404 | * @expectedException LinkNotFoundException | ||
405 | */ | ||
406 | public function testFilterHashInValid1() | ||
407 | { | ||
408 | $request = 'blabla'; | ||
409 | self::$publicLinkDB->filterHash($request); | ||
410 | } | ||
411 | |||
412 | /** | ||
413 | * Test filterHash() with an empty smallhash. | ||
414 | * | ||
415 | * @expectedException LinkNotFoundException | ||
416 | */ | ||
417 | public function testFilterHashInValid() | ||
418 | { | ||
419 | self::$publicLinkDB->filterHash(''); | ||
420 | } | ||
373 | } | 421 | } |
diff --git a/tests/LinkFilterTest.php b/tests/LinkFilterTest.php index ef1cc10a..1620bb78 100644 --- a/tests/LinkFilterTest.php +++ b/tests/LinkFilterTest.php | |||
@@ -12,8 +12,6 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase | |||
12 | */ | 12 | */ |
13 | protected static $linkFilter; | 13 | protected static $linkFilter; |
14 | 14 | ||
15 | protected static $NB_LINKS_REFDB = 7; | ||
16 | |||
17 | /** | 15 | /** |
18 | * Instanciate linkFilter with ReferenceLinkDB data. | 16 | * Instanciate linkFilter with ReferenceLinkDB data. |
19 | */ | 17 | */ |
@@ -29,7 +27,7 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase | |||
29 | public function testFilter() | 27 | public function testFilter() |
30 | { | 28 | { |
31 | $this->assertEquals( | 29 | $this->assertEquals( |
32 | self::$NB_LINKS_REFDB, | 30 | ReferenceLinkDB::$NB_LINKS_TOTAL, |
33 | count(self::$linkFilter->filter('', '')) | 31 | count(self::$linkFilter->filter('', '')) |
34 | ); | 32 | ); |
35 | 33 | ||
@@ -40,12 +38,12 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase | |||
40 | ); | 38 | ); |
41 | 39 | ||
42 | $this->assertEquals( | 40 | $this->assertEquals( |
43 | self::$NB_LINKS_REFDB, | 41 | ReferenceLinkDB::$NB_LINKS_TOTAL, |
44 | count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, '')) | 42 | count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, '')) |
45 | ); | 43 | ); |
46 | 44 | ||
47 | $this->assertEquals( | 45 | $this->assertEquals( |
48 | self::$NB_LINKS_REFDB, | 46 | ReferenceLinkDB::$NB_LINKS_TOTAL, |
49 | count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, '')) | 47 | count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, '')) |
50 | ); | 48 | ); |
51 | } | 49 | } |
@@ -167,13 +165,12 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase | |||
167 | 165 | ||
168 | /** | 166 | /** |
169 | * No link for this hash | 167 | * No link for this hash |
168 | * | ||
169 | * @expectedException LinkNotFoundException | ||
170 | */ | 170 | */ |
171 | public function testFilterUnknownSmallHash() | 171 | public function testFilterUnknownSmallHash() |
172 | { | 172 | { |
173 | $this->assertEquals( | 173 | self::$linkFilter->filter(LinkFilter::$FILTER_HASH, 'Iblaah'); |
174 | 0, | ||
175 | count(self::$linkFilter->filter(LinkFilter::$FILTER_HASH, 'Iblaah')) | ||
176 | ); | ||
177 | } | 174 | } |
178 | 175 | ||
179 | /** | 176 | /** |
@@ -383,7 +380,7 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase | |||
383 | )) | 380 | )) |
384 | ); | 381 | ); |
385 | $this->assertEquals( | 382 | $this->assertEquals( |
386 | self::$NB_LINKS_REFDB, | 383 | ReferenceLinkDB::$NB_LINKS_TOTAL, |
387 | count(self::$linkFilter->filter( | 384 | count(self::$linkFilter->filter( |
388 | LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT, | 385 | LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT, |
389 | '' | 386 | '' |
diff --git a/tests/Updater/UpdaterTest.php b/tests/Updater/UpdaterTest.php index d865066b..a29d9067 100644 --- a/tests/Updater/UpdaterTest.php +++ b/tests/Updater/UpdaterTest.php | |||
@@ -236,9 +236,9 @@ class UpdaterTest extends PHPUnit_Framework_TestCase | |||
236 | $refDB = new ReferenceLinkDB(); | 236 | $refDB = new ReferenceLinkDB(); |
237 | $refDB->write(self::$testDatastore); | 237 | $refDB->write(self::$testDatastore); |
238 | $linkDB = new LinkDB(self::$testDatastore, true, false); | 238 | $linkDB = new LinkDB(self::$testDatastore, true, false); |
239 | $this->assertEmpty($linkDB->filter(LinkFilter::$FILTER_TAG, 'exclude')); | 239 | $this->assertEmpty($linkDB->filterSearch(array('searchtags' => 'exclude'))); |
240 | $updater = new Updater(array(), self::$configFields, $linkDB, true); | 240 | $updater = new Updater(array(), self::$configFields, $linkDB, true); |
241 | $updater->updateMethodRenameDashTags(); | 241 | $updater->updateMethodRenameDashTags(); |
242 | $this->assertNotEmpty($linkDB->filter(LinkFilter::$FILTER_TAG, 'exclude')); | 242 | $this->assertNotEmpty($linkDB->filterSearch(array('searchtags' => 'exclude'))); |
243 | } | 243 | } |
244 | } | 244 | } |
diff --git a/tests/plugins/PluginMarkdownTest.php b/tests/plugins/PluginMarkdownTest.php index 8e1a128a..fa7e1d52 100644 --- a/tests/plugins/PluginMarkdownTest.php +++ b/tests/plugins/PluginMarkdownTest.php | |||
@@ -102,7 +102,8 @@ class PluginMarkdownTest extends PHPUnit_Framework_TestCase | |||
102 | /** | 102 | /** |
103 | * Test sanitize_html(). | 103 | * Test sanitize_html(). |
104 | */ | 104 | */ |
105 | function testSanitizeHtml() { | 105 | function testSanitizeHtml() |
106 | { | ||
106 | $input = '< script src="js.js"/>'; | 107 | $input = '< script src="js.js"/>'; |
107 | $input .= '< script attr>alert(\'xss\');</script>'; | 108 | $input .= '< script attr>alert(\'xss\');</script>'; |
108 | $input .= '<style> * { display: none }</style>'; | 109 | $input .= '<style> * { display: none }</style>'; |
@@ -114,4 +115,38 @@ class PluginMarkdownTest extends PHPUnit_Framework_TestCase | |||
114 | $input = escape($input); | 115 | $input = escape($input); |
115 | $this->assertEquals($input, sanitize_html($input)); | 116 | $this->assertEquals($input, sanitize_html($input)); |
116 | } | 117 | } |
118 | |||
119 | /** | ||
120 | * Test the no markdown tag. | ||
121 | */ | ||
122 | function testNoMarkdownTag() | ||
123 | { | ||
124 | $str = 'All _work_ and `no play` makes Jack a *dull* boy.'; | ||
125 | $data = array( | ||
126 | 'links' => array(array( | ||
127 | 'description' => $str, | ||
128 | 'tags' => NO_MD_TAG | ||
129 | )) | ||
130 | ); | ||
131 | |||
132 | $data = hook_markdown_render_linklist($data); | ||
133 | $this->assertEquals($str, $data['links'][0]['description']); | ||
134 | |||
135 | $data = array( | ||
136 | // Columns data | ||
137 | 'cols' => array( | ||
138 | // First, second, third. | ||
139 | 0 => array( | ||
140 | // nth link | ||
141 | 0 => array( | ||
142 | 'formatedDescription' => $str, | ||
143 | 'tags' => NO_MD_TAG | ||
144 | ), | ||
145 | ), | ||
146 | ), | ||
147 | ); | ||
148 | |||
149 | $data = hook_markdown_render_daily($data); | ||
150 | $this->assertEquals($str, $data['cols'][0][0]['formatedDescription']); | ||
151 | } | ||
117 | } | 152 | } |
diff --git a/tests/utils/ReferenceLinkDB.php b/tests/utils/ReferenceLinkDB.php index 61faef05..dc4f5dfa 100644 --- a/tests/utils/ReferenceLinkDB.php +++ b/tests/utils/ReferenceLinkDB.php | |||
@@ -4,6 +4,8 @@ | |||
4 | */ | 4 | */ |
5 | class ReferenceLinkDB | 5 | class ReferenceLinkDB |
6 | { | 6 | { |
7 | public static $NB_LINKS_TOTAL = 7; | ||
8 | |||
7 | private $_links = array(); | 9 | private $_links = array(); |
8 | private $_publicCount = 0; | 10 | private $_publicCount = 0; |
9 | private $_privateCount = 0; | 11 | private $_privateCount = 0; |
@@ -14,6 +16,15 @@ class ReferenceLinkDB | |||
14 | function __construct() | 16 | function __construct() |
15 | { | 17 | { |
16 | $this->addLink( | 18 | $this->addLink( |
19 | 'Link title: @website', | ||
20 | '?WDWyig', | ||
21 | 'Stallman has a beard and is part of the Free Software Foundation (or not). Seriously, read this.', | ||
22 | 0, | ||
23 | '20150310_114651', | ||
24 | 'stuff' | ||
25 | ); | ||
26 | |||
27 | $this->addLink( | ||
17 | 'Free as in Freedom 2.0 @website', | 28 | 'Free as in Freedom 2.0 @website', |
18 | 'https://static.fsf.org/nosvn/faif-2.0.pdf', | 29 | 'https://static.fsf.org/nosvn/faif-2.0.pdf', |
19 | 'Richard Stallman and the Free Software Revolution. Read this.', | 30 | 'Richard Stallman and the Free Software Revolution. Read this.', |
@@ -23,15 +34,6 @@ class ReferenceLinkDB | |||
23 | ); | 34 | ); |
24 | 35 | ||
25 | $this->addLink( | 36 | $this->addLink( |
26 | 'Link title: @website', | ||
27 | 'local', | ||
28 | 'Stallman has a beard and is part of the Free Software Foundation (or not). Seriously, read this.', | ||
29 | 0, | ||
30 | '20150310_114651', | ||
31 | 'stuff' | ||
32 | ); | ||
33 | |||
34 | $this->addLink( | ||
35 | 'MediaGoblin', | 37 | 'MediaGoblin', |
36 | 'http://mediagoblin.org/', | 38 | 'http://mediagoblin.org/', |
37 | 'A free software media publishing platform', | 39 | 'A free software media publishing platform', |
diff --git a/tpl/configure.html b/tpl/configure.html index 9c725a51..77c8b7d9 100644 --- a/tpl/configure.html +++ b/tpl/configure.html | |||
@@ -22,9 +22,12 @@ | |||
22 | <input type="checkbox" name="privateLinkByDefault" id="privateLinkByDefault" {if="!empty($GLOBALS['privateLinkByDefault'])"}checked{/if}/><label for="privateLinkByDefault"> All new links are private by default</label></td> | 22 | <input type="checkbox" name="privateLinkByDefault" id="privateLinkByDefault" {if="!empty($GLOBALS['privateLinkByDefault'])"}checked{/if}/><label for="privateLinkByDefault"> All new links are private by default</label></td> |
23 | </tr> | 23 | </tr> |
24 | <tr> | 24 | <tr> |
25 | <td valign="top"><b>Enable RSS Permalinks</b></td> | 25 | <td valign="top"><b>RSS direct links</b></td> |
26 | <td> | 26 | <td> |
27 | <input type="checkbox" name="enableRssPermalinks" id="enableRssPermalinks" {if="!empty($GLOBALS['config']['ENABLE_RSS_PERMALINKS'])"}checked{/if}/><label for="enableRssPermalinks"> Switches the RSS feed URLs between full URLs and shortlinks. Enabling it will show a permalink in the description, and the feed item will be linked to the absolute URL. Disabling it swaps this behaviour around (permalink in title and link in description). RSS Permalinks are currently <b>{if="$GLOBALS['config']['ENABLE_RSS_PERMALINKS']"}enabled{else}disabled{/if}</b></label> | 27 | <input type="checkbox" name="enableRssPermalinks" id="enableRssPermalinks" {if="!empty($GLOBALS['config']['ENABLE_RSS_PERMALINKS'])"}checked{/if}/> |
28 | <label for="enableRssPermalinks"> | ||
29 | Disable it to use permalinks in RSS feed instead of direct links to your shaared links. Currently <b>{if="$GLOBALS['config']['ENABLE_RSS_PERMALINKS']"}enabled{else}disabled{/if}.</b> | ||
30 | </label> | ||
28 | </td> | 31 | </td> |
29 | </tr> | 32 | </tr> |
30 | <tr> | 33 | <tr> |
diff --git a/tpl/feed.atom.html b/tpl/feed.atom.html new file mode 100644 index 00000000..2ebb162a --- /dev/null +++ b/tpl/feed.atom.html | |||
@@ -0,0 +1,40 @@ | |||
1 | <?xml version="1.0" encoding="UTF-8"?> | ||
2 | <feed xmlns="http://www.w3.org/2005/Atom"> | ||
3 | <title>{$pagetitle}</title> | ||
4 | <subtitle>Shaared links</subtitle> | ||
5 | {if="$show_dates"} | ||
6 | <updated>{$last_update}</updated> | ||
7 | {/if} | ||
8 | <link rel="self" href="{$self_link}#" /> | ||
9 | {if="!empty($pubsubhub_url)"} | ||
10 | <!-- PubSubHubbub Discovery --> | ||
11 | <link rel="hub" href="{$pubsubhub_url}#" /> | ||
12 | <!-- End Of PubSubHubbub Discovery --> | ||
13 | {/if} | ||
14 | <author> | ||
15 | <name>{$index_url}</name> | ||
16 | <uri>{$index_url}</uri> | ||
17 | </author> | ||
18 | <id>{$index_url}</id> | ||
19 | <generator>Shaarli</generator> | ||
20 | {loop="links"} | ||
21 | <entry> | ||
22 | <title>{$value.title}</title> | ||
23 | {if="$usepermalinks"} | ||
24 | <link href="{$value.guid}#" /> | ||
25 | {else} | ||
26 | <link href="{$value.url}#" /> | ||
27 | {/if} | ||
28 | <id>{$value.guid}</id> | ||
29 | {if="$show_dates"} | ||
30 | <updated>{$value.iso_date}</updated> | ||
31 | {/if} | ||
32 | <content type="html" xml:lang="{$language}"> | ||
33 | <![CDATA[{$value.description}]]> | ||
34 | </content> | ||
35 | {loop="$value.taglist"} | ||
36 | <category scheme="{$index_url}?searchtags=" term="{$value|strtolower}" label="{$value}" /> | ||
37 | {/loop} | ||
38 | </entry> | ||
39 | {/loop} | ||
40 | </feed> | ||
diff --git a/tpl/feed.rss.html b/tpl/feed.rss.html new file mode 100644 index 00000000..26de7f19 --- /dev/null +++ b/tpl/feed.rss.html | |||
@@ -0,0 +1,34 @@ | |||
1 | <?xml version="1.0" encoding="UTF-8"?> | ||
2 | <rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"> | ||
3 | <channel> | ||
4 | <title>{$pagetitle}</title> | ||
5 | <link>{$index_url}</link> | ||
6 | <description>Shaared links</description> | ||
7 | <language>{$language}</language> | ||
8 | <copyright>{$index_url}</copyright> | ||
9 | <generator>Shaarli</generator> | ||
10 | <atom:link rel="self" href="{$self_link}" /> | ||
11 | {if="!empty($pubsubhub_url)"} | ||
12 | <!-- PubSubHubbub Discovery --> | ||
13 | <atom:link rel="hub" href="{$pubsubhub_url}" /> | ||
14 | {/if} | ||
15 | {loop="links"} | ||
16 | <item> | ||
17 | <title>{$value.title}</title> | ||
18 | <guid isPermaLink="{if="$usepermalinks"}true{else}false{/if}">{$value.guid}</guid> | ||
19 | {if="$usepermalinks"} | ||
20 | <link>{$value.guid}</link> | ||
21 | {else} | ||
22 | <link>{$value.url}</link> | ||
23 | {/if} | ||
24 | {if="$show_dates"} | ||
25 | <pubDate>{$value.iso_date}</pubDate> | ||
26 | {/if} | ||
27 | <description><![CDATA[{$value.description}]]></description> | ||
28 | {loop="$value.taglist"} | ||
29 | <category domain="{$index_url}?searchtags=">{$value}</category> | ||
30 | {/loop} | ||
31 | </item> | ||
32 | {/loop} | ||
33 | </channel> | ||
34 | </rss> | ||