Overview
Comment:New file storage is working
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | dev
Files: files | file ages | folders
SHA1: 0e2782726924e82d51fefbe886440a0b0e6874c3
User & Date: bohwaz on 2020-12-13 15:34:35
Other Links: branch diff | manifest | tags
Context
2020-12-13
16:11
Reflect changes in config check-in: c83f057f54 user: bohwaz tags: dev
15:34
New file storage is working check-in: 0e27827269 user: bohwaz tags: dev
00:58
Progress on the rendering side of files check-in: 049b3cf40d user: bohwaz tags: dev
Changes

Modified src/include/data/1.1.0_migration.sql from [b636c3139c] to [0607d500c4].

1
2
3
4






5
6
7
8
9
10
11
ALTER TABLE membres_categories RENAME TO membres_categories_old;

.read 1.1.0_schema.sql







INSERT INTO membres_categories
	SELECT id, nom,
		droit_wiki, -- droit_web
		droit_wiki, -- droit_documents
		droit_membres,
		droit_compta,
		droit_inscription,




>
>
>
>
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ALTER TABLE membres_categories RENAME TO membres_categories_old;

.read 1.1.0_schema.sql

INSERT INTO files_contents (id, hash, content)
	SELECT id, hash, contenu FROM fichiers_contenu;

INSERT INTO files (id, hash, folder_id, name, type, created, content_id, author_id, public)
	SELECT f.id, c.hash, NULL, nom, type, datetime, c.id, NULL, 0 FROM fichiers f INNER JOIN fichiers_contenu c ON c.id = f.id_contenu;

INSERT INTO membres_categories
	SELECT id, nom,
		droit_wiki, -- droit_web
		droit_wiki, -- droit_documents
		droit_membres,
		droit_compta,
		droit_inscription,
85
86
87
88
89
90
91


92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114




INSERT INTO files_links (id, transaction_id)
	SELECT fichier, id FROM fichiers_acc_transactions;

INSERT INTO files_links (id, config)
	SELECT valeur, cle FROM config WHERE cle = 'image_fond' AND valeur > 0;



INSERT INTO files (hash, folder_id, name, type, created, content_id, author_id, public)
	SELECT hash, NULL, name, type, created, content_id, author_id, 0
	FROM files WHERE id = (SELECT new_id FROM wiki_as_files WHERE uri = (SELECT valeur FROM config WHERE cle = 'accueil_connexion'));

UPDATE config SET valeur = (SELECT id FROM files WHERE name = 'Accueil_connexion.skriv') WHERE cle = 'accueil_connexion';
UPDATE config SET cle = 'admin_homepage' WHERE cle = 'accueil_connexion';
DELETE FROM config WHERE cle = 'accueil_wiki';
INSERT INTO config (cle, valeur) VALUES ('telephone_asso', NULL);


DROP TRIGGER wiki_recherche_delete;
DROP TRIGGER wiki_recherche_update;
DROP TRIGGER wiki_recherche_contenu_insert;
DROP TRIGGER wiki_recherche_contenu_chiffre;

DROP TABLE wiki_recherche;

DROP TABLE wiki_pages;
DROP TABLE wiki_revisions;

DROP TABLE fichiers_wiki_pages;
DROP TABLE fichiers_acc_transactions;
DROP TABLE fichiers_membres;










>
>








<














>
>
>
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107

108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124

INSERT INTO files_links (id, transaction_id)
	SELECT fichier, id FROM fichiers_acc_transactions;

INSERT INTO files_links (id, config)
	SELECT valeur, cle FROM config WHERE cle = 'image_fond' AND valeur > 0;

UPDATE files SET public = 1 WHERE id = (SELECT valeur FROM config WHERE cle = 'image_fond');

INSERT INTO files (hash, folder_id, name, type, created, content_id, author_id, public)
	SELECT hash, NULL, name, type, created, content_id, author_id, 0
	FROM files WHERE id = (SELECT new_id FROM wiki_as_files WHERE uri = (SELECT valeur FROM config WHERE cle = 'accueil_connexion'));

