aboutsummaryrefslogtreecommitdiffhomepage
path: root/application
diff options
context:
space:
mode:
Diffstat (limited to 'application')
-rw-r--r--application/History.php17
-rw-r--r--application/Utils.php2
-rw-r--r--application/api/ApiMiddleware.php11
-rw-r--r--application/api/ApiUtils.php79
-rw-r--r--application/api/controllers/ApiController.php8
-rw-r--r--application/api/controllers/HistoryController.php2
-rw-r--r--application/api/controllers/Info.php5
-rw-r--r--application/api/controllers/Links.php79
-rw-r--r--application/api/controllers/Tags.php44
-rw-r--r--application/bookmark/BookmarkArray.php2
-rw-r--r--application/config/ConfigManager.php2
-rw-r--r--application/feed/FeedBuilder.php78
-rw-r--r--application/formatter/BookmarkMarkdownFormatter.php6
-rw-r--r--application/netscape/NetscapeBookmarkUtils.php117
-rw-r--r--application/render/PageBuilder.php24
-rw-r--r--application/updater/Updater.php480
-rw-r--r--application/updater/UpdaterUtils.php65
17 files changed, 287 insertions, 734 deletions
diff --git a/application/History.php b/application/History.php
index a5846652..4fd2f294 100644
--- a/application/History.php
+++ b/application/History.php
@@ -3,6 +3,7 @@ namespace Shaarli;
3 3
4use DateTime; 4use DateTime;
5use Exception; 5use Exception;
6use Shaarli\Bookmark\Bookmark;
6 7
7/** 8/**
8 * Class History 9 * Class History
@@ -20,7 +21,7 @@ use Exception;
20 * - UPDATED: link updated 21 * - UPDATED: link updated
21 * - DELETED: link deleted 22 * - DELETED: link deleted
22 * - SETTINGS: the settings have been updated through the UI. 23 * - SETTINGS: the settings have been updated through the UI.
23 * - IMPORT: bulk links import 24 * - IMPORT: bulk bookmarks import
24 * 25 *
25 * Note: new events are put at the beginning of the file and history array. 26 * Note: new events are put at the beginning of the file and history array.
26 */ 27 */
@@ -96,31 +97,31 @@ class History
96 /** 97 /**
97 * Add Event: new link. 98 * Add Event: new link.
98 * 99 *
99 * @param array $link Link data. 100 * @param Bookmark $link Link data.
100 */ 101 */
101 public function addLink($link) 102 public function addLink($link)
102 { 103 {
103 $this->addEvent(self::CREATED, $link['id']); 104 $this->addEvent(self::CREATED, $link->getId());
104 } 105 }
105 106
106 /** 107 /**
107 * Add Event: update existing link. 108 * Add Event: update existing link.
108 * 109 *
109 * @param array $link Link data. 110 * @param Bookmark $link Link data.
110 */ 111 */
111 public function updateLink($link) 112 public function updateLink($link)
112 { 113 {
113 $this->addEvent(self::UPDATED, $link['id']); 114 $this->addEvent(self::UPDATED, $link->getId());
114 } 115 }
115 116
116 /** 117 /**
117 * Add Event: delete existing link. 118 * Add Event: delete existing link.
118 * 119 *
119 * @param array $link Link data. 120 * @param Bookmark $link Link data.
120 */ 121 */
121 public function deleteLink($link) 122 public function deleteLink($link)
122 { 123 {
123 $this->addEvent(self::DELETED, $link['id']); 124 $this->addEvent(self::DELETED, $link->getId());
124 } 125 }
125 126
126 /** 127 /**
@@ -134,7 +135,7 @@ class History
134 /** 135 /**
135 * Add Event: bulk import. 136 * Add Event: bulk import.
136 * 137 *
137 * Note: we don't store links add/update one by one since it can have a huge impact on performances. 138 * Note: we don't store bookmarks add/update one by one since it can have a huge impact on performances.
138 */ 139 */
139 public function importLinks() 140 public function importLinks()
140 { 141 {
diff --git a/application/Utils.php b/application/Utils.php
index 925e1a22..56f5b9a2 100644
--- a/application/Utils.php
+++ b/application/Utils.php
@@ -162,7 +162,7 @@ function generateLocation($referer, $host, $loopTerms = array())
162 $finalReferer = '?'; 162 $finalReferer = '?';
163 163
164 // No referer if it contains any value in $loopCriteria. 164 // No referer if it contains any value in $loopCriteria.
165 foreach ($loopTerms as $value) { 165 foreach (array_filter($loopTerms) as $value) {
166 if (strpos($referer, $value) !== false) { 166 if (strpos($referer, $value) !== false) {
167 return $finalReferer; 167 return $finalReferer;
168 } 168 }
diff --git a/application/api/ApiMiddleware.php b/application/api/ApiMiddleware.php
index 2d55bda6..4745ac94 100644
--- a/application/api/ApiMiddleware.php
+++ b/application/api/ApiMiddleware.php
@@ -3,6 +3,7 @@ namespace Shaarli\Api;
3 3
4use Shaarli\Api\Exceptions\ApiAuthorizationException; 4use Shaarli\Api\Exceptions\ApiAuthorizationException;
5use Shaarli\Api\Exceptions\ApiException; 5use Shaarli\Api\Exceptions\ApiException;
6use Shaarli\Bookmark\BookmarkFileService;
6use Shaarli\Config\ConfigManager; 7use Shaarli\Config\ConfigManager;
7use Slim\Container; 8use Slim\Container;
8use Slim\Http\Request; 9use Slim\Http\Request;
@@ -117,7 +118,7 @@ class ApiMiddleware
117 } 118 }
118 119
119 /** 120 /**
120 * Instantiate a new LinkDB including private links, 121 * Instantiate a new LinkDB including private bookmarks,
121 * and load in the Slim container. 122 * and load in the Slim container.
122 * 123 *
123 * FIXME! LinkDB could use a refactoring to avoid this trick. 124 * FIXME! LinkDB could use a refactoring to avoid this trick.
@@ -126,10 +127,10 @@ class ApiMiddleware
126 */ 127 */
127 protected function setLinkDb($conf) 128 protected function setLinkDb($conf)
128 { 129 {
129 $linkDb = new \Shaarli\Bookmark\LinkDB( 130 $linkDb = new BookmarkFileService(
130 $conf->get('resource.datastore'), 131 $conf,
131 true, 132 $this->container->get('history'),
132 $conf->get('privacy.hide_public_links') 133 true
133 ); 134 );
134 $this->container['db'] = $linkDb; 135 $this->container['db'] = $linkDb;
135 } 136 }
diff --git a/application/api/ApiUtils.php b/application/api/ApiUtils.php
index 5ac07c4d..5156a5f7 100644
--- a/application/api/ApiUtils.php
+++ b/application/api/ApiUtils.php
@@ -2,6 +2,7 @@
2namespace Shaarli\Api; 2namespace Shaarli\Api;
3 3
4use Shaarli\Api\Exceptions\ApiAuthorizationException; 4use Shaarli\Api\Exceptions\ApiAuthorizationException;
5use Shaarli\Bookmark\Bookmark;
5use Shaarli\Http\Base64Url; 6use Shaarli\Http\Base64Url;
6 7
7/** 8/**
@@ -54,28 +55,28 @@ class ApiUtils
54 /** 55 /**
55 * Format a Link for the REST API. 56 * Format a Link for the REST API.
56 * 57 *
57 * @param array $link Link data read from the datastore. 58 * @param Bookmark $bookmark Bookmark data read from the datastore.
58 * @param string $indexUrl Shaarli's index URL (used for relative URL). 59 * @param string $indexUrl Shaarli's index URL (used for relative URL).
59 * 60 *
60 * @return array Link data formatted for the REST API. 61 * @return array Link data formatted for the REST API.
61 */ 62 */
62 public static function formatLink($link, $indexUrl) 63 public static function formatLink($bookmark, $indexUrl)
63 { 64 {
64 $out['id'] = $link['id']; 65 $out['id'] = $bookmark->getId();
65 // Not an internal link 66 // Not an internal link
66 if (! is_note($link['url'])) { 67 if (! $bookmark->isNote()) {
67 $out['url'] = $link['url']; 68 $out['url'] = $bookmark->getUrl();
68 } else { 69 } else {
69 $out['url'] = $indexUrl . $link['url']; 70 $out['url'] = $indexUrl . $bookmark->getUrl();
70 } 71 }
71 $out['shorturl'] = $link['shorturl']; 72 $out['shorturl'] = $bookmark->getShortUrl();
72 $out['title'] = $link['title']; 73 $out['title'] = $bookmark->getTitle();
73 $out['description'] = $link['description']; 74 $out['description'] = $bookmark->getDescription();
74 $out['tags'] = preg_split('/\s+/', $link['tags'], -1, PREG_SPLIT_NO_EMPTY); 75 $out['tags'] = $bookmark->getTags();
75 $out['private'] = $link['private'] == true; 76 $out['private'] = $bookmark->isPrivate();
76 $out['created'] = $link['created']->format(\DateTime::ATOM); 77 $out['created'] = $bookmark->getCreated()->format(\DateTime::ATOM);
77 if (! empty($link['updated'])) { 78 if (! empty($bookmark->getUpdated())) {
78 $out['updated'] = $link['updated']->format(\DateTime::ATOM); 79 $out['updated'] = $bookmark->getUpdated()->format(\DateTime::ATOM);
79 } else { 80 } else {
80 $out['updated'] = ''; 81 $out['updated'] = '';
81 } 82 }
@@ -83,7 +84,7 @@ class ApiUtils
83 } 84 }
84 85
85 /** 86 /**
86 * Convert a link given through a request, to a valid link for LinkDB. 87 * Convert a link given through a request, to a valid Bookmark for the datastore.
87 * 88 *
88 * If no URL is provided, it will generate a local note URL. 89 * If no URL is provided, it will generate a local note URL.
89 * If no title is provided, it will use the URL as title. 90 * If no title is provided, it will use the URL as title.
@@ -91,50 +92,42 @@ class ApiUtils
91 * @param array $input Request Link. 92 * @param array $input Request Link.
92 * @param bool $defaultPrivate Request Link. 93 * @param bool $defaultPrivate Request Link.
93 * 94 *
94 * @return array Formatted link. 95 * @return Bookmark instance.
95 */ 96 */
96 public static function buildLinkFromRequest($input, $defaultPrivate) 97 public static function buildLinkFromRequest($input, $defaultPrivate)
97 { 98 {
98 $input['url'] = ! empty($input['url']) ? cleanup_url($input['url']) : ''; 99 $bookmark = new Bookmark();
100 $url = ! empty($input['url']) ? cleanup_url($input['url']) : '';
99 if (isset($input['private'])) { 101 if (isset($input['private'])) {
100 $private = filter_var($input['private'], FILTER_VALIDATE_BOOLEAN); 102 $private = filter_var($input['private'], FILTER_VALIDATE_BOOLEAN);
101 } else { 103 } else {
102 $private = $defaultPrivate; 104 $private = $defaultPrivate;
103 } 105 }
104 106
105 $link = [ 107 $bookmark->setTitle(! empty($input['title']) ? $input['title'] : '');
106 'title' => ! empty($input['title']) ? $input['title'] : $input['url'], 108 $bookmark->setUrl($url);
107 'url' => $input['url'], 109 $bookmark->setDescription(! empty($input['description']) ? $input['description'] : '');
108 'description' => ! empty($input['description']) ? $input['description'] : '', 110 $bookmark->setTags(! empty($input['tags']) ? $input['tags'] : []);
109 'tags' => ! empty($input['tags']) ? implode(' ', $input['tags']) : '', 111 $bookmark->setPrivate($private);
110 'private' => $private, 112
111 'created' => new \DateTime(), 113 return $bookmark;
112 ];
113 return $link;
114 } 114 }
115 115
116 /** 116 /**
117 * Update link fields using an updated link object. 117 * Update link fields using an updated link object.
118 * 118 *
119 * @param array $oldLink data 119 * @param Bookmark $oldLink data
120 * @param array $newLink data 120 * @param Bookmark $newLink data
121 * 121 *
122 * @return array $oldLink updated with $newLink values 122 * @return Bookmark $oldLink updated with $newLink values
123 */ 123 */
124 public static function updateLink($oldLink, $newLink) 124 public static function updateLink($oldLink, $newLink)
125 { 125 {
126 foreach (['title', 'url', 'description', 'tags', 'private'] as $field) { 126 $oldLink->setTitle($newLink->getTitle());
127 $oldLink[$field] = $newLink[$field]; 127 $oldLink->setUrl($newLink->getUrl());
128 } 128 $oldLink->setDescription($newLink->getDescription());
129 $oldLink['updated'] = new \DateTime(); 129 $oldLink->setTags($newLink->getTags());
130 130 $oldLink->setPrivate($newLink->isPrivate());
131 if (empty($oldLink['url'])) {
132 $oldLink['url'] = '?' . $oldLink['shorturl'];
133 }
134
135 if (empty($oldLink['title'])) {
136 $oldLink['title'] = $oldLink['url'];
137 }
138 131
139 return $oldLink; 132 return $oldLink;
140 } 133 }
@@ -143,7 +136,7 @@ class ApiUtils
143 * Format a Tag for the REST API. 136 * Format a Tag for the REST API.
144 * 137 *
145 * @param string $tag Tag name 138 * @param string $tag Tag name
146 * @param int $occurrences Number of links using this tag 139 * @param int $occurrences Number of bookmarks using this tag
147 * 140 *
148 * @return array Link data formatted for the REST API. 141 * @return array Link data formatted for the REST API.
149 */ 142 */
diff --git a/application/api/controllers/ApiController.php b/application/api/controllers/ApiController.php
index a6e7cbab..c4b3d0c3 100644
--- a/application/api/controllers/ApiController.php
+++ b/application/api/controllers/ApiController.php
@@ -2,7 +2,7 @@
2 2
3namespace Shaarli\Api\Controllers; 3namespace Shaarli\Api\Controllers;
4 4
5use Shaarli\Bookmark\LinkDB; 5use Shaarli\Bookmark\BookmarkServiceInterface;
6use Shaarli\Config\ConfigManager; 6use Shaarli\Config\ConfigManager;
7use Slim\Container; 7use Slim\Container;
8 8
@@ -26,9 +26,9 @@ abstract class ApiController
26 protected $conf; 26 protected $conf;
27 27
28 /** 28 /**
29 * @var LinkDB 29 * @var BookmarkServiceInterface
30 */ 30 */
31 protected $linkDb; 31 protected $bookmarkService;
32 32
33 /** 33 /**
34 * @var HistoryController 34 * @var HistoryController
@@ -51,7 +51,7 @@ abstract class ApiController
51 { 51 {
52 $this->ci = $ci; 52 $this->ci = $ci;
53 $this->conf = $ci->get('conf'); 53 $this->conf = $ci->get('conf');
54 $this->linkDb = $ci->get('db'); 54 $this->bookmarkService = $ci->get('db');
55 $this->history = $ci->get('history'); 55 $this->history = $ci->get('history');
56 if ($this->conf->get('dev.debug', false)) { 56 if ($this->conf->get('dev.debug', false)) {
57 $this->jsonStyle = JSON_PRETTY_PRINT; 57 $this->jsonStyle = JSON_PRETTY_PRINT;
diff --git a/application/api/controllers/HistoryController.php b/application/api/controllers/HistoryController.php
index 9afcfa26..505647a9 100644
--- a/application/api/controllers/HistoryController.php
+++ b/application/api/controllers/HistoryController.php
@@ -41,7 +41,7 @@ class HistoryController extends ApiController
41 throw new ApiBadParametersException('Invalid offset'); 41 throw new ApiBadParametersException('Invalid offset');
42 } 42 }
43 43
44 // limit parameter is either a number of links or 'all' for everything. 44 // limit parameter is either a number of bookmarks or 'all' for everything.
45 $limit = $request->getParam('limit'); 45 $limit = $request->getParam('limit');
46 if (empty($limit)) { 46 if (empty($limit)) {
47 $limit = count($history); 47 $limit = count($history);
diff --git a/application/api/controllers/Info.php b/application/api/controllers/Info.php
index f37dcae5..12f6b2f0 100644
--- a/application/api/controllers/Info.php
+++ b/application/api/controllers/Info.php
@@ -2,6 +2,7 @@
2 2
3namespace Shaarli\Api\Controllers; 3namespace Shaarli\Api\Controllers;
4 4
5use Shaarli\Bookmark\BookmarkFilter;
5use Slim\Http\Request; 6use Slim\Http\Request;
6use Slim\Http\Response; 7use Slim\Http\Response;
7 8
@@ -26,8 +27,8 @@ class Info extends ApiController
26 public function getInfo($request, $response) 27 public function getInfo($request, $response)
27 { 28 {
28 $info = [ 29 $info = [
29 'global_counter' => count($this->linkDb), 30 'global_counter' => $this->bookmarkService->count(),
30 'private_counter' => count_private($this->linkDb), 31 'private_counter' => $this->bookmarkService->count(BookmarkFilter::$PRIVATE),
31 'settings' => array( 32 'settings' => array(
32 'title' => $this->conf->get('general.title', 'Shaarli'), 33 'title' => $this->conf->get('general.title', 'Shaarli'),
33 'header_link' => $this->conf->get('general.header_link', '?'), 34 'header_link' => $this->conf->get('general.header_link', '?'),
diff --git a/application/api/controllers/Links.php b/application/api/controllers/Links.php
index ffcfd4c7..29247950 100644
--- a/application/api/controllers/Links.php
+++ b/application/api/controllers/Links.php
@@ -11,7 +11,7 @@ use Slim\Http\Response;
11/** 11/**
12 * Class Links 12 * Class Links
13 * 13 *
14 * REST API Controller: all services related to links collection. 14 * REST API Controller: all services related to bookmarks collection.
15 * 15 *
16 * @package Api\Controllers 16 * @package Api\Controllers
17 * @see http://shaarli.github.io/api-documentation/#links-links-collection 17 * @see http://shaarli.github.io/api-documentation/#links-links-collection
@@ -19,12 +19,12 @@ use Slim\Http\Response;
19class Links extends ApiController 19class Links extends ApiController
20{ 20{
21 /** 21 /**
22 * @var int Number of links returned if no limit is provided. 22 * @var int Number of bookmarks returned if no limit is provided.
23 */ 23 */
24 public static $DEFAULT_LIMIT = 20; 24 public static $DEFAULT_LIMIT = 20;
25 25
26 /** 26 /**
27 * Retrieve a list of links, allowing different filters. 27 * Retrieve a list of bookmarks, allowing different filters.
28 * 28 *
29 * @param Request $request Slim request. 29 * @param Request $request Slim request.
30 * @param Response $response Slim response. 30 * @param Response $response Slim response.
@@ -36,33 +36,32 @@ class Links extends ApiController
36 public function getLinks($request, $response) 36 public function getLinks($request, $response)
37 { 37 {
38 $private = $request->getParam('visibility'); 38 $private = $request->getParam('visibility');
39 $links = $this->linkDb->filterSearch( 39 $bookmarks = $this->bookmarkService->search(
40 [ 40 [
41 'searchtags' => $request->getParam('searchtags', ''), 41 'searchtags' => $request->getParam('searchtags', ''),
42 'searchterm' => $request->getParam('searchterm', ''), 42 'searchterm' => $request->getParam('searchterm', ''),
43 ], 43 ],
44 false,
45 $private 44 $private
46 ); 45 );
47 46
48 // Return links from the {offset}th link, starting from 0. 47 // Return bookmarks from the {offset}th link, starting from 0.
49 $offset = $request->getParam('offset'); 48 $offset = $request->getParam('offset');
50 if (! empty($offset) && ! ctype_digit($offset)) { 49 if (! empty($offset) && ! ctype_digit($offset)) {
51 throw new ApiBadParametersException('Invalid offset'); 50 throw new ApiBadParametersException('Invalid offset');
52 } 51 }
53 $offset = ! empty($offset) ? intval($offset) : 0; 52 $offset = ! empty($offset) ? intval($offset) : 0;
54 if ($offset > count($links)) { 53 if ($offset > count($bookmarks)) {
55 return $response->withJson([], 200, $this->jsonStyle); 54 return $response->withJson([], 200, $this->jsonStyle);
56 } 55 }
57 56
58 // limit parameter is either a number of links or 'all' for everything. 57 // limit parameter is either a number of bookmarks or 'all' for everything.
59 $limit = $request->getParam('limit'); 58 $limit = $request->getParam('limit');
60 if (empty($limit)) { 59 if (empty($limit)) {
61 $limit = self::$DEFAULT_LIMIT; 60 $limit = self::$DEFAULT_LIMIT;
62 } elseif (ctype_digit($limit)) { 61 } elseif (ctype_digit($limit)) {
63 $limit = intval($limit); 62 $limit = intval($limit);
64 } elseif ($limit === 'all') { 63 } elseif ($limit === 'all') {
65 $limit = count($links); 64 $limit = count($bookmarks);
66 } else { 65 } else {
67 throw new ApiBadParametersException('Invalid limit'); 66 throw new ApiBadParametersException('Invalid limit');
68 } 67 }
@@ -72,12 +71,12 @@ class Links extends ApiController
72 71
73 $out = []; 72 $out = [];
74 $index = 0; 73 $index = 0;
75 foreach ($links as $link) { 74 foreach ($bookmarks as $bookmark) {
76 if (count($out) >= $limit) { 75 if (count($out) >= $limit) {
77 break; 76 break;
78 } 77 }
79 if ($index++ >= $offset) { 78 if ($index++ >= $offset) {
80 $out[] = ApiUtils::formatLink($link, $indexUrl); 79 $out[] = ApiUtils::formatLink($bookmark, $indexUrl);
81 } 80 }
82 } 81 }
83 82
@@ -97,11 +96,11 @@ class Links extends ApiController
97 */ 96 */
98 public function getLink($request, $response, $args) 97 public function getLink($request, $response, $args)
99 { 98 {
100 if (!isset($this->linkDb[$args['id']])) { 99 if (!$this->bookmarkService->exists($args['id'])) {
101 throw new ApiLinkNotFoundException(); 100 throw new ApiLinkNotFoundException();
102 } 101 }
103 $index = index_url($this->ci['environment']); 102 $index = index_url($this->ci['environment']);
104 $out = ApiUtils::formatLink($this->linkDb[$args['id']], $index); 103 $out = ApiUtils::formatLink($this->bookmarkService->get($args['id']), $index);
105 104
106 return $response->withJson($out, 200, $this->jsonStyle); 105 return $response->withJson($out, 200, $this->jsonStyle);
107 } 106 }
@@ -117,9 +116,11 @@ class Links extends ApiController
117 public function postLink($request, $response) 116 public function postLink($request, $response)
118 { 117 {
119 $data = $request->getParsedBody(); 118 $data = $request->getParsedBody();
120 $link = ApiUtils::buildLinkFromRequest($data, $this->conf->get('privacy.default_private_links')); 119 $bookmark = ApiUtils::buildLinkFromRequest($data, $this->conf->get('privacy.default_private_links'));
121 // duplicate by URL, return 409 Conflict 120 // duplicate by URL, return 409 Conflict
122 if (! empty($link['url']) && ! empty($dup = $this->linkDb->getLinkFromUrl($link['url']))) { 121 if (! empty($bookmark->getUrl())
122 && ! empty($dup = $this->bookmarkService->findByUrl($bookmark->getUrl()))
123 ) {
123 return $response->withJson( 124 return $response->withJson(
124 ApiUtils::formatLink($dup, index_url($this->ci['environment'])), 125 ApiUtils::formatLink($dup, index_url($this->ci['environment'])),
125 409, 126 409,
@@ -127,23 +128,9 @@ class Links extends ApiController
127 ); 128 );
128 } 129 }
129 130
130 $link['id'] = $this->linkDb->getNextId(); 131 $this->bookmarkService->add($bookmark);
131 $link['shorturl'] = link_small_hash($link['created'], $link['id']); 132 $out = ApiUtils::formatLink($bookmark, index_url($this->ci['environment']));
132 133 $redirect = $this->ci->router->relativePathFor('getLink', ['id' => $bookmark->getId()]);
133 // note: general relative URL
134 if (empty($link['url'])) {
135 $link['url'] = '?' . $link['shorturl'];
136 }
137
138 if (empty($link['title'])) {
139 $link['title'] = $link['url'];
140 }
141
142 $this->linkDb[$link['id']] = $link;
143 $this->linkDb->save($this->conf->get('resource.page_cache'));
144 $this->history->addLink($link);
145 $out = ApiUtils::formatLink($link, index_url($this->ci['environment']));
146 $redirect = $this->ci->router->relativePathFor('getLink', ['id' => $link['id']]);
147 return $response->withAddedHeader('Location', $redirect) 134 return $response->withAddedHeader('Location', $redirect)
148 ->withJson($out, 201, $this->jsonStyle); 135 ->withJson($out, 201, $this->jsonStyle);
149 } 136 }
@@ -161,18 +148,18 @@ class Links extends ApiController
161 */ 148 */
162 public function putLink($request, $response, $args) 149 public function putLink($request, $response, $args)
163 { 150 {
164 if (! isset($this->linkDb[$args['id']])) { 151 if (! $this->bookmarkService->exists($args['id'])) {
165 throw new ApiLinkNotFoundException(); 152 throw new ApiLinkNotFoundException();
166 } 153 }
167 154
168 $index = index_url($this->ci['environment']); 155 $index = index_url($this->ci['environment']);
169 $data = $request->getParsedBody(); 156 $data = $request->getParsedBody();
170 157
171 $requestLink = ApiUtils::buildLinkFromRequest($data, $this->conf->get('privacy.default_private_links')); 158 $requestBookmark = ApiUtils::buildLinkFromRequest($data, $this->conf->get('privacy.default_private_links'));
172 // duplicate URL on a different link, return 409 Conflict 159 // duplicate URL on a different link, return 409 Conflict
173 if (! empty($requestLink['url']) 160 if (! empty($requestBookmark->getUrl())
174 && ! empty($dup = $this->linkDb->getLinkFromUrl($requestLink['url'])) 161 && ! empty($dup = $this->bookmarkService->findByUrl($requestBookmark->getUrl()))
175 && $dup['id'] != $args['id'] 162 && $dup->getId() != $args['id']
176 ) { 163 ) {
177 return $response->withJson( 164 return $response->withJson(
178 ApiUtils::formatLink($dup, $index), 165 ApiUtils::formatLink($dup, $index),
@@ -181,13 +168,11 @@ class Links extends ApiController
181 ); 168 );
182 } 169 }
183 170
184 $responseLink = $this->linkDb[$args['id']]; 171 $responseBookmark = $this->bookmarkService->get($args['id']);
185 $responseLink = ApiUtils::updateLink($responseLink, $requestLink); 172 $responseBookmark = ApiUtils::updateLink($responseBookmark, $requestBookmark);
186 $this->linkDb[$responseLink['id']] = $responseLink; 173 $this->bookmarkService->set($responseBookmark);
187 $this->linkDb->save($this->conf->get('resource.page_cache'));
188 $this->history->updateLink($responseLink);
189 174
190 $out = ApiUtils::formatLink($responseLink, $index); 175 $out = ApiUtils::formatLink($responseBookmark, $index);
191 return $response->withJson($out, 200, $this->jsonStyle); 176 return $response->withJson($out, 200, $this->jsonStyle);
192 } 177 }
193 178
@@ -204,13 +189,11 @@ class Links extends ApiController
204 */ 189 */
205 public function deleteLink($request, $response, $args) 190 public function deleteLink($request, $response, $args)
206 { 191 {
207 if (! isset($this->linkDb[$args['id']])) { 192 if (! $this->bookmarkService->exists($args['id'])) {
208 throw new ApiLinkNotFoundException(); 193 throw new ApiLinkNotFoundException();
209 } 194 }
210 $link = $this->linkDb[$args['id']]; 195 $bookmark = $this->bookmarkService->get($args['id']);
211 unset($this->linkDb[(int) $args['id']]); 196 $this->bookmarkService->remove($bookmark);
212 $this->linkDb->save($this->conf->get('resource.page_cache'));
213 $this->history->deleteLink($link);
214 197
215 return $response->withStatus(204); 198 return $response->withStatus(204);
216 } 199 }
diff --git a/application/api/controllers/Tags.php b/application/api/controllers/Tags.php
index 82f3ef74..e60e00a7 100644
--- a/application/api/controllers/Tags.php
+++ b/application/api/controllers/Tags.php
@@ -5,6 +5,7 @@ namespace Shaarli\Api\Controllers;
5use Shaarli\Api\ApiUtils; 5use Shaarli\Api\ApiUtils;
6use Shaarli\Api\Exceptions\ApiBadParametersException; 6use Shaarli\Api\Exceptions\ApiBadParametersException;
7use Shaarli\Api\Exceptions\ApiTagNotFoundException; 7use Shaarli\Api\Exceptions\ApiTagNotFoundException;
8use Shaarli\Bookmark\BookmarkFilter;
8use Slim\Http\Request; 9use Slim\Http\Request;
9use Slim\Http\Response; 10use Slim\Http\Response;
10 11
@@ -18,7 +19,7 @@ use Slim\Http\Response;
18class Tags extends ApiController 19class Tags extends ApiController
19{ 20{
20 /** 21 /**
21 * @var int Number of links returned if no limit is provided. 22 * @var int Number of bookmarks returned if no limit is provided.
22 */ 23 */
23 public static $DEFAULT_LIMIT = 'all'; 24 public static $DEFAULT_LIMIT = 'all';
24 25
@@ -35,7 +36,7 @@ class Tags extends ApiController
35 public function getTags($request, $response) 36 public function getTags($request, $response)
36 { 37 {
37 $visibility = $request->getParam('visibility'); 38 $visibility = $request->getParam('visibility');
38 $tags = $this->linkDb->linksCountPerTag([], $visibility); 39 $tags = $this->bookmarkService->bookmarksCountPerTag([], $visibility);
39 40
40 // Return tags from the {offset}th tag, starting from 0. 41 // Return tags from the {offset}th tag, starting from 0.
41 $offset = $request->getParam('offset'); 42 $offset = $request->getParam('offset');
@@ -47,7 +48,7 @@ class Tags extends ApiController
47 return $response->withJson([], 200, $this->jsonStyle); 48 return $response->withJson([], 200, $this->jsonStyle);
48 } 49 }
49 50
50 // limit parameter is either a number of links or 'all' for everything. 51 // limit parameter is either a number of bookmarks or 'all' for everything.
51 $limit = $request->getParam('limit'); 52 $limit = $request->getParam('limit');
52 if (empty($limit)) { 53 if (empty($limit)) {
53 $limit = self::$DEFAULT_LIMIT; 54 $limit = self::$DEFAULT_LIMIT;
@@ -87,7 +88,7 @@ class Tags extends ApiController
87 */ 88 */
88 public function getTag($request, $response, $args) 89 public function getTag($request, $response, $args)
89 { 90 {
90 $tags = $this->linkDb->linksCountPerTag(); 91 $tags = $this->bookmarkService->bookmarksCountPerTag();
91 if (!isset($tags[$args['tagName']])) { 92 if (!isset($tags[$args['tagName']])) {
92 throw new ApiTagNotFoundException(); 93 throw new ApiTagNotFoundException();
93 } 94 }
@@ -111,7 +112,7 @@ class Tags extends ApiController
111 */ 112 */
112 public function putTag($request, $response, $args) 113 public function putTag($request, $response, $args)
113 { 114 {
114 $tags = $this->linkDb->linksCountPerTag(); 115 $tags = $this->bookmarkService->bookmarksCountPerTag();
115 if (! isset($tags[$args['tagName']])) { 116 if (! isset($tags[$args['tagName']])) {
116 throw new ApiTagNotFoundException(); 117 throw new ApiTagNotFoundException();
117 } 118 }
@@ -121,13 +122,19 @@ class Tags extends ApiController
121 throw new ApiBadParametersException('New tag name is required in the request body'); 122 throw new ApiBadParametersException('New tag name is required in the request body');
122 } 123 }
123 124
124 $updated = $this->linkDb->renameTag($args['tagName'], $data['name']); 125 $bookmarks = $this->bookmarkService->search(
125 $this->linkDb->save($this->conf->get('resource.page_cache')); 126 ['searchtags' => $args['tagName']],
126 foreach ($updated as $link) { 127 BookmarkFilter::$ALL,
127 $this->history->updateLink($link); 128 true
129 );
130 foreach ($bookmarks as $bookmark) {
131 $bookmark->renameTag($args['tagName'], $data['name']);
132 $this->bookmarkService->set($bookmark, false);
133 $this->history->updateLink($bookmark);
128 } 134 }
135 $this->bookmarkService->save();
129 136
130 $tags = $this->linkDb->linksCountPerTag(); 137 $tags = $this->bookmarkService->bookmarksCountPerTag();
131 $out = ApiUtils::formatTag($data['name'], $tags[$data['name']]); 138 $out = ApiUtils::formatTag($data['name'], $tags[$data['name']]);
132 return $response->withJson($out, 200, $this->jsonStyle); 139 return $response->withJson($out, 200, $this->jsonStyle);
133 } 140 }
@@ -145,15 +152,22 @@ class Tags extends ApiController
145 */ 152 */
146 public function deleteTag($request, $response, $args) 153 public function deleteTag($request, $response, $args)
147 { 154 {
148 $tags = $this->linkDb->linksCountPerTag(); 155 $tags = $this->bookmarkService->bookmarksCountPerTag();
149 if (! isset($tags[$args['tagName']])) { 156 if (! isset($tags[$args['tagName']])) {
150 throw new ApiTagNotFoundException(); 157 throw new ApiTagNotFoundException();
151 } 158 }
152 $updated = $this->linkDb->renameTag($args['tagName'], null); 159
153 $this->linkDb->save($this->conf->get('resource.page_cache')); 160 $bookmarks = $this->bookmarkService->search(
154 foreach ($updated as $link) { 161 ['searchtags' => $args['tagName']],
155 $this->history->updateLink($link); 162 BookmarkFilter::$ALL,
163 true
164 );
165 foreach ($bookmarks as $bookmark) {
166 $bookmark->deleteTag($args['tagName']);
167 $this->bookmarkService->set($bookmark, false);
168 $this->history->updateLink($bookmark);
156 } 169 }
170 $this->bookmarkService->save();
157 171
158 return $response->withStatus(204); 172 return $response->withStatus(204);
159 } 173 }
diff --git a/application/bookmark/BookmarkArray.php b/application/bookmark/BookmarkArray.php
index b427c91a..d87d43b4 100644
--- a/application/bookmark/BookmarkArray.php
+++ b/application/bookmark/BookmarkArray.php
@@ -118,7 +118,7 @@ class BookmarkArray implements \Iterator, \Countable, \ArrayAccess
118 $realOffset = $this->getBookmarkOffset($offset); 118 $realOffset = $this->getBookmarkOffset($offset);
119 $url = $this->bookmarks[$realOffset]->getUrl(); 119 $url = $this->bookmarks[$realOffset]->getUrl();
120 unset($this->urls[$url]); 120 unset($this->urls[$url]);
121 unset($this->ids[$realOffset]); 121 unset($this->ids[$offset]);
122 unset($this->bookmarks[$realOffset]); 122 unset($this->bookmarks[$realOffset]);
123 } 123 }
124 124
diff --git a/application/config/ConfigManager.php b/application/config/ConfigManager.php
index c95e6800..e45bb4c3 100644
--- a/application/config/ConfigManager.php
+++ b/application/config/ConfigManager.php
@@ -389,6 +389,8 @@ class ConfigManager
389 $this->setEmpty('translation.extensions', []); 389 $this->setEmpty('translation.extensions', []);
390 390
391 $this->setEmpty('plugins', array()); 391 $this->setEmpty('plugins', array());
392
393 $this->setEmpty('formatter', 'markdown');
392 } 394 }
393 395
394 /** 396 /**
diff --git a/application/feed/FeedBuilder.php b/application/feed/FeedBuilder.php
index 957c8273..40bd4f15 100644
--- a/application/feed/FeedBuilder.php
+++ b/application/feed/FeedBuilder.php
@@ -2,6 +2,9 @@
2namespace Shaarli\Feed; 2namespace Shaarli\Feed;
3 3
4use DateTime; 4use DateTime;
5use Shaarli\Bookmark\Bookmark;
6use Shaarli\Bookmark\BookmarkServiceInterface;
7use Shaarli\Formatter\BookmarkFormatter;
5 8
6/** 9/**
7 * FeedBuilder class. 10 * FeedBuilder class.
@@ -26,16 +29,21 @@ class FeedBuilder
26 public static $DEFAULT_LANGUAGE = 'en-en'; 29 public static $DEFAULT_LANGUAGE = 'en-en';
27 30
28 /** 31 /**
29 * @var int Number of links to display in a feed by default. 32 * @var int Number of bookmarks to display in a feed by default.
30 */ 33 */
31 public static $DEFAULT_NB_LINKS = 50; 34 public static $DEFAULT_NB_LINKS = 50;
32 35
33 /** 36 /**
34 * @var \Shaarli\Bookmark\LinkDB instance. 37 * @var BookmarkServiceInterface instance.
35 */ 38 */
36 protected $linkDB; 39 protected $linkDB;
37 40
38 /** 41 /**
42 * @var BookmarkFormatter instance.
43 */
44 protected $formatter;
45
46 /**
39 * @var string RSS or ATOM feed. 47 * @var string RSS or ATOM feed.
40 */ 48 */
41 protected $feedType; 49 protected $feedType;
@@ -56,7 +64,7 @@ class FeedBuilder
56 protected $isLoggedIn; 64 protected $isLoggedIn;
57 65
58 /** 66 /**
59 * @var boolean Use permalinks instead of direct links if true. 67 * @var boolean Use permalinks instead of direct bookmarks if true.
60 */ 68 */
61 protected $usePermalinks; 69 protected $usePermalinks;
62 70
@@ -78,16 +86,17 @@ class FeedBuilder
78 /** 86 /**
79 * Feed constructor. 87 * Feed constructor.
80 * 88 *
81 * @param \Shaarli\Bookmark\LinkDB $linkDB LinkDB instance. 89 * @param BookmarkServiceInterface $linkDB LinkDB instance.
90 * @param BookmarkFormatter $formatter instance.
82 * @param string $feedType Type of feed. 91 * @param string $feedType Type of feed.
83 * @param array $serverInfo $_SERVER. 92 * @param array $serverInfo $_SERVER.
84 * @param array $userInput $_GET. 93 * @param array $userInput $_GET.
85 * @param boolean $isLoggedIn True if the user is currently logged in, 94 * @param boolean $isLoggedIn True if the user is currently logged in, false otherwise.
86 * false otherwise.
87 */ 95 */
88 public function __construct($linkDB, $feedType, $serverInfo, $userInput, $isLoggedIn) 96 public function __construct($linkDB, $formatter, $feedType, $serverInfo, $userInput, $isLoggedIn)
89 { 97 {
90 $this->linkDB = $linkDB; 98 $this->linkDB = $linkDB;
99 $this->formatter = $formatter;
91 $this->feedType = $feedType; 100 $this->feedType = $feedType;
92 $this->serverInfo = $serverInfo; 101 $this->serverInfo = $serverInfo;
93 $this->userInput = $userInput; 102 $this->userInput = $userInput;
@@ -101,13 +110,13 @@ class FeedBuilder
101 */ 110 */
102 public function buildData() 111 public function buildData()
103 { 112 {
104 // Search for untagged links 113 // Search for untagged bookmarks
105 if (isset($this->userInput['searchtags']) && empty($this->userInput['searchtags'])) { 114 if (isset($this->userInput['searchtags']) && empty($this->userInput['searchtags'])) {
106 $this->userInput['searchtags'] = false; 115 $this->userInput['searchtags'] = false;
107 } 116 }
108 117
109 // Optionally filter the results: 118 // Optionally filter the results:
110 $linksToDisplay = $this->linkDB->filterSearch($this->userInput); 119 $linksToDisplay = $this->linkDB->search($this->userInput);
111 120
112 $nblinksToDisplay = $this->getNbLinks(count($linksToDisplay)); 121 $nblinksToDisplay = $this->getNbLinks(count($linksToDisplay));
113 122
@@ -118,6 +127,7 @@ class FeedBuilder
118 } 127 }
119 128
120 $pageaddr = escape(index_url($this->serverInfo)); 129 $pageaddr = escape(index_url($this->serverInfo));
130 $this->formatter->addContextData('index_url', $pageaddr);
121 $linkDisplayed = array(); 131 $linkDisplayed = array();
122 for ($i = 0; $i < $nblinksToDisplay && $i < count($keys); $i++) { 132 for ($i = 0; $i < $nblinksToDisplay && $i < count($keys); $i++) {
123 $linkDisplayed[$keys[$i]] = $this->buildItem($linksToDisplay[$keys[$i]], $pageaddr); 133 $linkDisplayed[$keys[$i]] = $this->buildItem($linksToDisplay[$keys[$i]], $pageaddr);
@@ -139,54 +149,44 @@ class FeedBuilder
139 /** 149 /**
140 * Build a feed item (one per shaare). 150 * Build a feed item (one per shaare).
141 * 151 *
142 * @param array $link Single link array extracted from LinkDB. 152 * @param Bookmark $link Single link array extracted from LinkDB.
143 * @param string $pageaddr Index URL. 153 * @param string $pageaddr Index URL.
144 * 154 *
145 * @return array Link array with feed attributes. 155 * @return array Link array with feed attributes.
146 */ 156 */
147 protected function buildItem($link, $pageaddr) 157 protected function buildItem($link, $pageaddr)
148 { 158 {
149 $link['guid'] = $pageaddr . '?' . $link['shorturl']; 159 $data = $this->formatter->format($link);
150 // Prepend the root URL for notes 160 $data['guid'] = $pageaddr . '?' . $data['shorturl'];
151 if (is_note($link['url'])) {
152 $link['url'] = $pageaddr . $link['url'];
153 }
154 if ($this->usePermalinks === true) { 161 if ($this->usePermalinks === true) {
155 $permalink = '<a href="' . $link['url'] . '" title="' . t('Direct link') . '">' . t('Direct link') . '</a>'; 162 $permalink = '<a href="'. $data['url'] .'" title="'. t('Direct link') .'">'. t('Direct link') .'</a>';
156 } else { 163 } else {
157 $permalink = '<a href="' . $link['guid'] . '" title="' . t('Permalink') . '">' . t('Permalink') . '</a>'; 164 $permalink = '<a href="'. $data['guid'] .'" title="'. t('Permalink') .'">'. t('Permalink') .'</a>';
158 } 165 }
159 $link['description'] = format_description($link['description'], $pageaddr); 166 $data['description'] .= PHP_EOL . PHP_EOL . '<br>&#8212; ' . $permalink;
160 $link['description'] .= PHP_EOL . PHP_EOL . '<br>&#8212; ' . $permalink;
161 167
162 $pubDate = $link['created']; 168 $data['pub_iso_date'] = $this->getIsoDate($data['created']);
163 $link['pub_iso_date'] = $this->getIsoDate($pubDate);
164 169
165 // atom:entry elements MUST contain exactly one atom:updated element. 170 // atom:entry elements MUST contain exactly one atom:updated element.
166 if (!empty($link['updated'])) { 171 if (!empty($link->getUpdated())) {
167 $upDate = $link['updated']; 172 $data['up_iso_date'] = $this->getIsoDate($data['updated'], DateTime::ATOM);
168 $link['up_iso_date'] = $this->getIsoDate($upDate, DateTime::ATOM);
169 } else { 173 } else {
170 $link['up_iso_date'] = $this->getIsoDate($pubDate, DateTime::ATOM); 174 $data['up_iso_date'] = $this->getIsoDate($data['created'], DateTime::ATOM);
171 } 175 }
172 176
173 // Save the more recent item. 177 // Save the more recent item.
174 if (empty($this->latestDate) || $this->latestDate < $pubDate) { 178 if (empty($this->latestDate) || $this->latestDate < $data['created']) {
175 $this->latestDate = $pubDate; 179 $this->latestDate = $data['created'];
176 } 180 }
177 if (!empty($upDate) && $this->latestDate < $upDate) { 181 if (!empty($data['updated']) && $this->latestDate < $data['updated']) {
178 $this->latestDate = $upDate; 182 $this->latestDate = $data['updated'];
179 } 183 }
180 184
181 $taglist = array_filter(explode(' ', $link['tags']), 'strlen'); 185 return $data;
182 uasort($taglist, 'strcasecmp');
183 $link['taglist'] = $taglist;
184
185 return $link;
186 } 186 }
187 187
188 /** 188 /**
189 * Set this to true to use permalinks instead of direct links. 189 * Set this to true to use permalinks instead of direct bookmarks.
190 * 190 *
191 * @param boolean $usePermalinks true to force permalinks. 191 * @param boolean $usePermalinks true to force permalinks.
192 */ 192 */
@@ -273,11 +273,11 @@ class FeedBuilder
273 * Returns the number of link to display according to 'nb' user input parameter. 273 * Returns the number of link to display according to 'nb' user input parameter.
274 * 274 *
275 * If 'nb' not set or invalid, default value: $DEFAULT_NB_LINKS. 275 * If 'nb' not set or invalid, default value: $DEFAULT_NB_LINKS.
276 * If 'nb' is set to 'all', display all filtered links (max parameter). 276 * If 'nb' is set to 'all', display all filtered bookmarks (max parameter).
277 * 277 *
278 * @param int $max maximum number of links to display. 278 * @param int $max maximum number of bookmarks to display.
279 * 279 *
280 * @return int number of links to display. 280 * @return int number of bookmarks to display.
281 */ 281 */
282 public function getNbLinks($max) 282 public function getNbLinks($max)
283 { 283 {
diff --git a/application/formatter/BookmarkMarkdownFormatter.php b/application/formatter/BookmarkMarkdownFormatter.php
index f60c61f4..7797bfbf 100644
--- a/application/formatter/BookmarkMarkdownFormatter.php
+++ b/application/formatter/BookmarkMarkdownFormatter.php
@@ -57,6 +57,7 @@ class BookmarkMarkdownFormatter extends BookmarkDefaultFormatter
57 $processedDescription = $bookmark->getDescription(); 57 $processedDescription = $bookmark->getDescription();
58 $processedDescription = $this->filterProtocols($processedDescription); 58 $processedDescription = $this->filterProtocols($processedDescription);
59 $processedDescription = $this->formatHashTags($processedDescription); 59 $processedDescription = $this->formatHashTags($processedDescription);
60 $processedDescription = $this->reverseEscapedHtml($processedDescription);
60 $processedDescription = $this->parsedown 61 $processedDescription = $this->parsedown
61 ->setMarkupEscaped($this->escape) 62 ->setMarkupEscaped($this->escape)
62 ->setBreaksEnabled(true) 63 ->setBreaksEnabled(true)
@@ -195,4 +196,9 @@ class BookmarkMarkdownFormatter extends BookmarkDefaultFormatter
195 ); 196 );
196 return $description; 197 return $description;
197 } 198 }
199
200 protected function reverseEscapedHtml($description)
201 {
202 return unescape($description);
203 }
198} 204}
diff --git a/application/netscape/NetscapeBookmarkUtils.php b/application/netscape/NetscapeBookmarkUtils.php
index 28665941..d64eef7f 100644
--- a/application/netscape/NetscapeBookmarkUtils.php
+++ b/application/netscape/NetscapeBookmarkUtils.php
@@ -7,8 +7,10 @@ use DateTimeZone;
7use Exception; 7use Exception;
8use Katzgrau\KLogger\Logger; 8use Katzgrau\KLogger\Logger;
9use Psr\Log\LogLevel; 9use Psr\Log\LogLevel;
10use Shaarli\Bookmark\LinkDB; 10use Shaarli\Bookmark\Bookmark;
11use Shaarli\Bookmark\BookmarkServiceInterface;
11use Shaarli\Config\ConfigManager; 12use Shaarli\Config\ConfigManager;
13use Shaarli\Formatter\BookmarkFormatter;
12use Shaarli\History; 14use Shaarli\History;
13use Shaarli\NetscapeBookmarkParser\NetscapeBookmarkParser; 15use Shaarli\NetscapeBookmarkParser\NetscapeBookmarkParser;
14 16
@@ -20,41 +22,39 @@ class NetscapeBookmarkUtils
20{ 22{
21 23
22 /** 24 /**
23 * Filters links and adds Netscape-formatted fields 25 * Filters bookmarks and adds Netscape-formatted fields
24 * 26 *
25 * Added fields: 27 * Added fields:
26 * - timestamp link addition date, using the Unix epoch format 28 * - timestamp link addition date, using the Unix epoch format
27 * - taglist comma-separated tag list 29 * - taglist comma-separated tag list
28 * 30 *
29 * @param LinkDB $linkDb Link datastore 31 * @param BookmarkServiceInterface $bookmarkService Link datastore
30 * @param string $selection Which links to export: (all|private|public) 32 * @param BookmarkFormatter $formatter instance
31 * @param bool $prependNoteUrl Prepend note permalinks with the server's URL 33 * @param string $selection Which bookmarks to export: (all|private|public)
32 * @param string $indexUrl Absolute URL of the Shaarli index page 34 * @param bool $prependNoteUrl Prepend note permalinks with the server's URL
35 * @param string $indexUrl Absolute URL of the Shaarli index page
33 * 36 *
34 * @throws Exception Invalid export selection 37 * @return array The bookmarks to be exported, with additional fields
38 *@throws Exception Invalid export selection
35 * 39 *
36 * @return array The links to be exported, with additional fields
37 */ 40 */
38 public static function filterAndFormat($linkDb, $selection, $prependNoteUrl, $indexUrl) 41 public static function filterAndFormat(
39 { 42 $bookmarkService,
43 $formatter,
44 $selection,
45 $prependNoteUrl,
46 $indexUrl
47 ) {
40 // see tpl/export.html for possible values 48 // see tpl/export.html for possible values
41 if (!in_array($selection, array('all', 'public', 'private'))) { 49 if (!in_array($selection, array('all', 'public', 'private'))) {
42 throw new Exception(t('Invalid export selection:') . ' "' . $selection . '"'); 50 throw new Exception(t('Invalid export selection:') . ' "' . $selection . '"');
43 } 51 }
44 52
45 $bookmarkLinks = array(); 53 $bookmarkLinks = array();
46 foreach ($linkDb as $link) { 54 foreach ($bookmarkService->search([], $selection) as $bookmark) {
47 if ($link['private'] != 0 && $selection == 'public') { 55 $link = $formatter->format($bookmark);
48 continue; 56 $link['taglist'] = implode(',', $bookmark->getTags());
49 } 57 if ($bookmark->isNote() && $prependNoteUrl) {
50 if ($link['private'] == 0 && $selection == 'private') {
51 continue;
52 }
53 $date = $link['created'];
54 $link['timestamp'] = $date->getTimestamp();
55 $link['taglist'] = str_replace(' ', ',', $link['tags']);
56
57 if (is_note($link['url']) && $prependNoteUrl) {
58 $link['url'] = $indexUrl . $link['url']; 58 $link['url'] = $indexUrl . $link['url'];
59 } 59 }
60 60
@@ -69,9 +69,9 @@ class NetscapeBookmarkUtils
69 * 69 *
70 * @param string $filename name of the file to import 70 * @param string $filename name of the file to import
71 * @param int $filesize size of the file to import 71 * @param int $filesize size of the file to import
72 * @param int $importCount how many links were imported 72 * @param int $importCount how many bookmarks were imported
73 * @param int $overwriteCount how many links were overwritten 73 * @param int $overwriteCount how many bookmarks were overwritten
74 * @param int $skipCount how many links were skipped 74 * @param int $skipCount how many bookmarks were skipped
75 * @param int $duration how many seconds did the import take 75 * @param int $duration how many seconds did the import take
76 * 76 *
77 * @return string Summary of the bookmark import status 77 * @return string Summary of the bookmark import status
@@ -91,7 +91,7 @@ class NetscapeBookmarkUtils
91 $status .= vsprintf( 91 $status .= vsprintf(
92 t( 92 t(
93 'was successfully processed in %d seconds: ' 93 'was successfully processed in %d seconds: '
94 . '%d links imported, %d links overwritten, %d links skipped.' 94 . '%d bookmarks imported, %d bookmarks overwritten, %d bookmarks skipped.'
95 ), 95 ),
96 [$duration, $importCount, $overwriteCount, $skipCount] 96 [$duration, $importCount, $overwriteCount, $skipCount]
97 ); 97 );
@@ -102,15 +102,15 @@ class NetscapeBookmarkUtils
102 /** 102 /**
103 * Imports Web bookmarks from an uploaded Netscape bookmark dump 103 * Imports Web bookmarks from an uploaded Netscape bookmark dump
104 * 104 *
105 * @param array $post Server $_POST parameters 105 * @param array $post Server $_POST parameters
106 * @param array $files Server $_FILES parameters 106 * @param array $files Server $_FILES parameters
107 * @param LinkDB $linkDb Loaded LinkDB instance 107 * @param BookmarkServiceInterface $bookmarkService Loaded LinkDB instance
108 * @param ConfigManager $conf instance 108 * @param ConfigManager $conf instance
109 * @param History $history History instance 109 * @param History $history History instance
110 * 110 *
111 * @return string Summary of the bookmark import status 111 * @return string Summary of the bookmark import status
112 */ 112 */
113 public static function import($post, $files, $linkDb, $conf, $history) 113 public static function import($post, $files, $bookmarkService, $conf, $history)
114 { 114 {
115 $start = time(); 115 $start = time();
116 $filename = $files['filetoupload']['name']; 116 $filename = $files['filetoupload']['name'];
@@ -121,10 +121,10 @@ class NetscapeBookmarkUtils
121 return self::importStatus($filename, $filesize); 121 return self::importStatus($filename, $filesize);
122 } 122 }
123 123
124 // Overwrite existing links? 124 // Overwrite existing bookmarks?
125 $overwrite = !empty($post['overwrite']); 125 $overwrite = !empty($post['overwrite']);
126 126
127 // Add tags to all imported links? 127 // Add tags to all imported bookmarks?
128 if (empty($post['default_tags'])) { 128 if (empty($post['default_tags'])) {
129 $defaultTags = array(); 129 $defaultTags = array();
130 } else { 130 } else {
@@ -134,7 +134,7 @@ class NetscapeBookmarkUtils
134 ); 134 );
135 } 135 }
136 136
137 // links are imported as public by default 137 // bookmarks are imported as public by default
138 $defaultPrivacy = 0; 138 $defaultPrivacy = 0;
139 139
140 $parser = new NetscapeBookmarkParser( 140 $parser = new NetscapeBookmarkParser(
@@ -164,22 +164,18 @@ class NetscapeBookmarkUtils
164 // use value from the imported file 164 // use value from the imported file
165 $private = $bkm['pub'] == '1' ? 0 : 1; 165 $private = $bkm['pub'] == '1' ? 0 : 1;
166 } elseif ($post['privacy'] == 'private') { 166 } elseif ($post['privacy'] == 'private') {
167 // all imported links are private 167 // all imported bookmarks are private
168 $private = 1; 168 $private = 1;
169 } elseif ($post['privacy'] == 'public') { 169 } elseif ($post['privacy'] == 'public') {
170 // all imported links are public 170 // all imported bookmarks are public
171 $private = 0; 171 $private = 0;
172 } 172 }
173 173
174 $newLink = array( 174 $link = $bookmarkService->findByUrl($bkm['uri']);
175 'title' => $bkm['title'], 175 $existingLink = $link !== null;
176 'url' => $bkm['uri'], 176 if (! $existingLink) {
177 'description' => $bkm['note'], 177 $link = new Bookmark();
178 'private' => $private, 178 }
179 'tags' => $bkm['tags']
180 );
181
182 $existingLink = $linkDb->getLinkFromUrl($bkm['uri']);
183 179
184 if ($existingLink !== false) { 180 if ($existingLink !== false) {
185 if ($overwrite === false) { 181 if ($overwrite === false) {
@@ -188,28 +184,25 @@ class NetscapeBookmarkUtils
188 continue; 184 continue;
189 } 185 }
190 186
191 // Overwrite an existing link, keep its date 187 $link->setUpdated(new DateTime());
192 $newLink['id'] = $existingLink['id'];
193 $newLink['created'] = $existingLink['created'];
194 $newLink['updated'] = new DateTime();
195 $newLink['shorturl'] = $existingLink['shorturl'];
196 $linkDb[$existingLink['id']] = $newLink;
197 $importCount++;
198 $overwriteCount++; 188 $overwriteCount++;
199 continue; 189 } else {
190 $newLinkDate = new DateTime('@' . strval($bkm['time']));
191 $newLinkDate->setTimezone(new DateTimeZone(date_default_timezone_get()));
192 $link->setCreated($newLinkDate);
200 } 193 }
201 194
202 // Add a new link - @ used for UNIX timestamps 195 $link->setTitle($bkm['title']);
203 $newLinkDate = new DateTime('@' . strval($bkm['time'])); 196 $link->setUrl($bkm['uri'], $conf->get('security.allowed_protocols'));
204 $newLinkDate->setTimezone(new DateTimeZone(date_default_timezone_get())); 197 $link->setDescription($bkm['note']);
205 $newLink['created'] = $newLinkDate; 198 $link->setPrivate($private);
206 $newLink['id'] = $linkDb->getNextId(); 199 $link->setTagsString($bkm['tags']);
207 $newLink['shorturl'] = link_small_hash($newLink['created'], $newLink['id']); 200
208 $linkDb[$newLink['id']] = $newLink; 201 $bookmarkService->addOrSet($link, false);
209 $importCount++; 202 $importCount++;
210 } 203 }
211 204
212 $linkDb->save($conf->get('resource.page_cache')); 205 $bookmarkService->save();
213 $history->importLinks(); 206 $history->importLinks();
214 207
215 $duration = time() - $start; 208 $duration = time() - $start;
diff --git a/application/render/PageBuilder.php b/application/render/PageBuilder.php
index 3f86fc26..65e85aaf 100644
--- a/application/render/PageBuilder.php
+++ b/application/render/PageBuilder.php
@@ -5,7 +5,7 @@ namespace Shaarli\Render;
5use Exception; 5use Exception;
6use RainTPL; 6use RainTPL;
7use Shaarli\ApplicationUtils; 7use Shaarli\ApplicationUtils;
8use Shaarli\Bookmark\LinkDB; 8use Shaarli\Bookmark\BookmarkServiceInterface;
9use Shaarli\Config\ConfigManager; 9use Shaarli\Config\ConfigManager;
10use Shaarli\Thumbnailer; 10use Shaarli\Thumbnailer;
11 11
@@ -34,9 +34,9 @@ class PageBuilder
34 protected $session; 34 protected $session;
35 35
36 /** 36 /**
37 * @var LinkDB $linkDB instance. 37 * @var BookmarkServiceInterface $bookmarkService instance.
38 */ 38 */
39 protected $linkDB; 39 protected $bookmarkService;
40 40
41 /** 41 /**
42 * @var null|string XSRF token 42 * @var null|string XSRF token
@@ -52,18 +52,18 @@ class PageBuilder
52 * PageBuilder constructor. 52 * PageBuilder constructor.
53 * $tpl is initialized at false for lazy loading. 53 * $tpl is initialized at false for lazy loading.
54 * 54 *
55 * @param ConfigManager $conf Configuration Manager instance (reference). 55 * @param ConfigManager $conf Configuration Manager instance (reference).
56 * @param array $session $_SESSION array 56 * @param array $session $_SESSION array
57 * @param LinkDB $linkDB instance. 57 * @param BookmarkServiceInterface $linkDB instance.
58 * @param string $token Session token 58 * @param string $token Session token
59 * @param bool $isLoggedIn 59 * @param bool $isLoggedIn
60 */ 60 */
61 public function __construct(&$conf, $session, $linkDB = null, $token = null, $isLoggedIn = false) 61 public function __construct(&$conf, $session, $linkDB = null, $token = null, $isLoggedIn = false)
62 { 62 {
63 $this->tpl = false; 63 $this->tpl = false;
64 $this->conf = $conf; 64 $this->conf = $conf;
65 $this->session = $session; 65 $this->session = $session;
66 $this->linkDB = $linkDB; 66 $this->bookmarkService = $linkDB;
67 $this->token = $token; 67 $this->token = $token;
68 $this->isLoggedIn = $isLoggedIn; 68 $this->isLoggedIn = $isLoggedIn;
69 } 69 }
@@ -125,8 +125,8 @@ class PageBuilder
125 125
126 $this->tpl->assign('language', $this->conf->get('translation.language')); 126 $this->tpl->assign('language', $this->conf->get('translation.language'));
127 127
128 if ($this->linkDB !== null) { 128 if ($this->bookmarkService !== null) {
129 $this->tpl->assign('tags', $this->linkDB->linksCountPerTag()); 129 $this->tpl->assign('tags', $this->bookmarkService->bookmarksCountPerTag());
130 } 130 }
131 131
132 $this->tpl->assign( 132 $this->tpl->assign(
@@ -141,6 +141,8 @@ class PageBuilder
141 unset($_SESSION['warnings']); 141 unset($_SESSION['warnings']);
142 } 142 }
143 143
144 $this->tpl->assign('formatter', $this->conf->get('formatter', 'default'));
145
144 // To be removed with a proper theme configuration. 146 // To be removed with a proper theme configuration.
145 $this->tpl->assign('conf', $this->conf); 147 $this->tpl->assign('conf', $this->conf);
146 } 148 }
diff --git a/application/updater/Updater.php b/application/updater/Updater.php
index beb9ea9b..95654d81 100644
--- a/application/updater/Updater.php
+++ b/application/updater/Updater.php
@@ -2,25 +2,14 @@
2 2
3namespace Shaarli\Updater; 3namespace Shaarli\Updater;
4 4
5use Exception;
6use RainTPL;
7use ReflectionClass;
8use ReflectionException;
9use ReflectionMethod;
10use Shaarli\ApplicationUtils;
11use Shaarli\Bookmark\LinkDB;
12use Shaarli\Bookmark\LinkFilter;
13use Shaarli\Config\ConfigJson;
14use Shaarli\Config\ConfigManager; 5use Shaarli\Config\ConfigManager;
15use Shaarli\Config\ConfigPhp; 6use Shaarli\Bookmark\BookmarkServiceInterface;
16use Shaarli\Exceptions\IOException;
17use Shaarli\Thumbnailer;
18use Shaarli\Updater\Exception\UpdaterException; 7use Shaarli\Updater\Exception\UpdaterException;
19 8
20/** 9/**
21 * Class updater. 10 * Class Updater.
22 * Used to update stuff when a new Shaarli's version is reached. 11 * Used to update stuff when a new Shaarli's version is reached.
23 * Update methods are ran only once, and the stored in a JSON file. 12 * Update methods are ran only once, and the stored in a TXT file.
24 */ 13 */
25class Updater 14class Updater
26{ 15{
@@ -30,9 +19,9 @@ class Updater
30 protected $doneUpdates; 19 protected $doneUpdates;
31 20
32 /** 21 /**
33 * @var LinkDB instance. 22 * @var BookmarkServiceInterface instance.
34 */ 23 */
35 protected $linkDB; 24 protected $linkServices;
36 25
37 /** 26 /**
38 * @var ConfigManager $conf Configuration Manager instance. 27 * @var ConfigManager $conf Configuration Manager instance.
@@ -45,36 +34,27 @@ class Updater
45 protected $isLoggedIn; 34 protected $isLoggedIn;
46 35
47 /** 36 /**
48 * @var array $_SESSION 37 * @var \ReflectionMethod[] List of current class methods.
49 */
50 protected $session;
51
52 /**
53 * @var ReflectionMethod[] List of current class methods.
54 */ 38 */
55 protected $methods; 39 protected $methods;
56 40
57 /** 41 /**
58 * Object constructor. 42 * Object constructor.
59 * 43 *
60 * @param array $doneUpdates Updates which are already done. 44 * @param array $doneUpdates Updates which are already done.
61 * @param LinkDB $linkDB LinkDB instance. 45 * @param BookmarkServiceInterface $linkDB LinksService instance.
62 * @param ConfigManager $conf Configuration Manager instance. 46 * @param ConfigManager $conf Configuration Manager instance.
63 * @param boolean $isLoggedIn True if the user is logged in. 47 * @param boolean $isLoggedIn True if the user is logged in.
64 * @param array $session $_SESSION (by reference)
65 *
66 * @throws ReflectionException
67 */ 48 */
68 public function __construct($doneUpdates, $linkDB, $conf, $isLoggedIn, &$session = []) 49 public function __construct($doneUpdates, $linkDB, $conf, $isLoggedIn)
69 { 50 {
70 $this->doneUpdates = $doneUpdates; 51 $this->doneUpdates = $doneUpdates;
71 $this->linkDB = $linkDB; 52 $this->linkServices = $linkDB;
72 $this->conf = $conf; 53 $this->conf = $conf;
73 $this->isLoggedIn = $isLoggedIn; 54 $this->isLoggedIn = $isLoggedIn;
74 $this->session = &$session;
75 55
76 // Retrieve all update methods. 56 // Retrieve all update methods.
77 $class = new ReflectionClass($this); 57 $class = new \ReflectionClass($this);
78 $this->methods = $class->getMethods(); 58 $this->methods = $class->getMethods();
79 } 59 }
80 60
@@ -96,12 +76,12 @@ class Updater
96 } 76 }
97 77
98 if ($this->methods === null) { 78 if ($this->methods === null) {
99 throw new UpdaterException(t('Couldn\'t retrieve updater class methods.')); 79 throw new UpdaterException('Couldn\'t retrieve LegacyUpdater class methods.');
100 } 80 }
101 81
102 foreach ($this->methods as $method) { 82 foreach ($this->methods as $method) {
103 // Not an update method or already done, pass. 83 // Not an update method or already done, pass.
104 if (!startsWith($method->getName(), 'updateMethod') 84 if (! startsWith($method->getName(), 'updateMethod')
105 || in_array($method->getName(), $this->doneUpdates) 85 || in_array($method->getName(), $this->doneUpdates)
106 ) { 86 ) {
107 continue; 87 continue;
@@ -114,7 +94,7 @@ class Updater
114 if ($res === true) { 94 if ($res === true) {
115 $updatesRan[] = $method->getName(); 95 $updatesRan[] = $method->getName();
116 } 96 }
117 } catch (Exception $e) { 97 } catch (\Exception $e) {
118 throw new UpdaterException($method, $e); 98 throw new UpdaterException($method, $e);
119 } 99 }
120 } 100 }
@@ -131,432 +111,4 @@ class Updater
131 { 111 {
132 return $this->doneUpdates; 112 return $this->doneUpdates;
133 } 113 }
134
135 /**
136 * Move deprecated options.php to config.php.
137 *
138 * Milestone 0.9 (old versioning) - shaarli/Shaarli#41:
139 * options.php is not supported anymore.
140 */
141 public function updateMethodMergeDeprecatedConfigFile()
142 {
143 if (is_file($this->conf->get('resource.data_dir') . '/options.php')) {
144 include $this->conf->get('resource.data_dir') . '/options.php';
145
146 // Load GLOBALS into config
147 $allowedKeys = array_merge(ConfigPhp::$ROOT_KEYS);
148 $allowedKeys[] = 'config';
149 foreach ($GLOBALS as $key => $value) {
150 if (in_array($key, $allowedKeys)) {
151 $this->conf->set($key, $value);
152 }
153 }
154 $this->conf->write($this->isLoggedIn);
155 unlink($this->conf->get('resource.data_dir') . '/options.php');
156 }
157
158 return true;
159 }
160
161 /**
162 * Move old configuration in PHP to the new config system in JSON format.
163 *
164 * Will rename 'config.php' into 'config.save.php' and create 'config.json.php'.
165 * It will also convert legacy setting keys to the new ones.
166 */
167 public function updateMethodConfigToJson()
168 {
169 // JSON config already exists, nothing to do.
170 if ($this->conf->getConfigIO() instanceof ConfigJson) {
171 return true;
172 }
173
174 $configPhp = new ConfigPhp();
175 $configJson = new ConfigJson();
176 $oldConfig = $configPhp->read($this->conf->getConfigFile() . '.php');
177 rename($this->conf->getConfigFileExt(), $this->conf->getConfigFile() . '.save.php');
178 $this->conf->setConfigIO($configJson);
179 $this->conf->reload();
180
181 $legacyMap = array_flip(ConfigPhp::$LEGACY_KEYS_MAPPING);
182 foreach (ConfigPhp::$ROOT_KEYS as $key) {
183 $this->conf->set($legacyMap[$key], $oldConfig[$key]);
184 }
185
186 // Set sub config keys (config and plugins)
187 $subConfig = array('config', 'plugins');
188 foreach ($subConfig as $sub) {
189 foreach ($oldConfig[$sub] as $key => $value) {
190 if (isset($legacyMap[$sub . '.' . $key])) {
191 $configKey = $legacyMap[$sub . '.' . $key];
192 } else {
193 $configKey = $sub . '.' . $key;
194 }
195 $this->conf->set($configKey, $value);
196 }
197 }
198
199 try {
200 $this->conf->write($this->isLoggedIn);
201 return true;
202 } catch (IOException $e) {
203 error_log($e->getMessage());
204 return false;
205 }
206 }
207
208 /**
209 * Escape settings which have been manually escaped in every request in previous versions:
210 * - general.title
211 * - general.header_link
212 * - redirector.url
213 *
214 * @return bool true if the update is successful, false otherwise.
215 */
216 public function updateMethodEscapeUnescapedConfig()
217 {
218 try {
219 $this->conf->set('general.title', escape($this->conf->get('general.title')));
220 $this->conf->set('general.header_link', escape($this->conf->get('general.header_link')));
221 $this->conf->write($this->isLoggedIn);
222 } catch (Exception $e) {
223 error_log($e->getMessage());
224 return false;
225 }
226 return true;
227 }
228
229 /**
230 * Update the database to use the new ID system, which replaces linkdate primary keys.
231 * Also, creation and update dates are now DateTime objects (done by LinkDB).
232 *
233 * Since this update is very sensitve (changing the whole database), the datastore will be
234 * automatically backed up into the file datastore.<datetime>.php.
235 *
236 * LinkDB also adds the field 'shorturl' with the precedent format (linkdate smallhash),
237 * which will be saved by this method.
238 *
239 * @return bool true if the update is successful, false otherwise.
240 */
241 public function updateMethodDatastoreIds()
242 {
243 // up to date database
244 if (isset($this->linkDB[0])) {
245 return true;
246 }
247
248 $save = $this->conf->get('resource.data_dir') . '/datastore.' . date('YmdHis') . '.php';
249 copy($this->conf->get('resource.datastore'), $save);
250
251 $links = array();
252 foreach ($this->linkDB as $offset => $value) {
253 $links[] = $value;
254 unset($this->linkDB[$offset]);
255 }
256 $links = array_reverse($links);
257 $cpt = 0;
258 foreach ($links as $l) {
259 unset($l['linkdate']);
260 $l['id'] = $cpt;
261 $this->linkDB[$cpt++] = $l;
262 }
263
264 $this->linkDB->save($this->conf->get('resource.page_cache'));
265 $this->linkDB->reorder();
266
267 return true;
268 }
269
270 /**
271 * Rename tags starting with a '-' to work with tag exclusion search.
272 */
273 public function updateMethodRenameDashTags()
274 {
275 $linklist = $this->linkDB->filterSearch();
276 foreach ($linklist as $key => $link) {
277 $link['tags'] = preg_replace('/(^| )\-/', '$1', $link['tags']);
278 $link['tags'] = implode(' ', array_unique(LinkFilter::tagsStrToArray($link['tags'], true)));
279 $this->linkDB[$key] = $link;
280 }
281 $this->linkDB->save($this->conf->get('resource.page_cache'));
282 return true;
283 }
284
285 /**
286 * Initialize API settings:
287 * - api.enabled: true
288 * - api.secret: generated secret
289 */
290 public function updateMethodApiSettings()
291 {
292 if ($this->conf->exists('api.secret')) {
293 return true;
294 }
295
296 $this->conf->set('api.enabled', true);
297 $this->conf->set(
298 'api.secret',
299 generate_api_secret(
300 $this->conf->get('credentials.login'),
301 $this->conf->get('credentials.salt')
302 )
303 );
304 $this->conf->write($this->isLoggedIn);
305 return true;
306 }
307
308 /**
309 * New setting: theme name. If the default theme is used, nothing to do.
310 *
311 * If the user uses a custom theme, raintpl_tpl dir is updated to the parent directory,
312 * and the current theme is set as default in the theme setting.
313 *
314 * @return bool true if the update is successful, false otherwise.
315 */
316 public function updateMethodDefaultTheme()
317 {
318 // raintpl_tpl isn't the root template directory anymore.
319 // We run the update only if this folder still contains the template files.
320 $tplDir = $this->conf->get('resource.raintpl_tpl');
321 $tplFile = $tplDir . '/linklist.html';
322 if (!file_exists($tplFile)) {
323 return true;
324 }
325
326 $parent = dirname($tplDir);
327 $this->conf->set('resource.raintpl_tpl', $parent);
328 $this->conf->set('resource.theme', trim(str_replace($parent, '', $tplDir), '/'));
329 $this->conf->write($this->isLoggedIn);
330
331 // Dependency injection gore
332 RainTPL::$tpl_dir = $tplDir;
333
334 return true;
335 }
336
337 /**
338 * Move the file to inc/user.css to data/user.css.
339 *
340 * Note: Due to hardcoded paths, it's not unit testable. But one line of code should be fine.
341 *
342 * @return bool true if the update is successful, false otherwise.
343 */
344 public function updateMethodMoveUserCss()
345 {
346 if (!is_file('inc/user.css')) {
347 return true;
348 }
349
350 return rename('inc/user.css', 'data/user.css');
351 }
352
353 /**
354 * * `markdown_escape` is a new setting, set to true as default.
355 *
356 * If the markdown plugin was already enabled, escaping is disabled to avoid
357 * breaking existing entries.
358 */
359 public function updateMethodEscapeMarkdown()
360 {
361 if ($this->conf->exists('security.markdown_escape')) {
362 return true;
363 }
364
365 if (in_array('markdown', $this->conf->get('general.enabled_plugins'))) {
366 $this->conf->set('security.markdown_escape', false);
367 } else {
368 $this->conf->set('security.markdown_escape', true);
369 }
370 $this->conf->write($this->isLoggedIn);
371
372 return true;
373 }
374
375 /**
376 * Add 'http://' to Piwik URL the setting is set.
377 *
378 * @return bool true if the update is successful, false otherwise.
379 */
380 public function updateMethodPiwikUrl()
381 {
382 if (!$this->conf->exists('plugins.PIWIK_URL') || startsWith($this->conf->get('plugins.PIWIK_URL'), 'http')) {
383 return true;
384 }
385
386 $this->conf->set('plugins.PIWIK_URL', 'http://' . $this->conf->get('plugins.PIWIK_URL'));
387 $this->conf->write($this->isLoggedIn);
388
389 return true;
390 }
391
392 /**
393 * Use ATOM feed as default.
394 */
395 public function updateMethodAtomDefault()
396 {
397 if (!$this->conf->exists('feed.show_atom') || $this->conf->get('feed.show_atom') === true) {
398 return true;
399 }
400
401 $this->conf->set('feed.show_atom', true);
402 $this->conf->write($this->isLoggedIn);
403
404 return true;
405 }
406
407 /**
408 * Update updates.check_updates_branch setting.
409 *
410 * If the current major version digit matches the latest branch
411 * major version digit, we set the branch to `latest`,
412 * otherwise we'll check updates on the `stable` branch.
413 *
414 * No update required for the dev version.
415 *
416 * Note: due to hardcoded URL and lack of dependency injection, this is not unit testable.
417 *
418 * FIXME! This needs to be removed when we switch to first digit major version
419 * instead of the second one since the versionning process will change.
420 */
421 public function updateMethodCheckUpdateRemoteBranch()
422 {
423 if (SHAARLI_VERSION === 'dev' || $this->conf->get('updates.check_updates_branch') === 'latest') {
424 return true;
425 }
426
427 // Get latest branch major version digit
428 $latestVersion = ApplicationUtils::getLatestGitVersionCode(
429 'https://raw.githubusercontent.com/shaarli/Shaarli/latest/shaarli_version.php',
430 5
431 );
432 if (preg_match('/(\d+)\.\d+$/', $latestVersion, $matches) === false) {
433 return false;
434 }
435 $latestMajor = $matches[1];
436
437 // Get current major version digit
438 preg_match('/(\d+)\.\d+$/', SHAARLI_VERSION, $matches);
439 $currentMajor = $matches[1];
440
441 if ($currentMajor === $latestMajor) {
442 $branch = 'latest';
443 } else {
444 $branch = 'stable';
445 }
446 $this->conf->set('updates.check_updates_branch', $branch);
447 $this->conf->write($this->isLoggedIn);
448 return true;
449 }
450
451 /**
452 * Reset history store file due to date format change.
453 */
454 public function updateMethodResetHistoryFile()
455 {
456 if (is_file($this->conf->get('resource.history'))) {
457 unlink($this->conf->get('resource.history'));
458 }
459 return true;
460 }
461
462 /**
463 * Save the datastore -> the link order is now applied when links are saved.
464 */
465 public function updateMethodReorderDatastore()
466 {
467 $this->linkDB->save($this->conf->get('resource.page_cache'));
468 return true;
469 }
470
471 /**
472 * Change privateonly session key to visibility.
473 */
474 public function updateMethodVisibilitySession()
475 {
476 if (isset($_SESSION['privateonly'])) {
477 unset($_SESSION['privateonly']);
478 $_SESSION['visibility'] = 'private';
479 }
480 return true;
481 }
482
483 /**
484 * Add download size and timeout to the configuration file
485 *
486 * @return bool true if the update is successful, false otherwise.
487 */
488 public function updateMethodDownloadSizeAndTimeoutConf()
489 {
490 if ($this->conf->exists('general.download_max_size')
491 && $this->conf->exists('general.download_timeout')
492 ) {
493 return true;
494 }
495
496 if (!$this->conf->exists('general.download_max_size')) {
497 $this->conf->set('general.download_max_size', 1024 * 1024 * 4);
498 }
499
500 if (!$this->conf->exists('general.download_timeout')) {
501 $this->conf->set('general.download_timeout', 30);
502 }
503
504 $this->conf->write($this->isLoggedIn);
505 return true;
506 }
507
508 /**
509 * * Move thumbnails management to WebThumbnailer, coming with new settings.
510 */
511 public function updateMethodWebThumbnailer()
512 {
513 if ($this->conf->exists('thumbnails.mode')) {
514 return true;
515 }
516
517 $thumbnailsEnabled = extension_loaded('gd') && $this->conf->get('thumbnail.enable_thumbnails', true);
518 $this->conf->set('thumbnails.mode', $thumbnailsEnabled ? Thumbnailer::MODE_ALL : Thumbnailer::MODE_NONE);
519 $this->conf->set('thumbnails.width', 125);
520 $this->conf->set('thumbnails.height', 90);
521 $this->conf->remove('thumbnail');
522 $this->conf->write(true);
523
524 if ($thumbnailsEnabled) {
525 $this->session['warnings'][] = t(
526 'You have enabled or changed thumbnails mode. <a href="?do=thumbs_update">Please synchronize them</a>.'
527 );
528 }
529
530 return true;
531 }
532
533 /**
534 * Set sticky = false on all links
535 *
536 * @return bool true if the update is successful, false otherwise.
537 */
538 public function updateMethodSetSticky()
539 {
540 foreach ($this->linkDB as $key => $link) {
541 if (isset($link['sticky'])) {
542 return true;
543 }
544 $link['sticky'] = false;
545 $this->linkDB[$key] = $link;
546 }
547
548 $this->linkDB->save($this->conf->get('resource.page_cache'));
549
550 return true;
551 }
552
553 /**
554 * Remove redirector settings.
555 */
556 public function updateMethodRemoveRedirector()
557 {
558 $this->conf->remove('redirector');
559 $this->conf->write(true);
560 return true;
561 }
562} 114}
diff --git a/application/updater/UpdaterUtils.php b/application/updater/UpdaterUtils.php
index 34d4f422..828a49fc 100644
--- a/application/updater/UpdaterUtils.php
+++ b/application/updater/UpdaterUtils.php
@@ -1,39 +1,44 @@
1<?php 1<?php
2 2
3/** 3namespace Shaarli\Updater;
4 * Read the updates file, and return already done updates. 4
5 * 5class UpdaterUtils
6 * @param string $updatesFilepath Updates file path.
7 *
8 * @return array Already done update methods.
9 */
10function read_updates_file($updatesFilepath)
11{ 6{
12 if (! empty($updatesFilepath) && is_file($updatesFilepath)) { 7 /**
13 $content = file_get_contents($updatesFilepath); 8 * Read the updates file, and return already done updates.
14 if (! empty($content)) { 9 *
15 return explode(';', $content); 10 * @param string $updatesFilepath Updates file path.
11 *
12 * @return array Already done update methods.
13 */
14 public static function read_updates_file($updatesFilepath)
15 {
16 if (! empty($updatesFilepath) && is_file($updatesFilepath)) {
17 $content = file_get_contents($updatesFilepath);
18 if (! empty($content)) {
19 return explode(';', $content);
20 }
16 } 21 }
22 return array();
17 } 23 }
18 return array();
19}
20 24
21/** 25 /**
22 * Write updates file. 26 * Write updates file.
23 * 27 *
24 * @param string $updatesFilepath Updates file path. 28 * @param string $updatesFilepath Updates file path.
25 * @param array $updates Updates array to write. 29 * @param array $updates Updates array to write.
26 * 30 *
27 * @throws Exception Couldn't write version number. 31 * @throws \Exception Couldn't write version number.
28 */ 32 */
29function write_updates_file($updatesFilepath, $updates) 33 public static function write_updates_file($updatesFilepath, $updates)
30{ 34 {
31 if (empty($updatesFilepath)) { 35 if (empty($updatesFilepath)) {
32 throw new Exception(t('Updates file path is not set, can\'t write updates.')); 36 throw new \Exception('Updates file path is not set, can\'t write updates.');
33 } 37 }
34 38
35 $res = file_put_contents($updatesFilepath, implode(';', $updates)); 39 $res = file_put_contents($updatesFilepath, implode(';', $updates));
36 if ($res === false) { 40 if ($res === false) {
37 throw new Exception(t('Unable to write updates in '. $updatesFilepath . '.')); 41 throw new \Exception('Unable to write updates in '. $updatesFilepath . '.');
42 }
38 } 43 }
39} 44}