Overview
Comment:Implement image/gallery block types
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | blocks
Files: files | file ages | folders
SHA3-256: ad1498501486aa88f2b733975cbfa95f12c1a8fcf0877eb688add121424a1c82
User & Date: bohwaz on 2022-05-03 23:12:01
Other Links: branch diff | manifest | tags
Context
2022-05-03
23:25
Missing CSS grid directive Closed-Leaf check-in: 6417b5b499 user: bohwaz tags: blocks
23:12
Implement image/gallery block types check-in: ad14985014 user: bohwaz tags: blocks
21:53
Hide blocks for now check-in: d83accac28 user: bohwaz tags: blocks
Changes

Modified src/include/lib/Garradin/Entities/Files/File.php from [6619ec3ce1] to [a96bc06098].

35
36
37
38
39
40
41



42




43
44
45
46
47
48
49
	protected $parent;

	/**
	 * File name
	 */
	protected $name;




	protected $path;




	protected $type = self::TYPE_FILE;
	protected $mime;
	protected $size;
	protected $modified;
	protected $image;

	protected $_types = [







>
>
>

>
>
>
>







35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
	protected $parent;

	/**
	 * File name
	 */
	protected $name;

	/**
	 * Complete file path (parent + '/' + name)
	 */
	protected $path;

	/**
	 * Type of file: file or directory
	 */
	protected $type = self::TYPE_FILE;
	protected $mime;
	protected $size;
	protected $modified;
	protected $image;

	protected $_types = [
584
585
586
587
588
589
590



591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608













609
610
611
612
613
614
615
			case UPLOAD_ERR_EXTENSION:
				return 'Une extension du serveur a interrompu l\'envoi du fichier.';
			default:
				return 'Erreur inconnue: ' . $error;
		}
	}




	public function url(bool $download = false): string
	{
		if ($this->context() == self::CONTEXT_WEB) {
			$path = Utils::basename(Utils::dirname($this->path)) . '/' . Utils::basename($this->path);
		}
		else {
			$path = $this->path;
		}


		$url = WWW_URL . $path;

		if ($download) {
			$url .= '?download';
		}

		return $url;
	}














	public function thumb_url($size = null): string
	{
		if (is_int($size)) {
			$size .= 'px';
		}








>
>
>


<
<
<
<
<
<
<
<
|







>
>
>
>
>
>
>
>
>
>
>
>
>







591
592
593
594
595
596
597
598
599
600
601
602








603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
			case UPLOAD_ERR_EXTENSION:
				return 'Une extension du serveur a interrompu l\'envoi du fichier.';
			default:
				return 'Erreur inconnue: ' . $error;
		}
	}

	/**
	 * Full URL with https://...
	 */
	public function url(bool $download = false): string
	{








		$url = WWW_URL . $this->uri();

		if ($download) {
			$url .= '?download';
		}

		return $url;
	}

	/**
	 * Returns local URI, eg. user/1245/file.jpg
	 */
	public function uri(): string
	{
		if ($this->context() == self::CONTEXT_WEB) {
			return Utils::basename(Utils::dirname($this->path)) . '/' . Utils::basename($this->path);
		}
		else {
			return $this->path;
		}
	}

	public function thumb_url($size = null): string
	{
		if (is_int($size)) {
			$size .= 'px';
		}

Modified src/include/lib/Garradin/Entities/Web/Page.php from [e5fc421c79] to [26ff90162d].

70
71
72
73
74
75
76

77
78
79
80
81
82
83
		self::TYPE_CATEGORY => 'category.html',
	];

	const DUPLICATE_URI_ERROR = 42;

	protected $_file;
	protected $_attachments;


	static public function create(int $type, ?string $parent, string $title, string $status = self::STATUS_ONLINE): self
	{
		$page = new self;
		$data = compact('type', 'parent', 'title', 'status');
		$data['content'] = '';








>







70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
		self::TYPE_CATEGORY => 'category.html',
	];

	const DUPLICATE_URI_ERROR = 42;

	protected $_file;
	protected $_attachments;
	protected $_tagged_attachments;

	static public function create(int $type, ?string $parent, string $title, string $status = self::STATUS_ONLINE): self
	{
		$page = new self;
		$data = compact('type', 'parent', 'title', 'status');
		$data['content'] = '';

324
325
326
327
328
329
330



331
332




333


334




335


336







337
338
339
340
341
342
343

			$this->_attachments = $list;
		}

		return $this->_attachments;
	}




	static public function findTaggedAttachments(string $text): array
	{




		preg_match_all('/<<?(?:file|image)\s*(?:\|\s*)?([\w\d_.-]+)/ui', $text, $match, PREG_PATTERN_ORDER);


		preg_match_all('/#(?:file|image):\[([\w\d_.-]+)\]/ui', $text, $match2, PREG_PATTERN_ORDER);







		return array_merge($match[1], $match2[1]);







	}

	/**
	 * Return list of images
	 * If $all is FALSE then this will only return images that are not present in the content
	 */
	public function getImageGallery(bool $all = true): array