UPDATE config SET valeur = (SELECT id FROM files WHERE name = 'Accueil_connexion.skriv') WHERE cle = 'accueil_connexion';
UPDATE config SET cle = 'admin_homepage' WHERE cle = 'accueil_connexion';
DELETE FROM config WHERE cle = 'accueil_wiki';
INSERT INTO config (cle, valeur) VALUES ('telephone_asso', NULL);


DROP TRIGGER wiki_recherche_delete;
DROP TRIGGER wiki_recherche_update;
DROP TRIGGER wiki_recherche_contenu_insert;
DROP TRIGGER wiki_recherche_contenu_chiffre;

DROP TABLE wiki_recherche;

DROP TABLE wiki_pages;
DROP TABLE wiki_revisions;

DROP TABLE fichiers_wiki_pages;
DROP TABLE fichiers_acc_transactions;
DROP TABLE fichiers_membres;

DROP TABLE fichiers;
DROP TABLE fichiers_contenu;

Modified src/include/init.php from [e6355f8bdd] to [2c68efc0c5].

162
163
164
165
166
167
168



169
170
171
172
173
174
175
	'SMTP_PORT'             => 587,
	'SMTP_SECURITY'         => 'STARTTLS',
	'ADMIN_URL'             => WWW_URL . 'admin/',
	'NTP_SERVER'            => 'fr.pool.ntp.org',
	'ENABLE_AUTOMATIC_BACKUPS' => true,
	'ADMIN_COLOR1'          => '#9c4f15',
	'ADMIN_COLOR2'          => '#d98628',



];

foreach ($default_config as $const => $value)
{
	$const = sprintf('Garradin\\%s', $const);

	if (!defined($const))







>
>
>







162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
	'SMTP_PORT'             => 587,
	'SMTP_SECURITY'         => 'STARTTLS',
	'ADMIN_URL'             => WWW_URL . 'admin/',
	'NTP_SERVER'            => 'fr.pool.ntp.org',
	'ENABLE_AUTOMATIC_BACKUPS' => true,
	'ADMIN_COLOR1'          => '#9c4f15',
	'ADMIN_COLOR2'          => '#d98628',
	'FILE_STORAGE_BACKEND'  => null,
	'FILE_STORAGE_CONFIG'   => [],
	'FILE_STORAGE_QUOTA'    => null,
];

