aboutsummaryrefslogtreecommitdiffhomepage
path: root/application
diff options
context:
space:
mode:
Diffstat (limited to 'application')
-rw-r--r--application/api/ApiMiddleware.php9
-rw-r--r--application/bookmark/BookmarkFileService.php2
-rw-r--r--application/container/ContainerBuilder.php11
-rw-r--r--application/container/ShaarliContainer.php7
-rw-r--r--application/front/ShaarliMiddleware.php73
-rw-r--r--application/front/controller/admin/ConfigureController.php3
-rw-r--r--application/front/controller/admin/ExportController.php5
-rw-r--r--application/front/controller/admin/ImportController.php3
-rw-r--r--application/front/controller/admin/ManageShaareController.php5
-rw-r--r--application/front/controller/admin/ManageTagController.php3
-rw-r--r--application/front/controller/admin/PasswordController.php9
-rw-r--r--application/front/controller/admin/PluginsController.php3
-rw-r--r--application/front/controller/admin/ThumbnailsController.php18
-rw-r--r--application/front/controller/admin/ToolsController.php3
-rw-r--r--application/front/controller/visitor/BookmarkListController.php248
-rw-r--r--application/front/controller/visitor/DailyController.php5
-rw-r--r--application/front/controller/visitor/LoginController.php3
-rw-r--r--application/front/controller/visitor/OpenSearchController.php3
-rw-r--r--application/front/controller/visitor/PictureWallController.php3
-rw-r--r--application/legacy/LegacyController.php130
-rw-r--r--application/legacy/LegacyRouter.php (renamed from application/Router.php)7
-rw-r--r--application/legacy/UnknowLegacyRouteException.php9
-rw-r--r--application/render/PageBuilder.php9
-rw-r--r--application/render/TemplatePage.php33
-rw-r--r--application/updater/Updater.php43
25 files changed, 596 insertions, 51 deletions
diff --git a/application/api/ApiMiddleware.php b/application/api/ApiMiddleware.php
index 4745ac94..09ce6445 100644
--- a/application/api/ApiMiddleware.php
+++ b/application/api/ApiMiddleware.php
@@ -71,7 +71,14 @@ class ApiMiddleware
71 $response = $e->getApiResponse(); 71 $response = $e->getApiResponse();
72 } 72 }
73 73
74 return $response; 74 return $response
75 ->withHeader('Access-Control-Allow-Origin', '*')
76 ->withHeader(
77 'Access-Control-Allow-Headers',
78 'X-Requested-With, Content-Type, Accept, Origin, Authorization'
79 )
80 ->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
81 ;
75 } 82 }
76 83
77 /** 84 /**
diff --git a/application/bookmark/BookmarkFileService.php b/application/bookmark/BookmarkFileService.php
index 7439d8d8..3d15d4c9 100644
--- a/application/bookmark/BookmarkFileService.php
+++ b/application/bookmark/BookmarkFileService.php
@@ -93,7 +93,7 @@ class BookmarkFileService implements BookmarkServiceInterface
93 throw new Exception('Not authorized'); 93 throw new Exception('Not authorized');
94 } 94 }
95 95
96 return $bookmark; 96 return $first;
97 } 97 }
98 98
99 /** 99 /**
diff --git a/application/container/ContainerBuilder.php b/application/container/ContainerBuilder.php
index ba91fe8b..ccb87c3a 100644
--- a/application/container/ContainerBuilder.php
+++ b/application/container/ContainerBuilder.php
@@ -18,6 +18,8 @@ use Shaarli\Render\PageCacheManager;
18use Shaarli\Security\LoginManager; 18use Shaarli\Security\LoginManager;
19use Shaarli\Security\SessionManager; 19use Shaarli\Security\SessionManager;
20use Shaarli\Thumbnailer; 20use Shaarli\Thumbnailer;
21use Shaarli\Updater\Updater;
22use Shaarli\Updater\UpdaterUtils;
21 23
22/** 24/**
23 * Class ContainerBuilder 25 * Class ContainerBuilder
@@ -128,6 +130,15 @@ class ContainerBuilder
128 return new NetscapeBookmarkUtils($container->bookmarkService, $container->conf, $container->history); 130 return new NetscapeBookmarkUtils($container->bookmarkService, $container->conf, $container->history);
129 }; 131 };
130 132
133 $container['updater'] = function (ShaarliContainer $container): Updater {
134 return new Updater(
135 UpdaterUtils::read_updates_file($container->conf->get('resource.updates')),
136 $container->bookmarkService,
137 $container->conf,
138 $container->loginManager->isLoggedIn()
139 );
140 };
141
131 return $container; 142 return $container;
132 } 143 }
133} 144}
diff --git a/application/container/ShaarliContainer.php b/application/container/ShaarliContainer.php
index b08fa4cb..09e7d5b1 100644
--- a/application/container/ShaarliContainer.php
+++ b/application/container/ShaarliContainer.php
@@ -17,15 +17,17 @@ use Shaarli\Render\PageCacheManager;
17use Shaarli\Security\LoginManager; 17use Shaarli\Security\LoginManager;
18use Shaarli\Security\SessionManager; 18use Shaarli\Security\SessionManager;
19use Shaarli\Thumbnailer; 19use Shaarli\Thumbnailer;
20use Shaarli\Updater\Updater;
20use Slim\Container; 21use Slim\Container;
21 22
22/** 23/**
23 * Extension of Slim container to document the injected objects. 24 * Extension of Slim container to document the injected objects.
24 * 25 *
25 * @property string $basePath Shaarli's instance base path (e.g. `/shaarli/`) 26 * @property string $basePath Shaarli's instance base path (e.g. `/shaarli/`)
26 * @property BookmarkServiceInterface $bookmarkService 27 * @property BookmarkServiceInterface $bookmarkService
27 * @property ConfigManager $conf 28 * @property ConfigManager $conf
28 * @property mixed[] $environment $_SERVER automatically injected by Slim 29 * @property mixed[] $environment $_SERVER automatically injected by Slim
30 * @property callable $errorHandler Overrides default Slim error display
29 * @property FeedBuilder $feedBuilder 31 * @property FeedBuilder $feedBuilder
30 * @property FormatterFactory $formatterFactory 32 * @property FormatterFactory $formatterFactory
31 * @property History $history 33 * @property History $history
@@ -37,6 +39,7 @@ use Slim\Container;
37 * @property PluginManager $pluginManager 39 * @property PluginManager $pluginManager
38 * @property SessionManager $sessionManager 40 * @property SessionManager $sessionManager
39 * @property Thumbnailer $thumbnailer 41 * @property Thumbnailer $thumbnailer
42 * @property Updater $updater
40 */ 43 */
41class ShaarliContainer extends Container 44class ShaarliContainer extends Container
42{ 45{
diff --git a/application/front/ShaarliMiddleware.php b/application/front/ShaarliMiddleware.php
index 7ad610c7..baea6ef2 100644
--- a/application/front/ShaarliMiddleware.php
+++ b/application/front/ShaarliMiddleware.php
@@ -25,6 +25,8 @@ class ShaarliMiddleware
25 25
26 /** 26 /**
27 * Middleware execution: 27 * Middleware execution:
28 * - run updates
29 * - if not logged in open shaarli, redirect to login
28 * - execute the controller 30 * - execute the controller
29 * - return the response 31 * - return the response
30 * 32 *
@@ -36,27 +38,82 @@ class ShaarliMiddleware
36 * 38 *
37 * @return Response response. 39 * @return Response response.
38 */ 40 */
39 public function __invoke(Request $request, Response $response, callable $next) 41 public function __invoke(Request $request, Response $response, callable $next): Response
40 { 42 {
41 $this->container->basePath = rtrim($request->getUri()->getBasePath(), '/'); 43 $this->container->basePath = rtrim($request->getUri()->getBasePath(), '/');
42 44
43 try { 45 try {
44 $response = $next($request, $response); 46 $this->runUpdates();
47 $this->checkOpenShaarli($request, $response, $next);
48
49 return $next($request, $response);
45 } catch (ShaarliFrontException $e) { 50 } catch (ShaarliFrontException $e) {
51 // Possible functional error
52 $this->container->pageBuilder->reset();
46 $this->container->pageBuilder->assign('message', $e->getMessage()); 53 $this->container->pageBuilder->assign('message', $e->getMessage());
54
55 $response = $response->withStatus($e->getCode());
56
57 return $response->write($this->container->pageBuilder->render('error'));
58 } catch (UnauthorizedException $e) {
59 return $response->withRedirect($this->container->basePath . '/login');
60 } catch (\Throwable $e) {
61 // Unknown error encountered
62 $this->container->pageBuilder->reset();
47 if ($this->container->conf->get('dev.debug', false)) { 63 if ($this->container->conf->get('dev.debug', false)) {
64 $this->container->pageBuilder->assign('message', $e->getMessage());
48 $this->container->pageBuilder->assign( 65 $this->container->pageBuilder->assign(
49 'stacktrace', 66 'stacktrace',
50 nl2br(get_class($this) .': '. $e->getTraceAsString()) 67 nl2br(get_class($e) .': '. PHP_EOL . $e->getTraceAsString())
51 ); 68 );
69 } else {
70 $this->container->pageBuilder->assign('message', t('An unexpected error occurred.'));
52 } 71 }
53 72
54 $response = $response->withStatus($e->getCode()); 73 $response = $response->withStatus(500);
55 $response = $response->write($this->container->pageBuilder->render('error')); 74
56 } catch (UnauthorizedException $e) { 75 return $response->write($this->container->pageBuilder->render('error'));
57 return $response->withRedirect($this->container->basePath . '/login'); 76 }
77 }
78
79 /**
80 * Run the updater for every requests processed while logged in.
81 */
82 protected function runUpdates(): void
83 {
84 if ($this->container->loginManager->isLoggedIn() !== true) {
85 return;
86 }
87
88 $newUpdates = $this->container->updater->update();
89 if (!empty($newUpdates)) {
90 $this->container->updater->writeUpdates(
91 $this->container->conf->get('resource.updates'),
92 $this->container->updater->getDoneUpdates()
93 );
94
95 $this->container->pageCacheManager->invalidateCaches();
96 }
97 }
98
99 /**
100 * Access is denied to most pages with `hide_public_links` + `force_login` settings.
101 */
102 protected function checkOpenShaarli(Request $request, Response $response, callable $next): bool
103 {
104 if (// if the user isn't logged in
105 !$this->container->loginManager->isLoggedIn()
106 // and Shaarli doesn't have public content...
107 && $this->container->conf->get('privacy.hide_public_links')
108 // and is configured to enforce the login
109 && $this->container->conf->get('privacy.force_login')
110 // and the current page isn't already the login page
111 // and the user is not requesting a feed (which would lead to a different content-type as expected)
112 && !in_array($next->getName(), ['login', 'atom', 'rss'], true)
113 ) {
114 throw new UnauthorizedException();
58 } 115 }
59 116
60 return $response; 117 return true;
61 } 118 }
62} 119}
diff --git a/application/front/controller/admin/ConfigureController.php b/application/front/controller/admin/ConfigureController.php
index 201a859b..865fc2b0 100644
--- a/application/front/controller/admin/ConfigureController.php
+++ b/application/front/controller/admin/ConfigureController.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
5namespace Shaarli\Front\Controller\Admin; 5namespace Shaarli\Front\Controller\Admin;
6 6
7use Shaarli\Languages; 7use Shaarli\Languages;
8use Shaarli\Render\TemplatePage;
8use Shaarli\Render\ThemeUtils; 9use Shaarli\Render\ThemeUtils;
9use Shaarli\Thumbnailer; 10use Shaarli\Thumbnailer;
10use Slim\Http\Request; 11use Slim\Http\Request;
@@ -52,7 +53,7 @@ class ConfigureController extends ShaarliAdminController
52 $this->assignView('thumbnails_mode', $this->container->conf->get('thumbnails.mode', Thumbnailer::MODE_NONE)); 53 $this->assignView('thumbnails_mode', $this->container->conf->get('thumbnails.mode', Thumbnailer::MODE_NONE));
53 $this->assignView('pagetitle', t('Configure') .' - '. $this->container->conf->get('general.title', 'Shaarli')); 54 $this->assignView('pagetitle', t('Configure') .' - '. $this->container->conf->get('general.title', 'Shaarli'));
54 55
55 return $response->write($this->render('configure')); 56 return $response->write($this->render(TemplatePage::CONFIGURE));
56 } 57 }
57 58
58 /** 59 /**
diff --git a/application/front/controller/admin/ExportController.php b/application/front/controller/admin/ExportController.php
index 7afbfc23..2be957fa 100644
--- a/application/front/controller/admin/ExportController.php
+++ b/application/front/controller/admin/ExportController.php
@@ -6,6 +6,7 @@ namespace Shaarli\Front\Controller\Admin;
6 6
7use DateTime; 7use DateTime;
8use Shaarli\Bookmark\Bookmark; 8use Shaarli\Bookmark\Bookmark;
9use Shaarli\Render\TemplatePage;
9use Slim\Http\Request; 10use Slim\Http\Request;
10use Slim\Http\Response; 11use Slim\Http\Response;
11 12
@@ -24,7 +25,7 @@ class ExportController extends ShaarliAdminController
24 { 25 {
25 $this->assignView('pagetitle', t('Export') .' - '. $this->container->conf->get('general.title', 'Shaarli')); 26 $this->assignView('pagetitle', t('Export') .' - '. $this->container->conf->get('general.title', 'Shaarli'));
26 27
27 return $response->write($this->render('export')); 28 return $response->write($this->render(TemplatePage::EXPORT));
28 } 29 }
29 30
30 /** 31 /**
@@ -74,6 +75,6 @@ class ExportController extends ShaarliAdminController
74 $this->assignView('eol', PHP_EOL); 75 $this->assignView('eol', PHP_EOL);
75 $this->assignView('selection', $selection); 76 $this->assignView('selection', $selection);
76 77
77 return $response->write($this->render('export.bookmarks')); 78 return $response->write($this->render(TemplatePage::NETSCAPE_EXPORT_BOOKMARKS));
78 } 79 }
79} 80}
diff --git a/application/front/controller/admin/ImportController.php b/application/front/controller/admin/ImportController.php
index 8c5305b9..758d5ef9 100644
--- a/application/front/controller/admin/ImportController.php
+++ b/application/front/controller/admin/ImportController.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
5namespace Shaarli\Front\Controller\Admin; 5namespace Shaarli\Front\Controller\Admin;
6 6
7use Psr\Http\Message\UploadedFileInterface; 7use Psr\Http\Message\UploadedFileInterface;
8use Shaarli\Render\TemplatePage;
8use Slim\Http\Request; 9use Slim\Http\Request;
9use Slim\Http\Response; 10use Slim\Http\Response;
10 11
@@ -39,7 +40,7 @@ class ImportController extends ShaarliAdminController
39 ); 40 );
40 $this->assignView('pagetitle', t('Import') .' - '. $this->container->conf->get('general.title', 'Shaarli')); 41 $this->assignView('pagetitle', t('Import') .' - '. $this->container->conf->get('general.title', 'Shaarli'));
41 42
42 return $response->write($this->render('import')); 43 return $response->write($this->render(TemplatePage::IMPORT));
43 } 44 }
44 45
45 /** 46 /**
diff --git a/application/front/controller/admin/ManageShaareController.php b/application/front/controller/admin/ManageShaareController.php
index bdfc5ca7..3aa48423 100644
--- a/application/front/controller/admin/ManageShaareController.php
+++ b/application/front/controller/admin/ManageShaareController.php
@@ -7,6 +7,7 @@ namespace Shaarli\Front\Controller\Admin;
7use Shaarli\Bookmark\Bookmark; 7use Shaarli\Bookmark\Bookmark;
8use Shaarli\Bookmark\Exception\BookmarkNotFoundException; 8use Shaarli\Bookmark\Exception\BookmarkNotFoundException;
9use Shaarli\Formatter\BookmarkMarkdownFormatter; 9use Shaarli\Formatter\BookmarkMarkdownFormatter;
10use Shaarli\Render\TemplatePage;
10use Shaarli\Thumbnailer; 11use Shaarli\Thumbnailer;
11use Slim\Http\Request; 12use Slim\Http\Request;
12use Slim\Http\Response; 13use Slim\Http\Response;
@@ -28,7 +29,7 @@ class ManageShaareController extends ShaarliAdminController
28 t('Shaare a new link') .' - '. $this->container->conf->get('general.title', 'Shaarli') 29 t('Shaare a new link') .' - '. $this->container->conf->get('general.title', 'Shaarli')
29 ); 30 );
30 31
31 return $response->write($this->render('addlink')); 32 return $response->write($this->render(TemplatePage::ADDLINK));
32 } 33 }
33 34
34 /** 35 /**
@@ -365,7 +366,7 @@ class ManageShaareController extends ShaarliAdminController
365 $editLabel . t('Shaare') .' - '. $this->container->conf->get('general.title', 'Shaarli') 366 $editLabel . t('Shaare') .' - '. $this->container->conf->get('general.title', 'Shaarli')
366 ); 367 );
367 368
368 return $response->write($this->render('editlink')); 369 return $response->write($this->render(TemplatePage::EDIT_LINK));
369 } 370 }
370 371
371 /** 372 /**
diff --git a/application/front/controller/admin/ManageTagController.php b/application/front/controller/admin/ManageTagController.php
index 7dab288a..0380ef1f 100644
--- a/application/front/controller/admin/ManageTagController.php
+++ b/application/front/controller/admin/ManageTagController.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
5namespace Shaarli\Front\Controller\Admin; 5namespace Shaarli\Front\Controller\Admin;
6 6
7use Shaarli\Bookmark\BookmarkFilter; 7use Shaarli\Bookmark\BookmarkFilter;
8use Shaarli\Render\TemplatePage;
8use Slim\Http\Request; 9use Slim\Http\Request;
9use Slim\Http\Response; 10use Slim\Http\Response;
10 11
@@ -28,7 +29,7 @@ class ManageTagController extends ShaarliAdminController
28 t('Manage tags') .' - '. $this->container->conf->get('general.title', 'Shaarli') 29 t('Manage tags') .' - '. $this->container->conf->get('general.title', 'Shaarli')
29 ); 30 );
30 31
31 return $response->write($this->render('changetag')); 32 return $response->write($this->render(TemplatePage::CHANGE_TAG));
32 } 33 }
33 34
34 /** 35 /**
diff --git a/application/front/controller/admin/PasswordController.php b/application/front/controller/admin/PasswordController.php
index bcce01a6..5ec0d24b 100644
--- a/application/front/controller/admin/PasswordController.php
+++ b/application/front/controller/admin/PasswordController.php
@@ -7,6 +7,7 @@ namespace Shaarli\Front\Controller\Admin;
7use Shaarli\Container\ShaarliContainer; 7use Shaarli\Container\ShaarliContainer;
8use Shaarli\Front\Exception\OpenShaarliPasswordException; 8use Shaarli\Front\Exception\OpenShaarliPasswordException;
9use Shaarli\Front\Exception\ShaarliFrontException; 9use Shaarli\Front\Exception\ShaarliFrontException;
10use Shaarli\Render\TemplatePage;
10use Slim\Http\Request; 11use Slim\Http\Request;
11use Slim\Http\Response; 12use Slim\Http\Response;
12use Throwable; 13use Throwable;
@@ -33,7 +34,7 @@ class PasswordController extends ShaarliAdminController
33 */ 34 */
34 public function index(Request $request, Response $response): Response 35 public function index(Request $request, Response $response): Response
35 { 36 {
36 return $response->write($this->render('changepassword')); 37 return $response->write($this->render(TemplatePage::CHANGE_PASSWORD));
37 } 38 }
38 39
39 /** 40 /**
@@ -55,7 +56,7 @@ class PasswordController extends ShaarliAdminController
55 56
56 return $response 57 return $response
57 ->withStatus(400) 58 ->withStatus(400)
58 ->write($this->render('changepassword')) 59 ->write($this->render(TemplatePage::CHANGE_PASSWORD))
59 ; 60 ;
60 } 61 }
61 62
@@ -71,7 +72,7 @@ class PasswordController extends ShaarliAdminController
71 72
72 return $response 73 return $response
73 ->withStatus(400) 74 ->withStatus(400)
74 ->write($this->render('changepassword')) 75 ->write($this->render(TemplatePage::CHANGE_PASSWORD))
75 ; 76 ;
76 } 77 }
77 78
@@ -95,6 +96,6 @@ class PasswordController extends ShaarliAdminController
95 96
96 $this->saveSuccessMessage(t('Your password has been changed')); 97 $this->saveSuccessMessage(t('Your password has been changed'));
97 98
98 return $response->write($this->render('changepassword')); 99 return $response->write($this->render(TemplatePage::CHANGE_PASSWORD));
99 } 100 }
100} 101}
diff --git a/application/front/controller/admin/PluginsController.php b/application/front/controller/admin/PluginsController.php
index d5ec91f0..44025395 100644
--- a/application/front/controller/admin/PluginsController.php
+++ b/application/front/controller/admin/PluginsController.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
5namespace Shaarli\Front\Controller\Admin; 5namespace Shaarli\Front\Controller\Admin;
6 6
7use Exception; 7use Exception;
8use Shaarli\Render\TemplatePage;
8use Slim\Http\Request; 9use Slim\Http\Request;
9use Slim\Http\Response; 10use Slim\Http\Response;
10 11
@@ -44,7 +45,7 @@ class PluginsController extends ShaarliAdminController
44 t('Plugin Administration') .' - '. $this->container->conf->get('general.title', 'Shaarli') 45 t('Plugin Administration') .' - '. $this->container->conf->get('general.title', 'Shaarli')
45 ); 46 );
46 47
47 return $response->write($this->render('pluginsadmin')); 48 return $response->write($this->render(TemplatePage::PLUGINS_ADMIN));
48 } 49 }
49 50
50 /** 51 /**
diff --git a/application/front/controller/admin/ThumbnailsController.php b/application/front/controller/admin/ThumbnailsController.php
index e5308510..81c87ed0 100644
--- a/application/front/controller/admin/ThumbnailsController.php
+++ b/application/front/controller/admin/ThumbnailsController.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
5namespace Shaarli\Front\Controller\Admin; 5namespace Shaarli\Front\Controller\Admin;
6 6
7use Shaarli\Bookmark\Exception\BookmarkNotFoundException; 7use Shaarli\Bookmark\Exception\BookmarkNotFoundException;
8use Shaarli\Render\TemplatePage;
8use Slim\Http\Request; 9use Slim\Http\Request;
9use Slim\Http\Response; 10use Slim\Http\Response;
10 11
@@ -36,7 +37,7 @@ class ThumbnailsController extends ShaarliAdminController
36 t('Thumbnails update') .' - '. $this->container->conf->get('general.title', 'Shaarli') 37 t('Thumbnails update') .' - '. $this->container->conf->get('general.title', 'Shaarli')
37 ); 38 );
38 39
39 return $response->write($this->render('thumbnails')); 40 return $response->write($this->render(TemplatePage::THUMBNAILS));
40 } 41 }
41 42
42 /** 43 /**
@@ -61,19 +62,4 @@ class ThumbnailsController extends ShaarliAdminController
61 62
62 return $response->withJson($this->container->formatterFactory->getFormatter('raw')->format($bookmark)); 63 return $response->withJson($this->container->formatterFactory->getFormatter('raw')->format($bookmark));
63 } 64 }
64
65 /**
66 * @param mixed[] $data Variables passed to the template engine
67 *
68 * @return mixed[] Template data after active plugins render_picwall hook execution.
69 */
70 protected function executeHooks(array $data): array
71 {
72 $this->container->pluginManager->executeHooks(
73 'render_tools',
74 $data
75 );
76
77 return $data;
78 }
79} 65}
diff --git a/application/front/controller/admin/ToolsController.php b/application/front/controller/admin/ToolsController.php
index d087f2cd..a476e898 100644
--- a/application/front/controller/admin/ToolsController.php
+++ b/application/front/controller/admin/ToolsController.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
4 4
5namespace Shaarli\Front\Controller\Admin; 5namespace Shaarli\Front\Controller\Admin;
6 6
7use Shaarli\Render\TemplatePage;
7use Slim\Http\Request; 8use Slim\Http\Request;
8use Slim\Http\Response; 9use Slim\Http\Response;
9 10
@@ -29,7 +30,7 @@ class ToolsController extends ShaarliAdminController
29 30
30 $this->assignView('pagetitle', t('Tools') .' - '. $this->container->conf->get('general.title', 'Shaarli')); 31 $this->assignView('pagetitle', t('Tools') .' - '. $this->container->conf->get('general.title', 'Shaarli'));
31 32
32 return $response->write($this->render('tools')); 33 return $response->write($this->render(TemplatePage::TOOLS));
33 } 34 }
34 35
35 /** 36 /**
diff --git a/application/front/controller/visitor/BookmarkListController.php b/application/front/controller/visitor/BookmarkListController.php
new file mode 100644
index 00000000..a37a7f6b
--- /dev/null
+++ b/application/front/controller/visitor/BookmarkListController.php
@@ -0,0 +1,248 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Shaarli\Front\Controller\Visitor;
6
7use Shaarli\Bookmark\Bookmark;
8use Shaarli\Bookmark\Exception\BookmarkNotFoundException;
9use Shaarli\Legacy\LegacyController;
10use Shaarli\Legacy\UnknowLegacyRouteException;
11use Shaarli\Render\TemplatePage;
12use Shaarli\Thumbnailer;
13use Slim\Http\Request;
14use Slim\Http\Response;
15
16/**
17 * Class BookmarkListController
18 *
19 * Slim controller used to render the bookmark list, the home page of Shaarli.
20 * It also displays permalinks, and process legacy routes based on GET parameters.
21 */
22class BookmarkListController extends ShaarliVisitorController
23{
24 /**
25 * GET / - Displays the bookmark list, with optional filter parameters.
26 */
27 public function index(Request $request, Response $response): Response
28 {
29 $legacyResponse = $this->processLegacyController($request, $response);
30 if (null !== $legacyResponse) {
31 return $legacyResponse;
32 }
33
34 $formatter = $this->container->formatterFactory->getFormatter();
35
36 $searchTags = escape(normalize_spaces($request->getParam('searchtags') ?? ''));
37 $searchTerm = escape(normalize_spaces($request->getParam('searchterm') ?? ''));;
38
39 // Filter bookmarks according search parameters.
40 $visibility = $this->container->sessionManager->getSessionParameter('visibility');
41 $search = [
42 'searchtags' => $searchTags,
43 'searchterm' => $searchTerm,
44 ];
45 $linksToDisplay = $this->container->bookmarkService->search(
46 $search,
47 $visibility,
48 false,
49 !!$this->container->sessionManager->getSessionParameter('untaggedonly')
50 ) ?? [];
51
52 // ---- Handle paging.
53 $keys = [];
54 foreach ($linksToDisplay as $key => $value) {
55 $keys[] = $key;
56 }
57
58 $linksPerPage = $this->container->sessionManager->getSessionParameter('LINKS_PER_PAGE', 20) ?: 20;
59
60 // Select articles according to paging.
61 $pageCount = (int) ceil(count($keys) / $linksPerPage) ?: 1;
62 $page = (int) $request->getParam('page') ?? 1;
63 $page = $page < 1 ? 1 : $page;
64 $page = $page > $pageCount ? $pageCount : $page;
65
66 // Start index.
67 $i = ($page - 1) * $linksPerPage;
68 $end = $i + $linksPerPage;
69
70 $linkDisp = [];
71 $save = false;
72 while ($i < $end && $i < count($keys)) {
73 $save = $this->updateThumbnail($linksToDisplay[$keys[$i]], false) || $save;
74 $link = $formatter->format($linksToDisplay[$keys[$i]]);
75
76 $linkDisp[$keys[$i]] = $link;
77 $i++;
78 }
79
80 if ($save) {
81 $this->container->bookmarkService->save();
82 }
83
84 // Compute paging navigation
85 $searchtagsUrl = $searchTags === '' ? '' : '&searchtags=' . urlencode($searchTags);
86 $searchtermUrl = $searchTerm === '' ? '' : '&searchterm=' . urlencode($searchTerm);
87
88 $previous_page_url = '';
89 if ($i !== count($keys)) {
90 $previous_page_url = '?page=' . ($page + 1) . $searchtermUrl . $searchtagsUrl;
91 }
92 $next_page_url = '';
93 if ($page > 1) {
94 $next_page_url = '?page=' . ($page - 1) . $searchtermUrl . $searchtagsUrl;
95 }
96
97 // Fill all template fields.
98 $data = array_merge(
99 $this->initializeTemplateVars(),
100 [
101 'previous_page_url' => $previous_page_url,
102 'next_page_url' => $next_page_url,
103 'page_current' => $page,
104 'page_max' => $pageCount,
105 'result_count' => count($linksToDisplay),
106 'search_term' => $searchTerm,
107 'search_tags' => $searchTags,
108 'visibility' => $visibility,
109 'links' => $linkDisp,
110 ]
111 );
112
113 if (!empty($searchTerm) || !empty($searchTags)) {
114 $data['pagetitle'] = t('Search: ');
115 $data['pagetitle'] .= ! empty($searchTerm) ? $searchTerm . ' ' : '';
116 $bracketWrap = function ($tag) {
117 return '[' . $tag . ']';
118 };
119 $data['pagetitle'] .= ! empty($searchTags)
120 ? implode(' ', array_map($bracketWrap, preg_split('/\s+/', $searchTags))) . ' '
121 : '';
122 $data['pagetitle'] .= '- ';
123 }
124
125 $data['pagetitle'] = ($data['pagetitle'] ?? '') . $this->container->conf->get('general.title', 'Shaarli');
126
127 $this->executeHooks($data);
128 $this->assignAllView($data);
129
130 return $response->write($this->render(TemplatePage::LINKLIST));
131 }
132
133 /**
134 * GET /shaare/{hash} - Display a single shaare
135 */
136 public function permalink(Request $request, Response $response, array $args): Response
137 {
138 try {
139 $bookmark = $this->container->bookmarkService->findByHash($args['hash']);
140 } catch (BookmarkNotFoundException $e) {
141 $this->assignView('error_message', $e->getMessage());
142
143 return $response->write($this->render(TemplatePage::ERROR_404));
144 }
145
146 $this->updateThumbnail($bookmark);
147
148 $data = array_merge(
149 $this->initializeTemplateVars(),
150 [
151 'pagetitle' => $bookmark->getTitle() .' - '. $this->container->conf->get('general.title', 'Shaarli'),
152 'links' => [$this->container->formatterFactory->getFormatter()->format($bookmark)],
153 ]
154 );
155
156 $this->executeHooks($data);
157 $this->assignAllView($data);
158
159 return $response->write($this->render(TemplatePage::LINKLIST));
160 }
161
162 /**
163 * Update the thumbnail of a single bookmark if necessary.
164 */
165 protected function updateThumbnail(Bookmark $bookmark, bool $writeDatastore = true): bool
166 {
167 // Logged in, thumbnails enabled, not a note, is HTTP
168 // and (never retrieved yet or no valid cache file)
169 if ($this->container->loginManager->isLoggedIn()
170 && $this->container->conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) !== Thumbnailer::MODE_NONE
171 && false !== $bookmark->getThumbnail()
172 && !$bookmark->isNote()
173 && (null === $bookmark->getThumbnail() || !is_file($bookmark->getThumbnail()))
174 && startsWith(strtolower($bookmark->getUrl()), 'http')
175 ) {
176 $bookmark->setThumbnail($this->container->thumbnailer->get($bookmark->getUrl()));
177 $this->container->bookmarkService->set($bookmark, $writeDatastore);
178
179 return true;
180 }
181
182 return false;
183 }
184
185 /**
186 * @param mixed[] $data Template vars to process in plugins, passed as reference.
187 */
188 protected function executeHooks(array &$data): void
189 {
190 $this->container->pluginManager->executeHooks(
191 'render_linklist',
192 $data,
193 ['loggedin' => $this->container->loginManager->isLoggedIn()]
194 );
195 }
196
197 /**
198 * @return string[] Default template variables without values.
199 */
200 protected function initializeTemplateVars(): array
201 {
202 return [
203 'previous_page_url' => '',
204 'next_page_url' => '',
205 'page_max' => '',
206 'search_tags' => '',
207 'result_count' => '',
208 ];
209 }
210
211 /**
212 * Process legacy routes if necessary. They used query parameters.
213 * If no legacy routes is passed, return null.
214 */
215 protected function processLegacyController(Request $request, Response $response): ?Response
216 {
217 // Legacy smallhash filter
218 $queryString = $this->container->environment['QUERY_STRING'] ?? null;
219 if (null !== $queryString && 1 === preg_match('/^([a-zA-Z0-9-_@]{6})($|&|#)/', $queryString, $match)) {
220 return $this->redirect($response, '/shaare/' . $match[1]);
221 }
222
223 // Legacy controllers (mostly used for redirections)
224 if (null !== $request->getQueryParam('do')) {
225 $legacyController = new LegacyController($this->container);
226
227 try {
228 return $legacyController->process($request, $response, $request->getQueryParam('do'));
229 } catch (UnknowLegacyRouteException $e) {
230 // We ignore legacy 404
231 return null;
232 }
233 }
234
235 // Legacy GET admin routes
236 $legacyGetRoutes = array_intersect(
237 LegacyController::LEGACY_GET_ROUTES,
238 array_keys($request->getQueryParams() ?? [])
239 );
240 if (1 === count($legacyGetRoutes)) {
241 $legacyController = new LegacyController($this->container);
242
243 return $legacyController->process($request, $response, $legacyGetRoutes[0]);
244 }
245
246 return null;
247 }
248}
diff --git a/application/front/controller/visitor/DailyController.php b/application/front/controller/visitor/DailyController.php
index e5c9ddac..05b4f095 100644
--- a/application/front/controller/visitor/DailyController.php
+++ b/application/front/controller/visitor/DailyController.php
@@ -7,6 +7,7 @@ namespace Shaarli\Front\Controller\Visitor;
7use DateTime; 7use DateTime;
8use DateTimeImmutable; 8use DateTimeImmutable;
9use Shaarli\Bookmark\Bookmark; 9use Shaarli\Bookmark\Bookmark;
10use Shaarli\Render\TemplatePage;
10use Slim\Http\Request; 11use Slim\Http\Request;
11use Slim\Http\Response; 12use Slim\Http\Response;
12 13
@@ -85,7 +86,7 @@ class DailyController extends ShaarliVisitorController
85 t('Daily') .' - '. format_date($dayDate, false) . ' - ' . $mainTitle 86 t('Daily') .' - '. format_date($dayDate, false) . ' - ' . $mainTitle
86 ); 87 );
87 88
88 return $response->write($this->render('daily')); 89 return $response->write($this->render(TemplatePage::DAILY));
89 } 90 }
90 91
91 /** 92 /**
@@ -152,7 +153,7 @@ class DailyController extends ShaarliVisitorController
152 $this->assignView('hide_timestamps', $this->container->conf->get('privacy.hide_timestamps', false)); 153 $this->assignView('hide_timestamps', $this->container->conf->get('privacy.hide_timestamps', false));
153 $this->assignView('days', $dataPerDay); 154 $this->assignView('days', $dataPerDay);
154 155
155 $rssContent = $this->render('dailyrss'); 156 $rssContent = $this->render(TemplatePage::DAILY_RSS);
156 157
157 $cache->cache($rssContent); 158 $cache->cache($rssContent);
158 159
diff --git a/application/front/controller/visitor/LoginController.php b/application/front/controller/visitor/LoginController.php
index 0db1f463..a257766f 100644
--- a/application/front/controller/visitor/LoginController.php
+++ b/application/front/controller/visitor/LoginController.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
5namespace Shaarli\Front\Controller\Visitor; 5namespace Shaarli\Front\Controller\Visitor;
6 6
7use Shaarli\Front\Exception\LoginBannedException; 7use Shaarli\Front\Exception\LoginBannedException;
8use Shaarli\Render\TemplatePage;
8use Slim\Http\Request; 9use Slim\Http\Request;
9use Slim\Http\Response; 10use Slim\Http\Response;
10 11
@@ -41,6 +42,6 @@ class LoginController extends ShaarliVisitorController
41 ->assignView('pagetitle', t('Login') .' - '. $this->container->conf->get('general.title', 'Shaarli')) 42 ->assignView('pagetitle', t('Login') .' - '. $this->container->conf->get('general.title', 'Shaarli'))
42 ; 43 ;
43 44
44 return $response->write($this->render('loginform')); 45 return $response->write($this->render(TemplatePage::LOGIN));
45 } 46 }
46} 47}
diff --git a/application/front/controller/visitor/OpenSearchController.php b/application/front/controller/visitor/OpenSearchController.php
index 0fd68db6..36d60acf 100644
--- a/application/front/controller/visitor/OpenSearchController.php
+++ b/application/front/controller/visitor/OpenSearchController.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
4 4
5namespace Shaarli\Front\Controller\Visitor; 5namespace Shaarli\Front\Controller\Visitor;
6 6
7use Shaarli\Render\TemplatePage;
7use Slim\Http\Request; 8use Slim\Http\Request;
8use Slim\Http\Response; 9use Slim\Http\Response;
9 10
@@ -21,6 +22,6 @@ class OpenSearchController extends ShaarliVisitorController
21 22
22 $this->assignView('serverurl', index_url($this->container->environment)); 23 $this->assignView('serverurl', index_url($this->container->environment));
23 24
24 return $response->write($this->render('opensearch')); 25 return $response->write($this->render(TemplatePage::OPEN_SEARCH));
25 } 26 }
26} 27}
diff --git a/application/front/controller/visitor/PictureWallController.php b/application/front/controller/visitor/PictureWallController.php
index 4e1dce8c..5ef2cb17 100644
--- a/application/front/controller/visitor/PictureWallController.php
+++ b/application/front/controller/visitor/PictureWallController.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
5namespace Shaarli\Front\Controller\Visitor; 5namespace Shaarli\Front\Controller\Visitor;
6 6
7use Shaarli\Front\Exception\ThumbnailsDisabledException; 7use Shaarli\Front\Exception\ThumbnailsDisabledException;
8use Shaarli\Render\TemplatePage;
8use Shaarli\Thumbnailer; 9use Shaarli\Thumbnailer;
9use Slim\Http\Request; 10use Slim\Http\Request;
10use Slim\Http\Response; 11use Slim\Http\Response;
@@ -46,7 +47,7 @@ class PictureWallController extends ShaarliVisitorController
46 $this->assignView($key, $value); 47 $this->assignView($key, $value);
47 } 48 }
48 49
49 return $response->write($this->render('picwall')); 50 return $response->write($this->render(TemplatePage::PICTURE_WALL));
50 } 51 }
51 52
52 /** 53 /**
diff --git a/application/legacy/LegacyController.php b/application/legacy/LegacyController.php
new file mode 100644
index 00000000..a97b07b1
--- /dev/null
+++ b/application/legacy/LegacyController.php
@@ -0,0 +1,130 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Shaarli\Legacy;
6
7use Shaarli\Feed\FeedBuilder;
8use Shaarli\Front\Controller\Visitor\ShaarliVisitorController;
9use Slim\Http\Request;
10use Slim\Http\Response;
11
12/**
13 * We use this to maintain legacy routes, and redirect requests to the corresponding Slim route.
14 * Only public routes, and both `?addlink` and `?post` were kept here.
15 * Other routes will just display the linklist.
16 *
17 * @deprecated
18 */
19class LegacyController extends ShaarliVisitorController
20{
21 /** @var string[] Both `?post` and `?addlink` do not use `?do=` format. */
22 public const LEGACY_GET_ROUTES = [
23 'post',
24 'addlink',
25 ];
26
27 /**
28 * This method will call `$action` method, which will redirect to corresponding Slim route.
29 */
30 public function process(Request $request, Response $response, string $action): Response
31 {
32 if (!method_exists($this, $action)) {
33 throw new UnknowLegacyRouteException();
34 }
35
36 return $this->{$action}($request, $response);
37 }
38
39 /** Legacy route: ?post= */
40 public function post(Request $request, Response $response): Response
41 {
42 $parameters = count($request->getQueryParams()) > 0 ? '?' . http_build_query($request->getQueryParams()) : '';
43
44 if (!$this->container->loginManager->isLoggedIn()) {
45 return $this->redirect($response, '/login' . $parameters);
46 }
47
48 return $this->redirect($response, '/admin/shaare' . $parameters);
49 }
50
51 /** Legacy route: ?addlink= */
52 protected function addlink(Request $request, Response $response): Response
53 {
54 if (!$this->container->loginManager->isLoggedIn()) {
55 return $this->redirect($response, '/login');
56 }
57
58 return $this->redirect($response, '/admin/add-shaare');
59 }
60
61 /** Legacy route: ?do=login */
62 protected function login(Request $request, Response $response): Response
63 {
64 return $this->redirect($response, '/login');
65 }
66
67 /** Legacy route: ?do=logout */
68 protected function logout(Request $request, Response $response): Response
69 {
70 return $this->redirect($response, '/logout');
71 }
72
73 /** Legacy route: ?do=picwall */
74 protected function picwall(Request $request, Response $response): Response
75 {
76 return $this->redirect($response, '/picture-wall');
77 }
78
79 /** Legacy route: ?do=tagcloud */
80 protected function tagcloud(Request $request, Response $response): Response
81 {
82 return $this->redirect($response, '/tags/cloud');
83 }
84
85 /** Legacy route: ?do=taglist */
86 protected function taglist(Request $request, Response $response): Response
87 {
88 return $this->redirect($response, '/tags/list');
89 }
90
91 /** Legacy route: ?do=daily */
92 protected function daily(Request $request, Response $response): Response
93 {
94 $dayParam = !empty($request->getParam('day')) ? '?day=' . escape($request->getParam('day')) : '';
95
96 return $this->redirect($response, '/daily' . $dayParam);
97 }
98
99 /** Legacy route: ?do=rss */
100 protected function rss(Request $request, Response $response): Response
101 {
102 return $this->feed($request, $response, FeedBuilder::$FEED_RSS);
103 }
104
105 /** Legacy route: ?do=atom */
106 protected function atom(Request $request, Response $response): Response
107 {
108 return $this->feed($request, $response, FeedBuilder::$FEED_ATOM);
109 }
110
111 /** Legacy route: ?do=opensearch */
112 protected function opensearch(Request $request, Response $response): Response
113 {
114 return $this->redirect($response, '/open-search');
115 }
116
117 /** Legacy route: ?do=dailyrss */
118 protected function dailyrss(Request $request, Response $response): Response
119 {
120 return $this->redirect($response, '/daily-rss');
121 }
122
123 /** Legacy route: ?do=feed */
124 protected function feed(Request $request, Response $response, string $feedType): Response
125 {
126 $parameters = count($request->getQueryParams()) > 0 ? '?' . http_build_query($request->getQueryParams()) : '';
127
128 return $this->redirect($response, '/feed/' . $feedType . $parameters);
129 }
130}
diff --git a/application/Router.php b/application/legacy/LegacyRouter.php
index d7187487..cea99154 100644
--- a/application/Router.php
+++ b/application/legacy/LegacyRouter.php
@@ -1,12 +1,15 @@
1<?php 1<?php
2namespace Shaarli; 2
3namespace Shaarli\Legacy;
3 4
4/** 5/**
5 * Class Router 6 * Class Router
6 * 7 *
7 * (only displayable pages here) 8 * (only displayable pages here)
9 *
10 * @deprecated
8 */ 11 */
9class Router 12class LegacyRouter
10{ 13{
11 public static $AJAX_THUMB_UPDATE = 'ajax_thumb_update'; 14 public static $AJAX_THUMB_UPDATE = 'ajax_thumb_update';
12 15
diff --git a/application/legacy/UnknowLegacyRouteException.php b/application/legacy/UnknowLegacyRouteException.php
new file mode 100644
index 00000000..ae1518ad
--- /dev/null
+++ b/application/legacy/UnknowLegacyRouteException.php
@@ -0,0 +1,9 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Shaarli\Legacy;
6
7class UnknowLegacyRouteException extends \Exception
8{
9}
diff --git a/application/render/PageBuilder.php b/application/render/PageBuilder.php
index 2779eb90..85e1d59d 100644
--- a/application/render/PageBuilder.php
+++ b/application/render/PageBuilder.php
@@ -70,6 +70,15 @@ class PageBuilder
70 } 70 }
71 71
72 /** 72 /**
73 * Reset current state of template rendering.
74 * Mostly useful for error handling. We remove everything, and display the error template.
75 */
76 public function reset(): void
77 {
78 $this->tpl = false;
79 }
80
81 /**
73 * Initialize all default tpl tags. 82 * Initialize all default tpl tags.
74 */ 83 */
75 private function initialize() 84 private function initialize()
diff --git a/application/render/TemplatePage.php b/application/render/TemplatePage.php
new file mode 100644
index 00000000..8af8228a
--- /dev/null
+++ b/application/render/TemplatePage.php
@@ -0,0 +1,33 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Shaarli\Render;
6
7interface TemplatePage
8{
9 public const ERROR_404 = '404';
10 public const ADDLINK = 'addlink';
11 public const CHANGE_PASSWORD = 'changepassword';
12 public const CHANGE_TAG = 'changetag';
13 public const CONFIGURE = 'configure';
14 public const DAILY = 'daily';
15 public const DAILY_RSS = 'dailyrss';
16 public const EDIT_LINK = 'editlink';
17 public const ERROR = 'error';
18 public const EXPORT = 'export';
19 public const NETSCAPE_EXPORT_BOOKMARKS = 'export.bookmarks';
20 public const FEED_ATOM = 'feed.atom';
21 public const FEED_RSS = 'feed.rss';
22 public const IMPORT = 'import';
23 public const INSTALL = 'install';
24 public const LINKLIST = 'linklist';
25 public const LOGIN = 'loginform';
26 public const OPEN_SEARCH = 'opensearch';
27 public const PICTURE_WALL = 'picwall';
28 public const PLUGINS_ADMIN = 'pluginsadmin';
29 public const TAG_CLOUD = 'tag.cloud';
30 public const TAG_LIST = 'tag.list';
31 public const THUMBNAILS = 'thumbnails';
32 public const TOOLS = 'tools';
33}
diff --git a/application/updater/Updater.php b/application/updater/Updater.php
index 4bbcb9f2..f73a7452 100644
--- a/application/updater/Updater.php
+++ b/application/updater/Updater.php
@@ -21,7 +21,7 @@ class Updater
21 /** 21 /**
22 * @var BookmarkServiceInterface instance. 22 * @var BookmarkServiceInterface instance.
23 */ 23 */
24 protected $linkServices; 24 protected $bookmarkService;
25 25
26 /** 26 /**
27 * @var ConfigManager $conf Configuration Manager instance. 27 * @var ConfigManager $conf Configuration Manager instance.
@@ -49,7 +49,7 @@ class Updater
49 public function __construct($doneUpdates, $linkDB, $conf, $isLoggedIn) 49 public function __construct($doneUpdates, $linkDB, $conf, $isLoggedIn)
50 { 50 {
51 $this->doneUpdates = $doneUpdates; 51 $this->doneUpdates = $doneUpdates;
52 $this->linkServices = $linkDB; 52 $this->bookmarkService = $linkDB;
53 $this->conf = $conf; 53 $this->conf = $conf;
54 $this->isLoggedIn = $isLoggedIn; 54 $this->isLoggedIn = $isLoggedIn;
55 55
@@ -68,7 +68,7 @@ class Updater
68 */ 68 */
69 public function update() 69 public function update()
70 { 70 {
71 $updatesRan = array(); 71 $updatesRan = [];
72 72
73 // If the user isn't logged in, exit without updating. 73 // If the user isn't logged in, exit without updating.
74 if ($this->isLoggedIn !== true) { 74 if ($this->isLoggedIn !== true) {
@@ -112,6 +112,16 @@ class Updater
112 return $this->doneUpdates; 112 return $this->doneUpdates;
113 } 113 }
114 114
115 public function readUpdates(string $updatesFilepath): array
116 {
117 return UpdaterUtils::read_updates_file($updatesFilepath);
118 }
119
120 public function writeUpdates(string $updatesFilepath, array $updates): void
121 {
122 UpdaterUtils::write_updates_file($updatesFilepath, $updates);
123 }
124
115 /** 125 /**
116 * With the Slim routing system, default header link should be `./` instead of `?`. 126 * With the Slim routing system, default header link should be `./` instead of `?`.
117 * Otherwise you can not go back to the home page. Example: `/picture-wall` -> `/picture-wall?` instead of `/`. 127 * Otherwise you can not go back to the home page. Example: `/picture-wall` -> `/picture-wall?` instead of `/`.
@@ -127,4 +137,31 @@ class Updater
127 137
128 return true; 138 return true;
129 } 139 }
140
141 /**
142 * With the Slim routing system, note bookmarks URL formatted `?abcdef`
143 * should be replaced with `/shaare/abcdef`
144 */
145 public function updateMethodMigrateExistingNotesUrl(): bool
146 {
147 $updated = false;
148
149 foreach ($this->bookmarkService->search() as $bookmark) {
150 if ($bookmark->isNote()
151 && startsWith($bookmark->getUrl(), '?')
152 && 1 === preg_match('/^\?([a-zA-Z0-9-_@]{6})($|&|#)/', $bookmark->getUrl(), $match)
153 ) {
154 $updated = true;
155 $bookmark = $bookmark->setUrl('/shaare/' . $match[1]);
156
157 $this->bookmarkService->set($bookmark, false);
158 }
159 }
160
161 if ($updated) {
162 $this->bookmarkService->save();
163 }
164
165 return true;
166 }
130} 167}