aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--application/Thumbnailer.php3
-rw-r--r--application/front/controller/admin/ConfigureController.php2
-rw-r--r--application/front/controller/admin/ThumbnailsController.php79
-rw-r--r--application/legacy/LegacyUpdater.php2
-rw-r--r--assets/common/js/thumbnails-update.js6
-rw-r--r--index.php35
-rw-r--r--tests/front/controller/admin/ThumbnailsControllerTest.php154
-rw-r--r--tpl/default/configure.html4
-rw-r--r--tpl/default/linklist.html2
-rw-r--r--tpl/default/picwall.html2
-rw-r--r--tpl/default/tools.html2
-rw-r--r--tpl/vintage/configure.html2
12 files changed, 253 insertions, 40 deletions
diff --git a/application/Thumbnailer.php b/application/Thumbnailer.php
index 314baf0d..5aec23c8 100644
--- a/application/Thumbnailer.php
+++ b/application/Thumbnailer.php
@@ -4,7 +4,6 @@ namespace Shaarli;
4 4
5use Shaarli\Config\ConfigManager; 5use Shaarli\Config\ConfigManager;
6use WebThumbnailer\Application\ConfigManager as WTConfigManager; 6use WebThumbnailer\Application\ConfigManager as WTConfigManager;
7use WebThumbnailer\Exception\WebThumbnailerException;
8use WebThumbnailer\WebThumbnailer; 7use WebThumbnailer\WebThumbnailer;
9 8
10/** 9/**
@@ -90,7 +89,7 @@ class Thumbnailer
90 89
91 try { 90 try {
92 return $this->wt->thumbnail($url); 91 return $this->wt->thumbnail($url);
93 } catch (WebThumbnailerException $e) { 92 } catch (\Throwable $e) {
94 // Exceptions are only thrown in debug mode. 93 // Exceptions are only thrown in debug mode.
95 error_log(get_class($e) . ': ' . $e->getMessage()); 94 error_log(get_class($e) . ': ' . $e->getMessage());
96 } 95 }
diff --git a/application/front/controller/admin/ConfigureController.php b/application/front/controller/admin/ConfigureController.php
index 44971c43..201a859b 100644
--- a/application/front/controller/admin/ConfigureController.php
+++ b/application/front/controller/admin/ConfigureController.php
@@ -99,7 +99,7 @@ class ConfigureController extends ShaarliAdminController
99 ) { 99 ) {
100 $this->saveWarningMessage(t( 100 $this->saveWarningMessage(t(
101 'You have enabled or changed thumbnails mode. ' 101 'You have enabled or changed thumbnails mode. '
102 .'<a href="./?do=thumbs_update">Please synchronize them</a>.' 102 .'<a href="'. $this->container->basePath .'/admin/thumbnails">Please synchronize them</a>.'
103 )); 103 ));
104 } 104 }
105 $this->container->conf->set('thumbnails.mode', $thumbnailsMode); 105 $this->container->conf->set('thumbnails.mode', $thumbnailsMode);
diff --git a/application/front/controller/admin/ThumbnailsController.php b/application/front/controller/admin/ThumbnailsController.php
new file mode 100644
index 00000000..e5308510
--- /dev/null
+++ b/application/front/controller/admin/ThumbnailsController.php
@@ -0,0 +1,79 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Shaarli\Front\Controller\Admin;
6
7use Shaarli\Bookmark\Exception\BookmarkNotFoundException;
8use Slim\Http\Request;
9use Slim\Http\Response;
10
11/**
12 * Class ToolsController
13 *
14 * Slim controller used to handle thumbnails update.
15 */
16class ThumbnailsController extends ShaarliAdminController
17{
18 /**
19 * GET /admin/thumbnails - Display thumbnails update page
20 */
21 public function index(Request $request, Response $response): Response
22 {
23 $ids = [];
24 foreach ($this->container->bookmarkService->search() as $bookmark) {
25 // A note or not HTTP(S)
26 if ($bookmark->isNote() || !startsWith(strtolower($bookmark->getUrl()), 'http')) {
27 continue;
28 }
29
30 $ids[] = $bookmark->getId();
31 }
32
33 $this->assignView('ids', $ids);
34 $this->assignView(
35 'pagetitle',
36 t('Thumbnails update') .' - '. $this->container->conf->get('general.title', 'Shaarli')
37 );
38
39 return $response->write($this->render('thumbnails'));
40 }
41
42 /**
43 * PATCH /admin/shaare/{id}/thumbnail-update - Route for AJAX calls
44 */
45 public function ajaxUpdate(Request $request, Response $response, array $args): Response
46 {
47 $id = $args['id'] ?? null;
48
49 if (false === ctype_digit($id)) {
50 return $response->withStatus(400);
51 }
52
53 try {
54 $bookmark = $this->container->bookmarkService->get($id);
55 } catch (BookmarkNotFoundException $e) {
56 return $response->withStatus(404);
57 }
58
59 $bookmark->setThumbnail($this->container->thumbnailer->get($bookmark->getUrl()));
60 $this->container->bookmarkService->set($bookmark);
61
62 return $response->withJson($this->container->formatterFactory->getFormatter('raw')->format($bookmark));
63 }
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}
diff --git a/application/legacy/LegacyUpdater.php b/application/legacy/LegacyUpdater.php
index 8d5cd071..cbf6890f 100644
--- a/application/legacy/LegacyUpdater.php
+++ b/application/legacy/LegacyUpdater.php
@@ -534,7 +534,7 @@ class LegacyUpdater
534 534
535 if ($thumbnailsEnabled) { 535 if ($thumbnailsEnabled) {
536 $this->session['warnings'][] = t( 536 $this->session['warnings'][] = t(
537 'You have enabled or changed thumbnails mode. <a href="./?do=thumbs_update">Please synchronize them</a>.' 537 'You have enabled or changed thumbnails mode. <a href="./admin/thumbnails">Please synchronize them</a>.'
538 ); 538 );
539 } 539 }
540 540
diff --git a/assets/common/js/thumbnails-update.js b/assets/common/js/thumbnails-update.js
index b37a32f3..3cd4c2a7 100644
--- a/assets/common/js/thumbnails-update.js
+++ b/assets/common/js/thumbnails-update.js
@@ -17,7 +17,7 @@
17 */ 17 */
18function updateThumb(basePath, ids, i, elements) { 18function updateThumb(basePath, ids, i, elements) {
19 const xhr = new XMLHttpRequest(); 19 const xhr = new XMLHttpRequest();
20 xhr.open('POST', `${basePath}/?do=ajax_thumb_update`); 20 xhr.open('PATCH', `${basePath}/admin/shaare/${ids[i]}/update-thumbnail`);
21 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); 21 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
22 xhr.responseType = 'json'; 22 xhr.responseType = 'json';
23 xhr.onload = () => { 23 xhr.onload = () => {
@@ -30,14 +30,14 @@ function updateThumb(basePath, ids, i, elements) {
30 elements.current.innerHTML = i; 30 elements.current.innerHTML = i;
31 elements.title.innerHTML = response.title; 31 elements.title.innerHTML = response.title;
32 if (response.thumbnail !== false) { 32 if (response.thumbnail !== false) {
33 elements.thumbnail.innerHTML = `<img src="${response.thumbnail}">`; 33 elements.thumbnail.innerHTML = `<img src="${basePath}/${response.thumbnail}">`;
34 } 34 }
35 if (i < ids.length) { 35 if (i < ids.length) {
36 updateThumb(basePath, ids, i, elements); 36 updateThumb(basePath, ids, i, elements);
37 } 37 }
38 } 38 }
39 }; 39 };
40 xhr.send(`id=${ids[i]}`); 40 xhr.send();
41} 41}
42 42
43(() => { 43(() => {
diff --git a/index.php b/index.php
index 9202cb84..a07de74d 100644
--- a/index.php
+++ b/index.php
@@ -603,38 +603,14 @@ function renderPage($conf, $pluginManager, $bookmarkService, $history, $sessionM
603 603
604 // -------- Thumbnails Update 604 // -------- Thumbnails Update
605 if ($targetPage == Router::$PAGE_THUMBS_UPDATE) { 605 if ($targetPage == Router::$PAGE_THUMBS_UPDATE) {
606 $ids = []; 606 header('Location: ./admin/thumbnails');
607 foreach ($bookmarkService->search() as $bookmark) {
608 // A note or not HTTP(S)
609 if ($bookmark->isNote() || ! startsWith(strtolower($bookmark->getUrl()), 'http')) {
610 continue;
611 }
612 $ids[] = $bookmark->getId();
613 }
614 $PAGE->assign('ids', $ids);
615 $PAGE->assign('pagetitle', t('Thumbnails update') .' - '. $conf->get('general.title', 'Shaarli'));
616 $PAGE->renderPage('thumbnails');
617 exit; 607 exit;
618 } 608 }
619 609
620 // -------- Single Thumbnail Update 610 // -------- Single Thumbnail Update
621 if ($targetPage == Router::$AJAX_THUMB_UPDATE) { 611 if ($targetPage == Router::$AJAX_THUMB_UPDATE) {
622 if (! isset($_POST['id']) || ! ctype_digit($_POST['id'])) { 612 // This route is no longer supported in legacy mode
623 http_response_code(400); 613 http_response_code(404);
624 exit;
625 }
626 $id = (int) $_POST['id'];
627 if (! $bookmarkService->exists($id)) {
628 http_response_code(404);
629 exit;
630 }
631 $thumbnailer = new Thumbnailer($conf);
632 $bookmark = $bookmarkService->get($id);
633 $bookmark->setThumbnail($thumbnailer->get($bookmark->getUrl()));
634 $bookmarkService->set($bookmark);
635
636 $factory = new FormatterFactory($conf, $loginManager->isLoggedIn());
637 echo json_encode($factory->getFormatter('raw')->format($bookmark));
638 exit; 614 exit;
639 } 615 }
640 616
@@ -971,6 +947,10 @@ $app->group('', function () {
971 $this->get('/admin/shaare/delete', '\Shaarli\Front\Controller\Admin\ManageShaareController:deleteBookmark'); 947 $this->get('/admin/shaare/delete', '\Shaarli\Front\Controller\Admin\ManageShaareController:deleteBookmark');
972 $this->get('/admin/shaare/visibility', '\Shaarli\Front\Controller\Admin\ManageShaareController:changeVisibility'); 948 $this->get('/admin/shaare/visibility', '\Shaarli\Front\Controller\Admin\ManageShaareController:changeVisibility');
973 $this->get('/admin/shaare/{id:[0-9]+}/pin', '\Shaarli\Front\Controller\Admin\ManageShaareController:pinBookmark'); 949 $this->get('/admin/shaare/{id:[0-9]+}/pin', '\Shaarli\Front\Controller\Admin\ManageShaareController:pinBookmark');
950 $this->patch(
951 '/admin/shaare/{id:[0-9]+}/update-thumbnail',
952 '\Shaarli\Front\Controller\Admin\ThumbnailsController:ajaxUpdate'
953 );
974 $this->get('/admin/export', '\Shaarli\Front\Controller\Admin\ExportController:index'); 954 $this->get('/admin/export', '\Shaarli\Front\Controller\Admin\ExportController:index');
975 $this->post('/admin/export', '\Shaarli\Front\Controller\Admin\ExportController:export'); 955 $this->post('/admin/export', '\Shaarli\Front\Controller\Admin\ExportController:export');
976 $this->get('/admin/import', '\Shaarli\Front\Controller\Admin\ImportController:index'); 956 $this->get('/admin/import', '\Shaarli\Front\Controller\Admin\ImportController:index');
@@ -978,6 +958,7 @@ $app->group('', function () {
978 $this->get('/admin/plugins', '\Shaarli\Front\Controller\Admin\PluginsController:index'); 958 $this->get('/admin/plugins', '\Shaarli\Front\Controller\Admin\PluginsController:index');
979 $this->post('/admin/plugins', '\Shaarli\Front\Controller\Admin\PluginsController:save'); 959 $this->post('/admin/plugins', '\Shaarli\Front\Controller\Admin\PluginsController:save');
980 $this->get('/admin/token', '\Shaarli\Front\Controller\Admin\TokenController:getToken'); 960 $this->get('/admin/token', '\Shaarli\Front\Controller\Admin\TokenController:getToken');
961 $this->get('/admin/thumbnails', '\Shaarli\Front\Controller\Admin\ThumbnailsController:index');
981 962
982 $this->get('/links-per-page', '\Shaarli\Front\Controller\Admin\SessionFilterController:linksPerPage'); 963 $this->get('/links-per-page', '\Shaarli\Front\Controller\Admin\SessionFilterController:linksPerPage');
983 $this->get('/visibility/{visibility}', '\Shaarli\Front\Controller\Admin\SessionFilterController:visibility'); 964 $this->get('/visibility/{visibility}', '\Shaarli\Front\Controller\Admin\SessionFilterController:visibility');
diff --git a/tests/front/controller/admin/ThumbnailsControllerTest.php b/tests/front/controller/admin/ThumbnailsControllerTest.php
new file mode 100644
index 00000000..0c0c8a83
--- /dev/null
+++ b/tests/front/controller/admin/ThumbnailsControllerTest.php
@@ -0,0 +1,154 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Shaarli\Front\Controller\Admin;
6
7use PHPUnit\Framework\TestCase;
8use Shaarli\Bookmark\Bookmark;
9use Shaarli\Bookmark\Exception\BookmarkNotFoundException;
10use Shaarli\Thumbnailer;
11use Slim\Http\Request;
12use Slim\Http\Response;
13
14class ThumbnailsControllerTest extends TestCase
15{
16 use FrontAdminControllerMockHelper;
17
18 /** @var ThumbnailsController */
19 protected $controller;
20
21 public function setUp(): void
22 {
23 $this->createContainer();
24
25 $this->controller = new ThumbnailsController($this->container);
26 }
27
28 /**
29 * Test displaying the thumbnails update page
30 * Note that only non-note and HTTP bookmarks should be returned.
31 */
32 public function testIndex(): void
33 {
34 $assignedVariables = [];
35 $this->assignTemplateVars($assignedVariables);
36
37 $request = $this->createMock(Request::class);
38 $response = new Response();
39
40 $this->container->bookmarkService
41 ->expects(static::once())
42 ->method('search')
43 ->willReturn([
44 (new Bookmark())->setId(1)->setUrl('http://url1.tld')->setTitle('Title 1'),
45 (new Bookmark())->setId(2)->setUrl('?abcdef')->setTitle('Note 1'),
46 (new Bookmark())->setId(3)->setUrl('http://url2.tld')->setTitle('Title 2'),
47 (new Bookmark())->setId(4)->setUrl('ftp://domain.tld', ['ftp'])->setTitle('FTP'),
48 ])
49 ;
50
51 $result = $this->controller->index($request, $response);
52
53 static::assertSame(200, $result->getStatusCode());
54 static::assertSame('thumbnails', (string) $result->getBody());
55
56 static::assertSame('Thumbnails update - Shaarli', $assignedVariables['pagetitle']);
57 static::assertSame([1, 3], $assignedVariables['ids']);
58 }
59
60 /**
61 * Test updating a bookmark thumbnail with valid parameters
62 */
63 public function testAjaxUpdateValid(): void
64 {
65 $request = $this->createMock(Request::class);
66 $response = new Response();
67
68 $bookmark = (new Bookmark())
69 ->setId($id = 123)
70 ->setUrl($url = 'http://url1.tld')
71 ->setTitle('Title 1')
72 ->setThumbnail(false)
73 ;
74
75 $this->container->thumbnailer = $this->createMock(Thumbnailer::class);
76 $this->container->thumbnailer
77 ->expects(static::once())
78 ->method('get')
79 ->with($url)
80 ->willReturn($thumb = 'http://img.tld/pic.png')
81 ;
82
83 $this->container->bookmarkService
84 ->expects(static::once())
85 ->method('get')
86 ->with($id)
87 ->willReturn($bookmark)
88 ;
89 $this->container->bookmarkService
90 ->expects(static::once())
91 ->method('set')
92 ->willReturnCallback(function (Bookmark $bookmark) use ($thumb) {
93 static::assertSame($thumb, $bookmark->getThumbnail());
94 })
95 ;
96
97 $result = $this->controller->ajaxUpdate($request, $response, ['id' => (string) $id]);
98
99 static::assertSame(200, $result->getStatusCode());
100
101 $payload = json_decode((string) $result->getBody(), true);
102
103 static::assertSame($id, $payload['id']);
104 static::assertSame($url, $payload['url']);
105 static::assertSame($thumb, $payload['thumbnail']);
106 }
107
108 /**
109 * Test updating a bookmark thumbnail - Invalid ID
110 */
111 public function testAjaxUpdateInvalidId(): void
112 {
113 $request = $this->createMock(Request::class);
114 $response = new Response();
115
116 $result = $this->controller->ajaxUpdate($request, $response, ['id' => 'nope']);
117
118 static::assertSame(400, $result->getStatusCode());
119 }
120
121 /**
122 * Test updating a bookmark thumbnail - No ID
123 */
124 public function testAjaxUpdateNoId(): void
125 {
126 $request = $this->createMock(Request::class);
127 $response = new Response();
128
129 $result = $this->controller->ajaxUpdate($request, $response, []);
130
131 static::assertSame(400, $result->getStatusCode());
132 }
133
134 /**
135 * Test updating a bookmark thumbnail with valid parameters
136 */
137 public function testAjaxUpdateBookmarkNotFound(): void
138 {
139 $id = 123;
140 $request = $this->createMock(Request::class);
141 $response = new Response();
142
143 $this->container->bookmarkService
144 ->expects(static::once())
145 ->method('get')
146 ->with($id)
147 ->willThrowException(new BookmarkNotFoundException())
148 ;
149
150 $result = $this->controller->ajaxUpdate($request, $response, ['id' => (string) $id]);
151
152 static::assertSame(404, $result->getStatusCode());
153 }
154}
diff --git a/tpl/default/configure.html b/tpl/default/configure.html
index fa1f7aa6..bb2564af 100644
--- a/tpl/default/configure.html
+++ b/tpl/default/configure.html
@@ -35,7 +35,7 @@
35 <div class="form-label"> 35 <div class="form-label">
36 <label for="titleLink"> 36 <label for="titleLink">
37 <span class="label-name">{'Home link'|t}</span><br> 37 <span class="label-name">{'Home link'|t}</span><br>
38 <span class="label-desc">{'Default value'|t}: {$base_path}</span> 38 <span class="label-desc">{'Default value'|t}: {$base_path}/</span>
39 </label> 39 </label>
40 </div> 40 </div>
41 </div> 41 </div>
@@ -289,7 +289,7 @@
289 {if="! $gd_enabled"} 289 {if="! $gd_enabled"}
290 {'You need to enable the extension <code>php-gd</code> to use thumbnails.'|t} 290 {'You need to enable the extension <code>php-gd</code> to use thumbnails.'|t}
291 {elseif="$thumbnails_enabled"} 291 {elseif="$thumbnails_enabled"}
292 <a href="{$base_path}/?do=thumbs_update">{'Synchronize thumbnails'|t}</a> 292 <a href="{$base_path}/admin/thumbnails">{'Synchronize thumbnails'|t}</a>
293 {/if} 293 {/if}
294 </span> 294 </span>
295 </label> 295 </label>
diff --git a/tpl/default/linklist.html b/tpl/default/linklist.html
index b0a5fdf2..8c1c2036 100644
--- a/tpl/default/linklist.html
+++ b/tpl/default/linklist.html
@@ -140,7 +140,7 @@
140 <div class="thumbnail"> 140 <div class="thumbnail">
141 {ignore}RainTPL hack: put the 2 src on two different line to avoid path replace bug{/ignore} 141 {ignore}RainTPL hack: put the 2 src on two different line to avoid path replace bug{/ignore}
142 <a href="{$value.real_url}" aria-hidden="true" tabindex="-1"> 142 <a href="{$value.real_url}" aria-hidden="true" tabindex="-1">
143 <img data-src="{$value.thumbnail}#" class="b-lazy" 143 <img data-src="{$base_path}/{$value.thumbnail}#" class="b-lazy"
144 src="" 144 src=""
145 alt="" width="{$thumbnails_width}" height="{$thumbnails_height}" /> 145 alt="" width="{$thumbnails_width}" height="{$thumbnails_height}" />
146 </a> 146 </a>
diff --git a/tpl/default/picwall.html b/tpl/default/picwall.html
index 1e97b366..3b5fccc3 100644
--- a/tpl/default/picwall.html
+++ b/tpl/default/picwall.html
@@ -9,7 +9,7 @@
9{if="count($linksToDisplay)===0 && $is_logged_in"} 9{if="count($linksToDisplay)===0 && $is_logged_in"}
10 <div class="pure-g pure-alert pure-alert-warning page-single-alert"> 10 <div class="pure-g pure-alert pure-alert-warning page-single-alert">
11 <div class="pure-u-1 center"> 11 <div class="pure-u-1 center">
12 {'There is no cached thumbnail. Try to <a href="{$base_path}/do=thumbs_update">synchronize them</a>.'|t} 12 {'There is no cached thumbnail. Try to <a href="{$base_path}/admin/thumbnails">synchronize them</a>.'|t}
13 </div> 13 </div>
14 </div> 14 </div>
15{/if} 15{/if}
diff --git a/tpl/default/tools.html b/tpl/default/tools.html
index 31f33a09..2cb08e38 100644
--- a/tpl/default/tools.html
+++ b/tpl/default/tools.html
@@ -47,7 +47,7 @@
47 47
48 {if="$thumbnails_enabled"} 48 {if="$thumbnails_enabled"}
49 <div class="tools-item"> 49 <div class="tools-item">
50 <a href="{$base_path}/?do=thumbs_update" title="{'Synchronize all link thumbnails'|t}"> 50 <a href="{$base_path}/admin/thumbnails" title="{'Synchronize all link thumbnails'|t}">
51 <span class="pure-button pure-u-lg-2-3 pure-u-3-4">{'Synchronize thumbnails'|t}</span> 51 <span class="pure-button pure-u-lg-2-3 pure-u-3-4">{'Synchronize thumbnails'|t}</span>
52 </a> 52 </a>
53 </div> 53 </div>
diff --git a/tpl/vintage/configure.html b/tpl/vintage/configure.html
index d04c69a9..c5861aae 100644
--- a/tpl/vintage/configure.html
+++ b/tpl/vintage/configure.html
@@ -159,7 +159,7 @@
159 {if="! $gd_enabled"} 159 {if="! $gd_enabled"}
160 {'You need to enable the extension <code>php-gd</code> to use thumbnails.'|t} 160 {'You need to enable the extension <code>php-gd</code> to use thumbnails.'|t}
161 {elseif="$thumbnails_enabled"} 161 {elseif="$thumbnails_enabled"}
162 <a href="{$base_path}/?do=thumbs_update">{'Synchonize thumbnails'|t}</a> 162 <a href="{$base_path}/admin/thumbnails">{'Synchonize thumbnails'|t}</a>
163 {/if} 163 {/if}
164 </label> 164 </label>
165 </td> 165 </td>