foreach ($default_config as $const => $value)
{
	$const = sprintf('Garradin\\%s', $const);

	if (!defined($const))

Modified src/include/lib/Garradin/Entities/Files/File.php from [e4f0a7a0d2] to [f144751b28].

1
2
3
4
5

6
7
8



9


10
11
12
13
14
15
16
17
<?php

namespace Garradin\Entities\Files;

use KD2\Image;

use Garradin\DB;
use Garradin\Entity;
use Garradin\UserException;






use const Garradin\WWW_URL;

class File extends Entity
{
	const TABLE = 'files';

	protected $id;
	protected $folder_id;





>



>
>
>

>
>
|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php

namespace Garradin\Entities\Files;

use KD2\Image;

use Garradin\DB;
use Garradin\Entity;
use Garradin\UserException;
use Garradin\Membres\Session;
use Garradin\Membres;
use Garradin\Utils;

use Garradin\Files\Files;

use const Garradin\{WWW_URL, ENABLE_XSENDFILE};

class File extends Entity
{
	const TABLE = 'files';

	protected $id;
	protected $folder_id;
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
		'storage'      => '?string',
		'storage_path' => '?string',
		'created'      => 'DateTime',
		'author_id'    => '?int',
		'content_id'   => '?int',
	];

	protected $_public;

	/**
	 * Tailles de miniatures autorisées, pour ne pas avoir 500 fichiers générés avec 500 tailles différentes
	 * @var array
	 */
	const ALLOWED_THUMB_SIZES = [200, 500, 1200];

	// Link to another file (ie. image included in a HTML file)







<
<







48
49
50
51
52
53
54


55
56
57
58
59
60
61
		'storage'      => '?string',
		'storage_path' => '?string',
		'created'      => 'DateTime',
		'author_id'    => '?int',
		'content_id'   => '?int',
	];



	/**
	 * Tailles de miniatures autorisées, pour ne pas avoir 500 fichiers générés avec 500 tailles différentes
	 * @var array
	 */
	const ALLOWED_THUMB_SIZES = [200, 500, 1200];

	// Link to another file (ie. image included in a HTML file)
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
367
	}

	/**
	 * Envoie le fichier au client HTTP
	 */
	public function serve(?Session $session = null): void
	{
		if (!$this->checkAccess($session)) {
			header('HTTP/1.1 403 Forbidden', true, 403);
			throw new UserException('Accès interdit');
			return;
		}

		$path = Files::callStorage('getPath', $this);
		$content = null === $path ? Files::callStorage('fetch', $this) : null;

		$this->_serve($session, $path, $content);
	}

	/**
	 * Envoie une miniature à la taille indiquée au client HTTP
	 */
	public function serveThumbnail(?Session $session = null, ?int $width = null): void
	{
		if (!$this->checkAccess($session)) {
			header('HTTP/1.1 403 Forbidden', true, 403);
			throw new UserException('Accès interdit');
			return;
		}

		if (!$this->image) {
			throw new UserException('Il n\'est pas possible de fournir une miniature pour un fichier qui n\'est pas une image.');







|

|






|







|







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
367
368
369
370
371
	}

	/**
	 * Envoie le fichier au client HTTP
	 */
	public function serve(?Session $session = null): void
	{
		if (!$this->checkReadAccess($session)) {
			header('HTTP/1.1 403 Forbidden', true, 403);
			throw new UserException('Vous n\'avez pas accès à ce fichier.');
			return;
		}

		$path = Files::callStorage('getPath', $this);
		$content = null === $path ? Files::callStorage('fetch', $this) : null;

		$this->_serve($path, $content);
	}

	/**
	 * Envoie une miniature à la taille indiquée au client HTTP
	 */
	public function serveThumbnail(?Session $session = null, ?int $width = null): void
	{
		if (!$this->checkReadAccess($session)) {
			header('HTTP/1.1 403 Forbidden', true, 403);
			throw new UserException('Accès interdit');
			return;
		}

		if (!$this->image) {
			throw new UserException('Il n\'est pas possible de fournir une miniature pour un fichier qui n\'est pas une image.');
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
				}
			}
			catch (\RuntimeException $e) {
				throw new UserException('Impossible de créer la miniature');
			}
		}

		$this->_serve($session, $path, null);
	}

	/**
	 * Servir un fichier local en HTTP
	 * @param  string $path Chemin vers le fichier local
	 * @param  string $type Type MIME du fichier
	 * @param  string $name Nom du fichier avec extension
	 * @param  integer $size Taille du fichier en octets (facultatif)
	 * @return boolean TRUE en cas de succès
	 */
	protected function _serve(?string $path, ?string $content): void
	{
		if ($this->isPublic()) {
			Utils::HTTPCache($this->hash, $this->datetime);
		}
		else {
			// Disable browser cache
			header('Pragma: private');
			header('Expires: -1');
			header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0');
		}







|












|
|







397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
				}
			}
			catch (\RuntimeException $e) {
				throw new UserException('Impossible de créer la miniature');
			}
		}

		$this->_serve($path, null);
	}

	/**
	 * Servir un fichier local en HTTP
	 * @param  string $path Chemin vers le fichier local
	 * @param  string $type Type MIME du fichier
	 * @param  string $name Nom du fichier avec extension
	 * @param  integer $size Taille du fichier en octets (facultatif)
	 * @return boolean TRUE en cas de succès
	 */
	protected function _serve(?string $path, ?string $content): void
	{
		if ($this->public) {
			Utils::HTTPCache($this->hash, $this->created->getTimestamp());
		}
		else {
			// Disable browser cache
			header('Pragma: private');
			header('Expires: -1');
			header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0');
		}
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
		elseif ($this->type == 'text/vnd.skriv.encrypted') {
			return \Garradin\Files\Render\EncryptedSkriv::render($this, $content);
		}

		throw new \LogicException('Unknown render type');
	}

	public function isPublic(): bool
	{
		if (null === $this->_public) {
			throw new \RuntimeException('_public is unset');
		}

		return $this->_public;
	}

	public function checkAccess(Session $session): bool
	{
		$link = DB::getInstance()->first('SELECT * FROM files_links WHERE id = ?;', $this->id());

		// If it's linked to a file, then we want to know what the parent file is linked to
		if ($link->{LINK_FILE}) {
			$link = DB::getInstance()->first('SELECT * FROM files_links WHERE id = ?;', $link->{LINK_FILE});
		}

		$this->_public = false;

		// Everyone has access to web content as long it's not draft (0)
		if ($link->{LINK_WEB} == 1) {
			$this->_public = true;
			return true;
		}
		elseif ($link->{LINK_WEB} == 0) {
			return false;
		}
		// Everyone has access to config files (logo etc.)
		else if ($link->{LINK_CONFIG}) {
			$this->_public = true;
			return true;
		}
		else if ($link->{LINK_TRANSACTION} && $session->canAccess('compta', Membres::DROIT_ACCES)) {
			return true;
		}
		// The user can access his own profile files
		else if ($link->{LINK_USER} && $link->{LINK_USER} == $session->getUser()->id) {
			return true;
		}
		// Only users able to manage users can see their profile files
		else if ($link->{LINK_USER} && $session->canAccess('membres', Membres::DROIT_ECRITURE)) {
			return true;
		}

		return $session->canAccess(Session::SECTION_DOCUMENTS, Membres::DROIT_ACCES);
	}
}