>
>
>
|

>
>
>
>
|
>
>
|
>
>
>
>
|
>
>
|
>
>
>
>
>
>
>







325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366

			$this->_attachments = $list;
		}

		return $this->_attachments;
	}

	/**
	 * List attachments that are cited in the text content
	 */
	public function listTaggedAttachments(): array
	{
		if (null === $this->_tagged_attachments) {
			$this->render();
			$this->_tagged_attachments = Render::listAttachments($this->file());
		}

		return $this->_tagged_attachments;
	}

	/**
	 * List attachments that are *NOT* cited in the text content
	 */
	public function listOrphanAttachments(): array
	{
		$used = $this->listTaggedAttachements();
		$orphans = [];

		foreach ($this->listAttachments() as $file) {
			if (!in_array($file->uri(), $used)) {
				$orphans[] = $file->uri();
			}
		}

		return $orphans;
	}

	/**
	 * Return list of images
	 * If $all is FALSE then this will only return images that are not present in the content
	 */
	public function getImageGallery(bool $all = true): array
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
	 */
	public function getAttachmentsGallery(bool $all = true, bool $images = false): array
	{
		$out = [];
		$tagged = [];

		if (!$all) {
			$tagged = $this->findTaggedAttachments($this->content);
		}

		foreach ($this->listAttachments() as $a) {
			if ($images && !$a->image) {
				continue;
			}
			elseif (!$images && $a->image) {







|







374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
	 */
	public function getAttachmentsGallery(bool $all = true, bool $images = false): array
	{
		$out = [];
		$tagged = [];

		if (!$all) {
			$tagged = $this->listTaggedAttachments($this->content);
		}

		foreach ($this->listAttachments() as $a) {
			if ($images && !$a->image) {
				continue;
			}
			elseif (!$images && $a->image) {
388
389
390
391
392
393
394

395
396
397
398
399
400
401
402
403
404
405
406
407
408
409

		$out = '';

		foreach ($meta as $key => $value) {
			$out .= sprintf("%s: %s\n", $key, $value);
		}


		$out .= "\n----\n\n" . $this->content;

		return $out;
	}

	public function importFromRaw(string $str): bool
	{
		$str = preg_replace("/\r\n|\r|\n/", "\n", $str);
		$str = explode("\n\n----\n\n", $str, 2);

		if (count($str) !== 2) {
			return false;
		}

		list($meta, $content) = $str;







>
|






|







411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433

		$out = '';

		foreach ($meta as $key => $value) {
			$out .= sprintf("%s: %s\n", $key, $value);
		}

		$content = preg_replace("/\r\n?/", "\n", $this->content);
		$out .= "\n----\n\n" . $content;

		return $out;
	}

	public function importFromRaw(string $str): bool
	{
		$str = preg_replace("/\r\n?/", "\n", $str);
		$str = explode("\n\n----\n\n", $str, 2);

		if (count($str) !== 2) {
			return false;
		}

		list($meta, $content) = $str;

Modified src/include/lib/Garradin/UserTemplate/Sections.php from [72f65108cc] to [9f8ab7b332].

194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
			if (!$page) {
				return null;
			}

			// Store attachments in temp table
			$db = DB::getInstance();
			$db->begin();
			$db->exec('CREATE TEMP TABLE IF NOT EXISTS web_pages_attachments (page_id, path, name, modified, image);');
			$page_file_name = Utils::basename($page->file_path);

			foreach ($page->listAttachments() as $file) {
				if ($file->name == $page_file_name || $file->type != File::TYPE_FILE) {
					continue;
				}

				$db->preparedQuery('INSERT OR REPLACE INTO web_pages_attachments VALUES (?, ?, ?, ?, ?);',
					$page->id(), $file->path, $file->name, $file->modified, $file->image);
			}

			$db->commit();

			return $page;
		});








|







|
|







194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
			if (!$page) {
				return null;
			}

			// Store attachments in temp table
			$db = DB::getInstance();
			$db->begin();
			$db->exec('CREATE TEMP TABLE IF NOT EXISTS web_pages_attachments (page_id, uri, path, name, modified, image);');
			$page_file_name = Utils::basename($page->file_path);

			foreach ($page->listAttachments() as $file) {
				if ($file->name == $page_file_name || $file->type != File::TYPE_FILE) {
					continue;
				}

				$db->preparedQuery('INSERT OR REPLACE INTO web_pages_attachments VALUES (?, ?, ?, ?, ?, ?);',
					$page->id(), $file->uri(), $file->path, $file->name, $file->modified, $file->image);
			}

			$db->commit();

			return $page;
		});

230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
			// Don't regenerate that table for each section called in the page,
			// we assume the content and list of files will not change between sections
			self::cache('page_files_text_' . $parent, function () use ($page) {
				$db = DB::getInstance();
				$db->begin();

				// Put files mentioned in the text in a temporary table
				$db->exec('CREATE TEMP TABLE IF NOT EXISTS files_tmp_in_text (page_id, name);');

				foreach (Page::findTaggedAttachments($page->content) as $name) {
					$db->insert('files_tmp_in_text', ['page_id' => $page->id(), 'name' => $name]);
				}

				$db->commit();
			});

			$params['where'] .= sprintf(' AND name NOT IN (SELECT name FROM files_tmp_in_text WHERE page_id = %d)', $page->id());
		}

		if (empty($params['order'])) {
			$params['order'] = 'name';
		}

		if ($params['order'] == 'name') {







|

|
|





|







230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
			// Don't regenerate that table for each section called in the page,
			// we assume the content and list of files will not change between sections
			self::cache('page_files_text_' . $parent, function () use ($page) {
				$db = DB::getInstance();
				$db->begin();

				// Put files mentioned in the text in a temporary table
				$db->exec('CREATE TEMP TABLE IF NOT EXISTS files_tmp_in_text (page_id, uri);');

				foreach ($page->listTaggedAttachments() as $uri) {
					$db->insert('files_tmp_in_text', ['page_id' => $page->id(), 'uri' => $uri]);
				}

				$db->commit();
			});

			$params['where'] .= sprintf(' AND uri NOT IN (SELECT uri FROM files_tmp_in_text WHERE page_id = %d)', $page->id());
		}

		if (empty($params['order'])) {
			$params['order'] = 'name';
		}

		if ($params['order'] == 'name') {

Modified src/include/lib/Garradin/Web/Render/AbstractRender.php from [5c2f7cfe96] to [a7823fc02a].

26
27
28
29
30
31
32





33

34
35
36
37
38

39
40
41
42





43
44
45
46
47
48
49
50
		}

		$this->user_prefix = $user_prefix;
	}

	abstract public function render(?string $content = null): string;






	protected function resolveAttachment(string $uri) {

		$prefix = $this->current_path;
		$pos = strpos($uri, '/');

		// Absolute URL: treat it as absolute!
		if ($pos === 0) {

			return WWW_URL . ltrim($uri, '/');
		}

		// Handle relative URIs





		return WWW_URL . $prefix . '/' . $uri;
	}

	protected function resolveLink(string $uri) {
		$first = substr($uri, 0, 1);
		if ($first == '/' || $first == '!') {
			return Utils::getLocalURL($uri);
		}







>
>
>
>
>
|
>



<

>
|

|
|
>
>
>
>
>
|







26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
		}

		$this->user_prefix = $user_prefix;
	}

	abstract public function render(?string $content = null): string;

	public function registerAttachment(string $uri)
	{
		Render::registerAttachment($this->file, $uri);
	}

	protected function resolveAttachment(string $uri)
	{
		$prefix = $this->current_path;
		$pos = strpos($uri, '/');


		if ($pos === 0) {
			// Absolute URL: treat it as absolute!
			$uri = ltrim($uri, '/');
		}
		else {
			// Handle relative URIs
			$uri = $prefix . '/' . $uri;
		}

		$this->registerAttachment($uri);

		return WWW_URL . $uri;
	}

	protected function resolveLink(string $uri) {
		$first = substr($uri, 0, 1);
		if ($first == '/' || $first == '!') {
			return Utils::getLocalURL($uri);
		}

Modified src/include/lib/Garradin/Web/Render/Blocks.php from [968394d235] to [1286f52e69].

11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use Garradin\UserTemplate\CommonModifiers;

use Parsedown;
use Parsedown_Extra;

use const Garradin\{ADMIN_URL, WWW_URL};

class Blocks
{
	const SEPARATOR = "\n\n====\n\n";

	static protected $parsedown;
	protected $_stack = [];

	// Grid columns templates







|







11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use Garradin\UserTemplate\CommonModifiers;

use Parsedown;
use Parsedown_Extra;

use const Garradin\{ADMIN_URL, WWW_URL};

class Blocks extends AbstractRender
{
	const SEPARATOR = "\n\n====\n\n";

	static protected $parsedown;
	protected $_stack = [];

	// Grid columns templates
112
113
114
115
116
117
118
119

120
121

122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
			default:
				throw new \LogicException('Unknown type: ' . $type);
		}
	}

	public function image(string $content, array $meta): string
	{
		$url = WWW_URL . trim($content);

		$size = intval($meta['size'] ?? 0);


		$caption = !empty($meta['caption']) ? sprintf('<figcaption>%s</figcaption>', htmlspecialchars(trim($meta['caption']))) : '';

		if (!empty($meta['size'])) {
			return sprintf(
				'<figure><a href="%s"><img src="%s" alt="%s" /></a>%s</figure>',
				htmlspecialchars($url),
				htmlspecialchars(sprintf('%s?%dpx', $url, $size)),
				htmlspecialchars(trim($meta['caption'] ?? '')),
				$caption
			);
		}
		else {
			return sprintf(
				'<figure><img src="%s" alt="%s" />%s</figure>',
				$url,
				htmlspecialchars(trim($meta['caption'] ?? '')),
				$caption
			);
		}
	}

	public function gallery(string $content, array $meta): string
	{
		$images = explode("\n", trim($content));
		$out = '';

		foreach ($images as $image) {
			$image = explode('=', $image);
			$out .= $this->image($image[0], ['size' => 200, 'caption' => $image[1] ?? '']);
		}

		return $out;
	}
}







