aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--application/Utils.php8
-rw-r--r--application/bookmark/BookmarkFilter.php2
-rw-r--r--application/front/controllers/DailyController.php142
-rw-r--r--index.php111
-rw-r--r--tests/front/controller/DailyControllerTest.php423
-rw-r--r--tpl/default/daily.html6
6 files changed, 577 insertions, 115 deletions
diff --git a/application/Utils.php b/application/Utils.php
index 4e97cdda..72c90049 100644
--- a/application/Utils.php
+++ b/application/Utils.php
@@ -294,15 +294,15 @@ function normalize_spaces($string)
294 * Requires php-intl to display international datetimes, 294 * Requires php-intl to display international datetimes,
295 * otherwise default format '%c' will be returned. 295 * otherwise default format '%c' will be returned.
296 * 296 *
297 * @param DateTime $date to format. 297 * @param DateTimeInterface $date to format.
298 * @param bool $time Displays time if true. 298 * @param bool $time Displays time if true.
299 * @param bool $intl Use international format if true. 299 * @param bool $intl Use international format if true.
300 * 300 *
301 * @return bool|string Formatted date, or false if the input is invalid. 301 * @return bool|string Formatted date, or false if the input is invalid.
302 */ 302 */
303function format_date($date, $time = true, $intl = true) 303function format_date($date, $time = true, $intl = true)
304{ 304{
305 if (! $date instanceof DateTime) { 305 if (! $date instanceof DateTimeInterface) {
306 return false; 306 return false;
307 } 307 }
308 308
diff --git a/application/bookmark/BookmarkFilter.php b/application/bookmark/BookmarkFilter.php
index fd556679..797a36b8 100644
--- a/application/bookmark/BookmarkFilter.php
+++ b/application/bookmark/BookmarkFilter.php
@@ -436,7 +436,7 @@ class BookmarkFilter
436 throw new Exception('Invalid date format'); 436 throw new Exception('Invalid date format');
437 } 437 }
438 438
439 $filtered = array(); 439 $filtered = [];
440 foreach ($this->bookmarks as $key => $l) { 440 foreach ($this->bookmarks as $key => $l) {
441 if ($l->getCreated()->format('Ymd') == $day) { 441 if ($l->getCreated()->format('Ymd') == $day) {
442 $filtered[$key] = $l; 442 $filtered[$key] = $l;
diff --git a/application/front/controllers/DailyController.php b/application/front/controllers/DailyController.php
new file mode 100644
index 00000000..c2fdaa55
--- /dev/null
+++ b/application/front/controllers/DailyController.php
@@ -0,0 +1,142 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Shaarli\Front\Controller;
6
7use DateTime;
8use Shaarli\Bookmark\Bookmark;
9use Slim\Http\Request;
10use Slim\Http\Response;
11
12/**
13 * Class DailyController
14 *
15 * Slim controller used to render the daily page.
16 *
17 * @package Front\Controller
18 */
19class DailyController extends ShaarliController
20{
21 /**
22 * Controller displaying all bookmarks published in a single day.
23 * It take a `day` date query parameter (format YYYYMMDD).
24 */
25 public function index(Request $request, Response $response): Response
26 {
27 $day = $request->getQueryParam('day') ?? date('Ymd');
28
29 $availableDates = $this->container->bookmarkService->days();
30 $nbAvailableDates = count($availableDates);
31 $index = array_search($day, $availableDates);
32
33 if ($index === false && $nbAvailableDates > 0) {
34 // no bookmarks for day, but at least one day with bookmarks
35 $index = $nbAvailableDates - 1;
36 $day = $availableDates[$index];
37 }
38
39 if ($day === date('Ymd')) {
40 $this->assignView('dayDesc', t('Today'));
41 } elseif ($day === date('Ymd', strtotime('-1 days'))) {
42 $this->assignView('dayDesc', t('Yesterday'));
43 }
44
45 if ($index !== false) {
46 if ($index >= 1) {
47 $previousDay = $availableDates[$index - 1];
48 }
49 if ($index < $nbAvailableDates - 1) {
50 $nextDay = $availableDates[$index + 1];
51 }
52 }
53
54 try {
55 $linksToDisplay = $this->container->bookmarkService->filterDay($day);
56 } catch (\Exception $exc) {
57 $linksToDisplay = [];
58 }
59
60 $formatter = $this->container->formatterFactory->getFormatter();
61 // We pre-format some fields for proper output.
62 foreach ($linksToDisplay as $key => $bookmark) {
63 $linksToDisplay[$key] = $formatter->format($bookmark);
64 // This page is a bit specific, we need raw description to calculate the length
65 $linksToDisplay[$key]['formatedDescription'] = $linksToDisplay[$key]['description'];
66 $linksToDisplay[$key]['description'] = $bookmark->getDescription();
67 }
68
69 $dayDate = DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, $day.'_000000');
70 $data = [
71 'linksToDisplay' => $linksToDisplay,
72 'day' => $dayDate->getTimestamp(),
73 'dayDate' => $dayDate,
74 'previousday' => $previousDay ?? '',
75 'nextday' => $nextDay ?? '',
76 ];
77
78 // Hooks are called before column construction so that plugins don't have to deal with columns.
79 $this->executeHooks($data);
80
81 $data['cols'] = $this->calculateColumns($data['linksToDisplay']);
82
83 foreach ($data as $key => $value) {
84 $this->assignView($key, $value);
85 }
86
87 $mainTitle = $this->container->conf->get('general.title', 'Shaarli');
88 $this->assignView(
89 'pagetitle',
90 t('Daily') .' - '. format_date($dayDate, false) . ' - ' . $mainTitle
91 );
92
93 return $response->write($this->render('daily'));
94 }
95
96 /**
97 * We need to spread the articles on 3 columns.
98 * did not want to use a JavaScript lib like http://masonry.desandro.com/
99 * so I manually spread entries with a simple method: I roughly evaluate the
100 * height of a div according to title and description length.
101 */
102 protected function calculateColumns(array $links): array
103 {
104 // Entries to display, for each column.
105 $columns = [[], [], []];
106 // Rough estimate of columns fill.
107 $fill = [0, 0, 0];
108 foreach ($links as $link) {
109 // Roughly estimate length of entry (by counting characters)
110 // Title: 30 chars = 1 line. 1 line is 30 pixels height.
111 // Description: 836 characters gives roughly 342 pixel height.
112 // This is not perfect, but it's usually OK.
113 $length = strlen($link['title'] ?? '') + (342 * strlen($link['description'] ?? '')) / 836;
114 if (! empty($link['thumbnail'])) {
115 $length += 100; // 1 thumbnails roughly takes 100 pixels height.
116 }
117 // Then put in column which is the less filled:
118 $smallest = min($fill); // find smallest value in array.
119 $index = array_search($smallest, $fill); // find index of this smallest value.
120 array_push($columns[$index], $link); // Put entry in this column.
121 $fill[$index] += $length;
122 }
123
124 return $columns;
125 }
126
127 /**
128 * @param mixed[] $data Variables passed to the template engine
129 *
130 * @return mixed[] Template data after active plugins render_picwall hook execution.
131 */
132 protected function executeHooks(array $data): array
133 {
134 $this->container->pluginManager->executeHooks(
135 'render_daily',
136 $data,
137 ['loggedin' => $this->container->loginManager->isLoggedIn()]
138 );
139
140 return $data;
141 }
142}
diff --git a/index.php b/index.php
index 89a1e581..f8337d79 100644
--- a/index.php
+++ b/index.php
@@ -399,112 +399,6 @@ function showDailyRSS($bookmarkService, $conf, $loginManager)
399} 399}
400 400
401/** 401/**
402 * Show the 'Daily' page.
403 *
404 * @param PageBuilder $pageBuilder Template engine wrapper.
405 * @param BookmarkServiceInterface $bookmarkService instance.
406 * @param ConfigManager $conf Configuration Manager instance.
407 * @param PluginManager $pluginManager Plugin Manager instance.
408 * @param LoginManager $loginManager Login Manager instance
409 */
410function showDaily($pageBuilder, $bookmarkService, $conf, $pluginManager, $loginManager)
411{
412 if (isset($_GET['day'])) {
413 $day = $_GET['day'];
414 if ($day === date('Ymd', strtotime('now'))) {
415 $pageBuilder->assign('dayDesc', t('Today'));
416 } elseif ($day === date('Ymd', strtotime('-1 days'))) {
417 $pageBuilder->assign('dayDesc', t('Yesterday'));
418 }
419 } else {
420 $day = date('Ymd', strtotime('now')); // Today, in format YYYYMMDD.
421 $pageBuilder->assign('dayDesc', t('Today'));
422 }
423
424 $days = $bookmarkService->days();
425 $i = array_search($day, $days);
426 if ($i === false && count($days)) {
427 // no bookmarks for day, but at least one day with bookmarks
428 $i = count($days) - 1;
429 $day = $days[$i];
430 }
431 $previousday = '';
432 $nextday = '';
433
434 if ($i !== false) {
435 if ($i >= 1) {
436 $previousday = $days[$i - 1];
437 }
438 if ($i < count($days) - 1) {
439 $nextday = $days[$i + 1];
440 }
441 }
442 try {
443 $linksToDisplay = $bookmarkService->filterDay($day);
444 } catch (Exception $exc) {
445 error_log($exc);
446 $linksToDisplay = [];
447 }
448
449 $factory = new FormatterFactory($conf, $loginManager->isLoggedIn());
450 $formatter = $factory->getFormatter();
451 // We pre-format some fields for proper output.
452 foreach ($linksToDisplay as $key => $bookmark) {
453 $linksToDisplay[$key] = $formatter->format($bookmark);
454 // This page is a bit specific, we need raw description to calculate the length
455 $linksToDisplay[$key]['formatedDescription'] = $linksToDisplay[$key]['description'];
456 $linksToDisplay[$key]['description'] = $bookmark->getDescription();
457 }
458
459 $dayDate = DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, $day.'_000000');
460 $data = array(
461 'pagetitle' => $conf->get('general.title') .' - '. format_date($dayDate, false),
462 'linksToDisplay' => $linksToDisplay,
463 'day' => $dayDate->getTimestamp(),
464 'dayDate' => $dayDate,
465 'previousday' => $previousday,
466 'nextday' => $nextday,
467 );
468
469 /* Hook is called before column construction so that plugins don't have
470 to deal with columns. */
471 $pluginManager->executeHooks('render_daily', $data, array('loggedin' => $loginManager->isLoggedIn()));
472
473 /* We need to spread the articles on 3 columns.
474 I did not want to use a JavaScript lib like http://masonry.desandro.com/
475 so I manually spread entries with a simple method: I roughly evaluate the
476 height of a div according to title and description length.
477 */
478 $columns = array(array(), array(), array()); // Entries to display, for each column.
479 $fill = array(0, 0, 0); // Rough estimate of columns fill.
480 foreach ($data['linksToDisplay'] as $key => $bookmark) {
481 // Roughly estimate length of entry (by counting characters)
482 // Title: 30 chars = 1 line. 1 line is 30 pixels height.
483 // Description: 836 characters gives roughly 342 pixel height.
484 // This is not perfect, but it's usually OK.
485 $length = strlen($bookmark['title']) + (342 * strlen($bookmark['description'])) / 836;
486 if (! empty($bookmark['thumbnail'])) {
487 $length += 100; // 1 thumbnails roughly takes 100 pixels height.
488 }
489 // Then put in column which is the less filled:
490 $smallest = min($fill); // find smallest value in array.
491 $index = array_search($smallest, $fill); // find index of this smallest value.
492 array_push($columns[$index], $bookmark); // Put entry in this column.
493 $fill[$index] += $length;
494 }
495
496 $data['cols'] = $columns;
497
498 foreach ($data as $key => $value) {
499 $pageBuilder->assign($key, $value);
500 }
501
502 $pageBuilder->assign('pagetitle', t('Daily') .' - '. $conf->get('general.title', 'Shaarli'));
503 $pageBuilder->renderPage('daily');
504 exit;
505}
506
507/**
508 * Renders the linklist 402 * Renders the linklist
509 * 403 *
510 * @param pageBuilder $PAGE pageBuilder instance. 404 * @param pageBuilder $PAGE pageBuilder instance.
@@ -628,7 +522,8 @@ function renderPage($conf, $pluginManager, $bookmarkService, $history, $sessionM
628 522
629 // Daily page. 523 // Daily page.
630 if ($targetPage == Router::$PAGE_DAILY) { 524 if ($targetPage == Router::$PAGE_DAILY) {
631 showDaily($PAGE, $bookmarkService, $conf, $pluginManager, $loginManager); 525 header('Location: ./daily');
526 exit;
632 } 527 }
633 528
634 // ATOM and RSS feed. 529 // ATOM and RSS feed.
@@ -1850,6 +1745,8 @@ $app->group('', function () {
1850 $this->get('/picture-wall', '\Shaarli\Front\Controller\PictureWallController:index')->setName('picwall'); 1745 $this->get('/picture-wall', '\Shaarli\Front\Controller\PictureWallController:index')->setName('picwall');
1851 $this->get('/tag-cloud', '\Shaarli\Front\Controller\TagCloudController:cloud')->setName('tagcloud'); 1746 $this->get('/tag-cloud', '\Shaarli\Front\Controller\TagCloudController:cloud')->setName('tagcloud');
1852 $this->get('/tag-list', '\Shaarli\Front\Controller\TagCloudController:list')->setName('taglist'); 1747 $this->get('/tag-list', '\Shaarli\Front\Controller\TagCloudController:list')->setName('taglist');
1748 $this->get('/daily', '\Shaarli\Front\Controller\DailyController:index')->setName('daily');
1749
1853 $this->get('/add-tag/{newTag}', '\Shaarli\Front\Controller\TagController:addTag')->setName('add-tag'); 1750 $this->get('/add-tag/{newTag}', '\Shaarli\Front\Controller\TagController:addTag')->setName('add-tag');
1854})->add('\Shaarli\Front\ShaarliMiddleware'); 1751})->add('\Shaarli\Front\ShaarliMiddleware');
1855 1752
diff --git a/tests/front/controller/DailyControllerTest.php b/tests/front/controller/DailyControllerTest.php
new file mode 100644
index 00000000..bb453d5b
--- /dev/null
+++ b/tests/front/controller/DailyControllerTest.php
@@ -0,0 +1,423 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Shaarli\Front\Controller;
6
7use PHPUnit\Framework\TestCase;
8use Shaarli\Bookmark\Bookmark;
9use Shaarli\Bookmark\BookmarkServiceInterface;
10use Shaarli\Config\ConfigManager;
11use Shaarli\Container\ShaarliContainer;
12use Shaarli\Formatter\BookmarkFormatter;
13use Shaarli\Formatter\BookmarkRawFormatter;
14use Shaarli\Formatter\FormatterFactory;
15use Shaarli\Plugin\PluginManager;
16use Shaarli\Render\PageBuilder;
17use Shaarli\Security\LoginManager;
18use Slim\Http\Request;
19use Slim\Http\Response;
20
21class DailyControllerTest extends TestCase
22{
23 /** @var ShaarliContainer */
24 protected $container;
25
26 /** @var DailyController */
27 protected $controller;
28
29 public function setUp(): void
30 {
31 $this->container = $this->createMock(ShaarliContainer::class);
32 $this->controller = new DailyController($this->container);
33 }
34
35 public function testValidControllerInvokeDefault(): void
36 {
37 $this->createValidContainerMockSet();
38
39 $currentDay = new \DateTimeImmutable('2020-05-13');
40
41 $request = $this->createMock(Request::class);
42 $request->method('getQueryParam')->willReturn($currentDay->format('Ymd'));
43 $response = new Response();
44
45 // Save RainTPL assigned variables
46 $assignedVariables = [];
47 $this->assignTemplateVars($assignedVariables);
48
49 // Links dataset: 2 links with thumbnails
50 $this->container->bookmarkService
51 ->expects(static::once())
52 ->method('days')
53 ->willReturnCallback(function () use ($currentDay): array {
54 return [
55 '20200510',
56 $currentDay->format('Ymd'),
57 '20200516',
58 ];
59 })
60 ;
61 $this->container->bookmarkService
62 ->expects(static::once())
63 ->method('filterDay')
64 ->willReturnCallback(function (): array {
65 return [
66 (new Bookmark())
67 ->setId(1)
68 ->setUrl('http://url.tld')
69 ->setTitle(static::generateContent(50))
70 ->setDescription(static::generateContent(500))
71 ,
72 (new Bookmark())
73 ->setId(2)
74 ->setUrl('http://url2.tld')
75 ->setTitle(static::generateContent(50))
76 ->setDescription(static::generateContent(500))
77 ,
78 (new Bookmark())
79 ->setId(3)
80 ->setUrl('http://url3.tld')
81 ->setTitle(static::generateContent(50))
82 ->setDescription(static::generateContent(500))
83 ,
84 ];
85 })
86 ;
87
88 // Make sure that PluginManager hook is triggered
89 $this->container->pluginManager
90 ->expects(static::at(0))
91 ->method('executeHooks')
92 ->willReturnCallback(function (string $hook, array $data, array $param) use ($currentDay): array {
93 static::assertSame('render_daily', $hook);
94
95 static::assertArrayHasKey('linksToDisplay', $data);
96 static::assertCount(3, $data['linksToDisplay']);
97 static::assertSame(1, $data['linksToDisplay'][0]['id']);
98 static::assertSame($currentDay->getTimestamp(), $data['day']);
99 static::assertSame('20200510', $data['previousday']);
100 static::assertSame('20200516', $data['nextday']);
101
102 static::assertArrayHasKey('loggedin', $param);
103
104 return $data;
105 });
106
107 $result = $this->controller->index($request, $response);
108
109 static::assertSame(200, $result->getStatusCode());
110 static::assertSame('daily', (string) $result->getBody());
111 static::assertSame(
112 'Daily - '. format_date($currentDay, false, true) .' - Shaarli',
113 $assignedVariables['pagetitle']
114 );
115 static::assertCount(3, $assignedVariables['linksToDisplay']);
116
117 $link = $assignedVariables['linksToDisplay'][0];
118
119 static::assertSame(1, $link['id']);
120 static::assertSame('http://url.tld', $link['url']);
121 static::assertNotEmpty($link['title']);
122 static::assertNotEmpty($link['description']);
123 static::assertNotEmpty($link['formatedDescription']);
124
125 $link = $assignedVariables['linksToDisplay'][1];
126
127 static::assertSame(2, $link['id']);
128 static::assertSame('http://url2.tld', $link['url']);
129 static::assertNotEmpty($link['title']);
130 static::assertNotEmpty($link['description']);
131 static::assertNotEmpty($link['formatedDescription']);
132
133 $link = $assignedVariables['linksToDisplay'][2];
134
135 static::assertSame(3, $link['id']);
136 static::assertSame('http://url3.tld', $link['url']);
137 static::assertNotEmpty($link['title']);
138 static::assertNotEmpty($link['description']);
139 static::assertNotEmpty($link['formatedDescription']);
140
141 static::assertCount(3, $assignedVariables['cols']);
142 static::assertCount(1, $assignedVariables['cols'][0]);
143 static::assertCount(1, $assignedVariables['cols'][1]);
144 static::assertCount(1, $assignedVariables['cols'][2]);
145
146 $link = $assignedVariables['cols'][0][0];
147
148 static::assertSame(1, $link['id']);
149 static::assertSame('http://url.tld', $link['url']);
150 static::assertNotEmpty($link['title']);
151 static::assertNotEmpty($link['description']);
152 static::assertNotEmpty($link['formatedDescription']);
153
154 $link = $assignedVariables['cols'][1][0];
155
156 static::assertSame(2, $link['id']);
157 static::assertSame('http://url2.tld', $link['url']);
158 static::assertNotEmpty($link['title']);
159 static::assertNotEmpty($link['description']);
160 static::assertNotEmpty($link['formatedDescription']);
161
162 $link = $assignedVariables['cols'][2][0];
163
164 static::assertSame(3, $link['id']);
165 static::assertSame('http://url3.tld', $link['url']);
166 static::assertNotEmpty($link['title']);
167 static::assertNotEmpty($link['description']);
168 static::assertNotEmpty($link['formatedDescription']);
169 }
170
171 /**
172 * Daily page - test that everything goes fine with no future or past bookmarks
173 */
174 public function testValidControllerInvokeNoFutureOrPast(): void
175 {
176 $this->createValidContainerMockSet();
177
178 $currentDay = new \DateTimeImmutable('2020-05-13');
179
180 $request = $this->createMock(Request::class);
181 $response = new Response();
182
183 // Save RainTPL assigned variables
184 $assignedVariables = [];
185 $this->assignTemplateVars($assignedVariables);
186
187 // Links dataset: 2 links with thumbnails
188 $this->container->bookmarkService
189 ->expects(static::once())
190 ->method('days')
191 ->willReturnCallback(function () use ($currentDay): array {
192 return [
193 $currentDay->format($currentDay->format('Ymd')),
194 ];
195 })
196 ;
197 $this->container->bookmarkService
198 ->expects(static::once())
199 ->method('filterDay')
200 ->willReturnCallback(function (): array {
201 return [
202 (new Bookmark())
203 ->setId(1)
204 ->setUrl('http://url.tld')
205 ->setTitle(static::generateContent(50))
206 ->setDescription(static::generateContent(500))
207 ,
208 ];
209 })
210 ;
211
212 // Make sure that PluginManager hook is triggered
213 $this->container->pluginManager
214 ->expects(static::at(0))
215 ->method('executeHooks')
216 ->willReturnCallback(function (string $hook, array $data, array $param) use ($currentDay): array {
217 static::assertSame('render_daily', $hook);
218
219 static::assertArrayHasKey('linksToDisplay', $data);
220 static::assertCount(1, $data['linksToDisplay']);
221 static::assertSame(1, $data['linksToDisplay'][0]['id']);
222 static::assertSame($currentDay->getTimestamp(), $data['day']);
223 static::assertEmpty($data['previousday']);
224 static::assertEmpty($data['nextday']);
225
226 static::assertArrayHasKey('loggedin', $param);
227
228 return $data;
229 });
230
231 $result = $this->controller->index($request, $response);
232
233 static::assertSame(200, $result->getStatusCode());
234 static::assertSame('daily', (string) $result->getBody());
235 static::assertSame(
236 'Daily - '. format_date($currentDay, false, true) .' - Shaarli',
237 $assignedVariables['pagetitle']
238 );
239 static::assertCount(1, $assignedVariables['linksToDisplay']);
240
241 $link = $assignedVariables['linksToDisplay'][0];
242 static::assertSame(1, $link['id']);
243 }
244
245 /**
246 * Daily page - test that height adjustment in columns is working
247 */
248 public function testValidControllerInvokeHeightAdjustment(): void
249 {
250 $this->createValidContainerMockSet();
251
252 $currentDay = new \DateTimeImmutable('2020-05-13');
253
254 $request = $this->createMock(Request::class);
255 $response = new Response();
256
257 // Save RainTPL assigned variables
258 $assignedVariables = [];
259 $this->assignTemplateVars($assignedVariables);
260
261 // Links dataset: 2 links with thumbnails
262 $this->container->bookmarkService
263 ->expects(static::once())
264 ->method('days')
265 ->willReturnCallback(function () use ($currentDay): array {
266 return [
267 $currentDay->format($currentDay->format('Ymd')),
268 ];
269 })
270 ;
271 $this->container->bookmarkService
272 ->expects(static::once())
273 ->method('filterDay')
274 ->willReturnCallback(function (): array {
275 return [
276 (new Bookmark())->setId(1)->setUrl('http://url.tld')->setTitle('title'),
277 (new Bookmark())
278 ->setId(2)
279 ->setUrl('http://url.tld')
280 ->setTitle(static::generateContent(50))
281 ->setDescription(static::generateContent(5000))
282 ,
283 (new Bookmark())->setId(3)->setUrl('http://url.tld')->setTitle('title'),
284 (new Bookmark())->setId(4)->setUrl('http://url.tld')->setTitle('title'),
285 (new Bookmark())->setId(5)->setUrl('http://url.tld')->setTitle('title'),
286 (new Bookmark())->setId(6)->setUrl('http://url.tld')->setTitle('title'),
287 (new Bookmark())->setId(7)->setUrl('http://url.tld')->setTitle('title'),
288 ];
289 })
290 ;
291
292 // Make sure that PluginManager hook is triggered
293 $this->container->pluginManager
294 ->expects(static::at(0))
295 ->method('executeHooks')
296 ->willReturnCallback(function (string $hook, array $data, array $param): array {
297 return $data;
298 })
299 ;
300
301 $result = $this->controller->index($request, $response);
302
303 static::assertSame(200, $result->getStatusCode());
304 static::assertSame('daily', (string) $result->getBody());
305 static::assertCount(7, $assignedVariables['linksToDisplay']);
306
307 $columnIds = function (array $column): array {
308 return array_map(function (array $item): int { return $item['id']; }, $column);
309 };
310
311 static::assertSame([1, 4, 6], $columnIds($assignedVariables['cols'][0]));
312 static::assertSame([2], $columnIds($assignedVariables['cols'][1]));
313 static::assertSame([3, 5, 7], $columnIds($assignedVariables['cols'][2]));
314 }
315
316 /**
317 * Daily page - no bookmark
318 */
319 public function testValidControllerInvokeNoBookmark(): void
320 {
321 $this->createValidContainerMockSet();
322
323 $request = $this->createMock(Request::class);
324 $response = new Response();
325
326 // Save RainTPL assigned variables
327 $assignedVariables = [];
328 $this->assignTemplateVars($assignedVariables);
329
330 // Links dataset: 2 links with thumbnails
331 $this->container->bookmarkService
332 ->expects(static::once())
333 ->method('days')
334 ->willReturnCallback(function (): array {
335 return [];
336 })
337 ;
338 $this->container->bookmarkService
339 ->expects(static::once())
340 ->method('filterDay')
341 ->willReturnCallback(function (): array {
342 return [];
343 })
344 ;
345
346 // Make sure that PluginManager hook is triggered
347 $this->container->pluginManager
348 ->expects(static::at(0))
349 ->method('executeHooks')
350 ->willReturnCallback(function (string $hook, array $data, array $param): array {
351 return $data;
352 })
353 ;
354
355 $result = $this->controller->index($request, $response);
356
357 static::assertSame(200, $result->getStatusCode());
358 static::assertSame('daily', (string) $result->getBody());
359 static::assertCount(0, $assignedVariables['linksToDisplay']);
360 static::assertSame('Today', $assignedVariables['dayDesc']);
361 }
362
363 protected function createValidContainerMockSet(): void
364 {
365 $loginManager = $this->createMock(LoginManager::class);
366 $this->container->loginManager = $loginManager;
367
368 // Config
369 $conf = $this->createMock(ConfigManager::class);
370 $this->container->conf = $conf;
371 $this->container->conf->method('get')->willReturnCallback(function (string $parameter, $default) {
372 return $default;
373 });
374
375 // PageBuilder
376 $pageBuilder = $this->createMock(PageBuilder::class);
377 $pageBuilder
378 ->method('render')
379 ->willReturnCallback(function (string $template): string {
380 return $template;
381 })
382 ;
383 $this->container->pageBuilder = $pageBuilder;
384
385 // Plugin Manager
386 $pluginManager = $this->createMock(PluginManager::class);
387 $this->container->pluginManager = $pluginManager;
388
389 // BookmarkService
390 $bookmarkService = $this->createMock(BookmarkServiceInterface::class);
391 $this->container->bookmarkService = $bookmarkService;
392
393 // Formatter
394 $formatterFactory = $this->createMock(FormatterFactory::class);
395 $formatterFactory
396 ->method('getFormatter')
397 ->willReturnCallback(function (): BookmarkFormatter {
398 return new BookmarkRawFormatter($this->container->conf, true);
399 })
400 ;
401 $this->container->formatterFactory = $formatterFactory;
402 }
403
404 protected function assignTemplateVars(array &$variables): void
405 {
406 $this->container->pageBuilder
407 ->expects(static::atLeastOnce())
408 ->method('assign')
409 ->willReturnCallback(function ($key, $value) use (&$variables) {
410 $variables[$key] = $value;
411
412 return $this;
413 })
414 ;
415 }
416
417 protected static function generateContent(int $length): string
418 {
419 // bin2hex(random_bytes) generates string twice as long as given parameter
420 $length = (int) ceil($length / 2);
421 return bin2hex(random_bytes($length));
422 }
423}
diff --git a/tpl/default/daily.html b/tpl/default/daily.html
index e2e7b47b..f07c0a8b 100644
--- a/tpl/default/daily.html
+++ b/tpl/default/daily.html
@@ -25,7 +25,7 @@
25 <div class="pure-g"> 25 <div class="pure-g">
26 <div class="pure-u-lg-1-3 pure-u-1 center"> 26 <div class="pure-u-lg-1-3 pure-u-1 center">
27 {if="$previousday"} 27 {if="$previousday"}
28 <a href="./?do=daily&amp;day={$previousday}"> 28 <a href="./daily?day={$previousday}">
29 <i class="fa fa-arrow-left"></i> 29 <i class="fa fa-arrow-left"></i>
30 {'Previous day'|t} 30 {'Previous day'|t}
31 </a> 31 </a>
@@ -36,7 +36,7 @@
36 </div> 36 </div>
37 <div class="pure-u-lg-1-3 pure-u-1 center"> 37 <div class="pure-u-lg-1-3 pure-u-1 center">
38 {if="$nextday"} 38 {if="$nextday"}
39 <a href="./?do=daily&amp;day={$nextday}"> 39 <a href="./daily?day={$nextday}">
40 {'Next day'|t} 40 {'Next day'|t}
41 <i class="fa fa-arrow-right"></i> 41 <i class="fa fa-arrow-right"></i>
42 </a> 42 </a>
@@ -69,7 +69,7 @@
69 {$link=$value} 69 {$link=$value}
70 <div class="daily-entry"> 70 <div class="daily-entry">
71 <div class="daily-entry-title center"> 71 <div class="daily-entry-title center">
72 <a href="?{$link.shorturl}" title="{'Permalink'|t}"> 72 <a href="./?{$link.shorturl}" title="{'Permalink'|t}">
73 <i class="fa fa-link"></i> 73 <i class="fa fa-link"></i>
74 </a> 74 </a>
75 <a href="{$link.real_url}">{$link.title}</a> 75 <a href="{$link.real_url}">{$link.title}</a>