|

|
<
<
|
|
|

<
<



|



<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|



|



|






487
488
489
490
491
492
493
494
495
496


497
498
499
500


501
502
503
504
505
506
507















508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
		elseif ($this->type == 'text/vnd.skriv.encrypted') {
			return \Garradin\Files\Render\EncryptedSkriv::render($this, $content);
		}

		throw new \LogicException('Unknown render type');
	}

	public function checkReadAccess(Session $session): bool
	{
		// Web and config files should be marked as public when not in draft


		if ($this->public) {
			return true;
		}



		$link = DB::getInstance()->first('SELECT * FROM files_links WHERE id = ?;', $this->id());

		// If it's linked to a file, then we want to know what the parent file is linked to
		if ($link->{self::LINK_FILE}) {
			$link = DB::getInstance()->first('SELECT * FROM files_links WHERE id = ?;', $link->{LINK_FILE});
		}
















		if ($link->{self::LINK_TRANSACTION} && $session->canAccess(Session::SECTION_ACCOUNTING, Membres::DROIT_ACCES)) {
			return true;
		}
		// The user can access his own profile files
		else if ($link->{self::LINK_USER} && $link->{self::LINK_USER} == $session->getUser()->id) {
			return true;
		}
		// Only users able to manage users can see their profile files
		else if ($link->{self::LINK_USER} && $session->canAccess(Session::SECTION_USERS, Membres::DROIT_ECRITURE)) {
			return true;
		}

		return $session->canAccess(Session::SECTION_DOCUMENTS, Membres::DROIT_ACCES);
	}
}

Modified src/include/lib/Garradin/Files/Files.php from [c4da787241] to [f113720eda].

1
2
3
4
5
6

7
8
9
10
11
12
13
14
15












16
17
18
19
20
21
22
23
24
<?php

namespace Garradin\Files;

use Garradin\Static_Cache;
use Garradin\DB;

use Garradin\Entities\Files\File;
use KD2\DB\EntityManager as EM;

use const Garradin\FILE_STORAGE_BACKEND;

class Files
{
	static public function callStorage(string $function, ...$args)
	{












		$storage = FILE_STORAGE_BACKEND ?? 'SQLite';
		$class_name = get_class(__NAMESPACE__ . '\\Backend\\' . $storage);
		return call_user_func_array([$class_name, $function], $args);
	}