|
>


>
|






|
|






|
|










<
|





112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150

151
152
153
154
155
156
			default:
				throw new \LogicException('Unknown type: ' . $type);
		}
	}

	public function image(string $content, array $meta): string
	{
		$content = explode('|', $content);
		$url = $this->resolveAttachment(trim($content[0] ?? ''));
		$size = intval($meta['size'] ?? 0);

		$caption = htmlspecialchars(trim($content[1] ?? ''));
		$figcaption = $caption ? sprintf('<figcaption>%s</figcaption>', $caption) : '';

		if (!empty($meta['size'])) {
			return sprintf(
				'<figure><a href="%s"><img src="%s" alt="%s" /></a>%s</figure>',
				htmlspecialchars($url),
				htmlspecialchars(sprintf('%s?%dpx', $url, $size)),
				$caption,
				$figcaption
			);
		}
		else {
			return sprintf(
				'<figure><img src="%s" alt="%s" />%s</figure>',
				$url,
				$caption,
				$figcaption
			);
		}
	}

	public function gallery(string $content, array $meta): string
	{
		$images = explode("\n", trim($content));
		$out = '';

		foreach ($images as $image) {

			$out .= $this->image($image, ['size' => 200]);
		}

		return $out;
	}
}

Modified src/include/lib/Garradin/Web/Render/Render.php from [0aa02671bd] to [e445f0f98d].