	static public function migrateStorage(string $from, string $to): void
	{
		$res = EM::getInstance(File::class)->iterate('SELECT * FROM @TABLE;');







>









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

|







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
<?php

namespace Garradin\Files;

use Garradin\Static_Cache;
use Garradin\DB;
use Garradin\Membres\Session;
use Garradin\Entities\Files\File;
use KD2\DB\EntityManager as EM;

use const Garradin\FILE_STORAGE_BACKEND;

class Files
{
	static public function callStorage(string $function, ...$args)
	{
		// Check that we can store this data
		if ($function == 'store') {
			$quota = FILE_STORAGE_QUOTA ?: self::callStorage('getQuota');
			$used = self::callStorage('getTotalSize');

			$size = $args[0] ? filesize($args[0]) : strlen($args[1]);

			if (($used + $size) >= $quota) {
				throw new \OutOfBoundsException('File quota has been exhausted');
			}
		}

		$storage = FILE_STORAGE_BACKEND ?? 'SQLite';
		$class_name = __NAMESPACE__ . '\\Storage\\' . $storage;
		return call_user_func_array([$class_name, $function], $args);
	}

	static public function migrateStorage(string $from, string $to): void
	{
		$res = EM::getInstance(File::class)->iterate('SELECT * FROM @TABLE;');

76
77
78
79
80
81
82
83

84





































		}
	}

	static public function get(int $id): ?File
	{
		return EM::findOneById(File::class, $id);
	}
}














































|
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
		}
	}

	static public function get(int $id): ?File
	{
		return EM::findOneById(File::class, $id);
	}

	static public function serveFromQueryString(): void
	{
		$id = isset($_GET['id']) ? $_GET['id'] : null;
		$filename = !empty($_GET['file']) ? $_GET['file'] : null;

		$size = null;

		if (empty($id)) {
			header('HTTP/1.1 404 Not Found', true, 404);
			throw new UserException('Fichier inconnu.');
		}

		foreach ($_GET as $key => $value) {
			if (substr($key, -2) == 'px') {
				$size = (int)substr($key, 0, -2);
				break;
			}
		}

		$id = base_convert($id, 36, 10);

		$file = self::get((int) $id);

		if (!$file) {
			header('HTTP/1.1 404 Not Found', true, 404);
			throw new UserException('Ce fichier n\'existe pas.');
		}

		$session = Session::getInstance();

		if ($size) {
			$file->serveThumbnail($session, $size);
		}
		else {
			$file->serve($session);
		}
	}
}

Modified src/include/lib/Garradin/Files/Storage/FileSystem.php from [e05f9c9e78] to [302efe0368].

1
2
3


4
5
6
7
8
9
10
<?php

namespace Garradin\Files\Storage;



use const Garradin\FILE_STORAGE_CONFIG;

/**
 * This class provides storage in the file system
 * You need ton configure FILE_STORAGE_CONFIG to give a file path
 */



>
>







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

namespace Garradin\Files\Storage;

use Garradin\Entities\Files\File;

use const Garradin\FILE_STORAGE_CONFIG;

/**
 * This class provides storage in the file system
 * You need ton configure FILE_STORAGE_CONFIG to give a file path
 */
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
		}

		self::$_size = (int) $total;

		return self::$_size;
	}

	static public function getRemainingQuota(): int
	{
		return disk_free_space(self::_getRoot());
	}

	static public function getQuota(): int
	{
		return disk_total_space(self::_getRoot());
	}
}







<
<
<
<
<





115
116
117
118
119
120
121





122
123
124
125
126
		}

		self::$_size = (int) $total;

		return self::$_size;
	}






	static public function getQuota(): int
	{
		return disk_total_space(self::_getRoot());
	}
}

Deleted src/include/lib/Garradin/Files/Storage/FileSystemQuota.php version [66a57977b0].

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
<?php

namespace Garradin\Files\Storage;

use const Garradin\FILE_STORAGE_CONFIG;

/**
 * This class provides storage, same as FileSystem,
 * but adds the ability to define a custom quota.
 * To that end, just append ;quota=XXX to FILE_STORAGE_CONFIG
 * where XXX is the maximum storage allowed for that user, in bytes
 */
class FileSystemQuota extends FileSystem
{
	static protected $quota;
	static protected $root;