1
2
3
4
5
6
7
8
9
10
11
12


13
14
15





16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31




32
33
34













<?php

namespace Garradin\Web\Render;

use Garradin\Entities\Files\File;

class Render
{
	const FORMAT_SKRIV = 'skriv';
	const FORMAT_ENCRYPTED = 'skriv/encrypted';
	const FORMAT_MARKDOWN = 'markdown';
	const FORMAT_BLOCKS = 'blocks';



	static public function render(string $format, File $file, string $content = null, string $link_prefix = null)
	{





		if ($format == self::FORMAT_SKRIV) {
			$r = new Skriv($file, $link_prefix);
		}
		else if ($format == self::FORMAT_ENCRYPTED) {
			$r = new EncryptedSkriv($file, $link_prefix);
		}
		else if ($format == self::FORMAT_MARKDOWN) {
			$r = new Markdown($file, $link_prefix);
		}
		else if ($format == self::FORMAT_BLOCKS) {
			$r = new Blocks($file, $link_prefix);
		}
		else {
			throw new \LogicException('Invalid format: ' . $format);
		}





		return $r->render($content);
	}
}

























>
>



>
>
>
>
>

|


|


|


|




|
>
>
>
>
|
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<?php

namespace Garradin\Web\Render;

use Garradin\Entities\Files\File;