	static protected function _getRoot()
	{
		if (null === self::$root) {
			if (!FILE_STORAGE_CONFIG) {
				throw new \RuntimeException('Le stockage de fichier n\'a pas été configuré (FILE_STORAGE_CONFIG est vide).');
			}

			$target = strtok(FILE_STORAGE_CONFIG, ';');

			if (!is_writable($target)) {
				throw new \RuntimeException('Le répertoire de stockage des fichiers est protégé contre l\'écriture.');
			}

			strtok('=');
			$size = (int) strtok('');

			if (!$size) {
				throw new \RuntimeException('Aucun quota indiqué dans FILE_STORAGE_CONFIG');
			}

			$target = rtrim($target, DIRECTORY_SEPARATOR);

			self::$root = realpath($target);
			self::$quota = $size;
		}

		return self::$root;
	}

	static public function getRemainingQuota(): int
	{
		return self::getTotalSize() - self::getQuota();
	}

	static public function getQuota(): int
	{
		self::_getRoot(); // Make sure quota is loaded
		return self::$quota;
	}
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


















































































































Modified src/include/lib/Garradin/Files/Storage/SQLite.php from [e6d2cf42a4] to [bb87054c4e].

1
2
3
4


5
6
7
8
9
10
11
<?php

namespace Garradin\Files\Storage;



use Garradin\Static_Cache;
use Garradin\DB;

class SQLite implements StorageInterface
{
	/**
	 * Renvoie le chemin vers le fichier local en cache, et le crée s'il n'existe pas




>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
<?php

namespace Garradin\Files\Storage;

use Garradin\Entities\Files\File;

use Garradin\Static_Cache;
use Garradin\DB;

class SQLite implements StorageInterface
{
	/**
	 * Renvoie le chemin vers le fichier local en cache, et le crée s'il n'existe pas
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
	}

	static public function move(File $old_file, File $new_file): bool
	{
		return true;
	}

	static public function getTotalSize(): ?int
	{
		return (int) DB::getInstance()->firstColumn('SELECT SUM(size) FROM files_contents;');
	}

	static public function getRemainingQuota(): int
	{
		return disk_free_space(dirname(DB_FILE));
	}

	static public function getQuota(): int
	{
		return disk_total_space(dirname(DB_FILE));
	}
}







|




<
<
<
<
<





75
76
77
78
79
80
81
82
83
84
85
86





87
88
89
90
91
	}

	static public function move(File $old_file, File $new_file): bool
	{
		return true;
	}

	static public function getTotalSize(): int
	{
		return (int) DB::getInstance()->firstColumn('SELECT SUM(size) FROM files_contents;');
	}






	static public function getQuota(): int
	{
		return disk_total_space(dirname(DB_FILE));
	}
}

Modified src/include/lib/Garradin/Files/Storage/StorageInterface.php from [1b7b1b4fca] to [e0b5fefef7].

1
2
3
4


5
6
7
8
9
10
11
<?php

namespace Garradin\Files\Storage;



interface StorageInterface
{
	static public function store(File $file, ?string $path, ?string $content): bool;

	/**
	 * List files contained in a path, this must return an array of File instances
	 * If this storage backend wants to leave the directory handling to Garradin, just return NULL.




>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
<?php

namespace Garradin\Files\Storage;

use Garradin\Entities\Files\File;

interface StorageInterface
{
	static public function store(File $file, ?string $path, ?string $content): bool;

	/**
	 * List files contained in a path, this must return an array of File instances
	 * If this storage backend wants to leave the directory handling to Garradin, just return NULL.
27
28
29
30
31
32
33
34
35
36
37

	static public function delete(File $file): bool;

	static public function move(File $old_file, File $new_file): bool;

	static public function getTotalSize(): int;

	static public function getRemainingQuota(): int;

	static public function getQuota(): int;
}







<
<


29
30
31
32
33
34
35


36
37

	static public function delete(File $file): bool;

	static public function move(File $old_file, File $new_file): bool;

	static public function getTotalSize(): int;



	static public function getQuota(): int;
}

Modified src/include/lib/Garradin/Membres/Session.php from [d8e75a6058] to [d8f65f6ef2].

16
17
18
19
20
21
22





23
24
25
26
27
28
29
use KD2\Security;
use KD2\Security_OTP;
use KD2\Graphics\QRCode;
use KD2\HTTP;

class Session extends \KD2\UserSession
{





	// Personalisation de la config de UserSession
	protected $cookie_name = 'gdin';
	protected $remember_me_cookie_name = 'gdinp';
	protected $remember_me_expiry = '+3 months';

	const MINIMUM_PASSWORD_LENGTH = 8;








>
>
>
>
>







16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
use KD2\Security;
use KD2\Security_OTP;
use KD2\Graphics\QRCode;
use KD2\HTTP;

class Session extends \KD2\UserSession
{
	const SECTION_WEB = 'web';
	const SECTION_DOCUMENTS = 'documents';
	const SECTION_USERS = 'membres';
	const SECTION_ACCOUNTING = 'compta';

	// Personalisation de la config de UserSession
	protected $cookie_name = 'gdin';
	protected $remember_me_cookie_name = 'gdinp';
	protected $remember_me_expiry = '+3 months';

	const MINIMUM_PASSWORD_LENGTH = 8;

Modified src/include/lib/Garradin/Template.php from [b3f70be648] to [f94b6d765a].

92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118

		$this->register_modifier('date_short', function ($dt) {
			return Utils::date_fr($dt, 'd/m/Y');
		});

		$this->register_modifier('html_money', [$this, 'htmlMoney']);
		$this->register_modifier('money_currency', [$this, 'htmlMoneyCurrency']);

		$this->register_modifier('format_wiki', function ($str) {
			$str = Utils::SkrivToHTML($str);
			$str = Squelette_Filtres::typo_fr($str);
			return $str;
		});

		$this->register_modifier('liens_wiki', function ($str, $prefix) {
			return preg_replace_callback('!<a href="([^/.:@]+)">!i', function ($matches) use ($prefix) {
				return '<a href="' . $prefix . Wiki::transformTitleToURI($matches[1]) . '">';
			}, $str);
		});

	}

	protected function htmlMoney($number, bool $hide_empty = true): string
	{
		if ($hide_empty && !$number) {
			return '';
		}







<
<
<
<
<
<
<
<
<
<
<
<
<







92
93
94
95
96
97
98













99
100
101
102
103
104
105

		$this->register_modifier('date_short', function ($dt) {
			return Utils::date_fr($dt, 'd/m/Y');
		});

		$this->register_modifier('html_money', [$this, 'htmlMoney']);
		$this->register_modifier('money_currency', [$this, 'htmlMoneyCurrency']);













	}

	protected function htmlMoney($number, bool $hide_empty = true): string
	{
		if ($hide_empty && !$number) {
			return '';
		}

Modified src/www/file.php from [a75fe87ff3] to [596ad632c6].

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
<?php

namespace Garradin;

require __DIR__ . '/_inc.php';

$id = isset($_GET['id']) ? $_GET['id'] : false;
$filename = !empty($_GET['file']) ? $_GET['file'] : false;
$size = false;

if (empty($id))
{
	throw new UserException('Fichier inconnu.');
}

foreach ($_GET as $key=>$value)
{
	if (substr($key, -2) == 'px')
	{
		$size = (int)substr($key, 0, -2);
		break;
	}
}

$id = base_convert($id, 36, 10);

try {
	$file = new Fichiers((int)$id);
}
catch (\InvalidArgumentException $e)
{
	throw new UserException('Ce fichier n\'existe pas.');
}

$session = new Membres\Session;

if (!$file->checkAccess($session))
{
	header('HTTP/1.1 403 Forbidden', true, 403);
	throw new UserException('Vous n\'avez pas accès à ce fichier.');
}

if ($size)
{
	$file->serveThumbnail($size);
}
else
{
	$file->serve();
}




<
|
<
<
<

<
<
<
<
|
<
|
<
<
<
<
<
<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
1
2
3
4

5



6




7

8






9


























<?php

namespace Garradin;


use Garradin\Files\Files;








require __DIR__ . '/_inc.php';








Files::serveFromQueryString();