class Render
{
	const FORMAT_SKRIV = 'skriv';
	const FORMAT_ENCRYPTED = 'skriv/encrypted';
	const FORMAT_MARKDOWN = 'markdown';
	const FORMAT_BLOCKS = 'blocks';

	static protected $attachments = [];

	static public function render(string $format, File $file, string $content = null, string $link_prefix = null)
	{
		return self::getRenderer($format, $file, $link_prefix)->render($content);
	}

	static public function getRenderer(string $format, File $file, string $link_prefix = null)
	{
		if ($format == self::FORMAT_SKRIV) {
			return new Skriv($file, $link_prefix);
		}
		else if ($format == self::FORMAT_ENCRYPTED) {
			return new EncryptedSkriv($file, $link_prefix);
		}
		else if ($format == self::FORMAT_MARKDOWN) {
			return new Markdown($file, $link_prefix);
		}
		else if ($format == self::FORMAT_BLOCKS) {
			return new Blocks($file, $link_prefix);
		}
		else {
			throw new \LogicException('Invalid format: ' . $format);
		}
	}

	static public function registerAttachment(?File $file, string $uri): void
	{
		if (null === $file) {
			return;
		}

		$hash = $file->pathHash();

		if (!array_key_exists($hash, self::$attachments)) {
			self::$attachments[$hash] = [];
		}

		self::$attachments[$hash][$uri] = true;
	}

	static public function listAttachments(File $file) {
		return array_keys(self::$attachments[$file->pathHash()] ?? []);
	}
}