Overview
Comment:Use phpstan and psalm to fix potential issues
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | dev
Files: files | file ages | folders
SHA3-256: 370e94a66f0e81a5713464e088c10b860b580261ed6f919773df721d421629cc
User & Date: bohwaz on 2021-03-03 01:46:54
Other Links: branch diff | manifest | tags
Context
2021-03-04
00:52
Update indexes check-in: 43b4ece848 user: bohwaz tags: dev
2021-03-03
01:46
Use phpstan and psalm to fix potential issues check-in: 370e94a66f user: bohwaz tags: dev
2021-03-02
19:36
Fix change page parent check-in: 306f68f3bc user: bohwaz tags: dev, 1.1.0-alpha2
Changes

Modified src/include/lib/Garradin/Accounting/Transactions.php from [9104f318ab] to [69fd74d5a9].

363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
		$db = DB::getInstance();

		if (null !== $id_analytical && !$db->test(Account::TABLE, 'type = ? AND id = ?', Account::TYPE_ANALYTICAL, $id_analytical)) {
			throw new \InvalidArgumentException('Chosen account ID is not analytical');
		}

		if (isset($transactions, $lines) || ($transactions === null && $lines === null)) {
			throw new BadMethodCallException('Only one of transactions or lines should be set');
		}

		$selection = array_map('intval', $transactions ?? $lines);
		$where = sprintf($transactions ? 'id_transaction IN (%s)' : 'id IN (%s)', implode(', ', $selection));

		return $db->exec(sprintf('UPDATE acc_transactions_lines SET id_analytical = %s WHERE %s;',
			(int)$id_analytical ?: 'NULL', $where));







|







363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
		$db = DB::getInstance();

		if (null !== $id_analytical && !$db->test(Account::TABLE, 'type = ? AND id = ?', Account::TYPE_ANALYTICAL, $id_analytical)) {
			throw new \InvalidArgumentException('Chosen account ID is not analytical');
		}

		if (isset($transactions, $lines) || ($transactions === null && $lines === null)) {
			throw new \BadMethodCallException('Only one of transactions or lines should be set');
		}

		$selection = array_map('intval', $transactions ?? $lines);
		$where = sprintf($transactions ? 'id_transaction IN (%s)' : 'id IN (%s)', implode(', ', $selection));

		return $db->exec(sprintf('UPDATE acc_transactions_lines SET id_analytical = %s WHERE %s;',
			(int)$id_analytical ?: 'NULL', $where));

Modified src/include/lib/Garradin/CSV.php from [b79b34dab0] to [58e95c673d].

150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
...
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199

		$fp = fopen('php://output', 'w');

		if ($header) {
			fputs($fp, self::row($header));
		}

		if ($iterator->valid()) {
			foreach ($iterator as $row) {
				foreach ($row as $key => &$v) {
					if (is_object($v)&& $v instanceof \DateTimeInterface) {
						$v = $v->format('d/m/Y');
					}
				}

................................................................................
		$ods = new ODSWriter;
		$ods->table_name = $name;

		if ($header) {
			$ods->add((array) $header);
		}

		if ($iterator->valid()) {
			foreach ($iterator as $row) {
				$row = self::rowToArray($row, $row_map_callback);

				if (!$header)
				{
					$ods->add(array_keys($row));
					$header = true;







|







 







|







150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
...
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199

		$fp = fopen('php://output', 'w');

		if ($header) {
			fputs($fp, self::row($header));
		}

		if (!($iterator instanceof \Iterator) || $iterator->valid()) {
			foreach ($iterator as $row) {
				foreach ($row as $key => &$v) {
					if (is_object($v)&& $v instanceof \DateTimeInterface) {
						$v = $v->format('d/m/Y');
					}
				}

................................................................................
		$ods = new ODSWriter;
		$ods->table_name = $name;

		if ($header) {
			$ods->add((array) $header);
		}

		if (!($iterator instanceof \Iterator) || $iterator->valid()) {
			foreach ($iterator as $row) {
				$row = self::rowToArray($row, $row_map_callback);

				if (!$header)
				{
					$ods->add(array_keys($row));
					$header = true;

Modified src/include/lib/Garradin/Entities/Accounting/Transaction.php from [e7a7ef2155] to [afe6ab0bb5].

87
88
89
90
91
92
93



94
95
96
97
98
99
100
...
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
...
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
		'reference' => 'string|max:200',
		'date'      => 'required|date_format:d/m/Y',
	];

	protected $_lines;
	protected $_old_lines = [];




	protected $_related;

	static public function getTypeFromAccountType(int $account_type)
	{
		switch ($account_type) {
			case Account::TYPE_REVENUE:
				return self::TYPE_REVENUE;
................................................................................

		$db = DB::getInstance();

		if ($db->test(Year::TABLE, 'id = ? AND closed = 1', $this->id_year)) {
			throw new ValidationException('Il n\'est pas possible de supprimer une écriture qui fait partie d\'un exercice clôturé');
		}

		Files::deleteLinkedFiles(File::CONTEXT_TRANSACTION, $this->id());

		return parent::delete();
	}

	public function selfCheck(): void
	{
		parent::selfCheck();

		$db = DB::getInstance();
		$config = Config::getInstance();

		// ID d'exercice obligatoire
		if (null === $this->id_year) {
			throw new \LogicException('Aucun exercice spécifié.');
		}

		if (!$db->test(Year::TABLE, 'id = ? AND start_date <= ? AND end_date >= ?;', $this->id_year, $this->date->format('Y-m-d'), $this->date->format('Y-m-d')))
................................................................................

			$lines = Utils::array_transpose($source['lines']);

			foreach ($lines as $i => $line) {
				$line['id_account'] = @count($line['account']) ? key($line['account']) : null;

				if (!$line['id_account']) {
					throw new ValidationException('Numéro de compte invalide sur la ligne ' . ($i+1));
				}

				$line = (new Line)->import($line);
				$this->addLine($line);
			}
		}
		else {







>
>
>







 







|









<







 







|







87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
...
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370

371
372
373
374
375
376
377
...
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
		'reference' => 'string|max:200',
		'date'      => 'required|date_format:d/m/Y',
	];

	protected $_lines;
	protected $_old_lines = [];

	/**
	 * @var Transaction
	 */
	protected $_related;

	static public function getTypeFromAccountType(int $account_type)
	{
		switch ($account_type) {
			case Account::TYPE_REVENUE:
				return self::TYPE_REVENUE;
................................................................................

		$db = DB::getInstance();

		if ($db->test(Year::TABLE, 'id = ? AND closed = 1', $this->id_year)) {
			throw new ValidationException('Il n\'est pas possible de supprimer une écriture qui fait partie d\'un exercice clôturé');
		}

		Files::delete($this->getAttachementsDirectory());

		return parent::delete();
	}

	public function selfCheck(): void
	{
		parent::selfCheck();

		$db = DB::getInstance();


		// ID d'exercice obligatoire
		if (null === $this->id_year) {
			throw new \LogicException('Aucun exercice spécifié.');
		}

		if (!$db->test(Year::TABLE, 'id = ? AND start_date <= ? AND end_date >= ?;', $this->id_year, $this->date->format('Y-m-d'), $this->date->format('Y-m-d')))
................................................................................

			$lines = Utils::array_transpose($source['lines']);

			foreach ($lines as $i => $line) {
				$line['id_account'] = @count($line['account']) ? key($line['account']) : null;

				if (!$line['id_account']) {
					throw new ValidationException('Numéro de compte invalide sur la ligne ' . ((int) $i+1));
				}

				$line = (new Line)->import($line);
				$this->addLine($line);
			}
		}
		else {

Modified src/include/lib/Garradin/Entities/Accounting/Year.php from [42cf3b1884] to [f33e68404f].

4
5
6
7
8
9
10

11
12
13
14
15
16
17
...
129
130
131
132
133
134
135









136
137
138
139
140
141
142
143
144
145
146
147

use KD2\DB\EntityManager;
use Garradin\DB;
use Garradin\Entity;
use Garradin\UserException;
use Garradin\Accounting\Accounts;
use Garradin\Files\Files;


class Year extends Entity
{
	const TABLE = 'acc_years';

	protected $id;
	protected $label;
................................................................................

		DB::getInstance()->preparedQuery('UPDATE acc_transactions SET id_year = ? WHERE id_year = ? AND date > ?;',
			$target->id(), $this->id(), $date->format('Y-m-d'));
	}

	public function delete(): bool
	{









		// Manual delete of transactions, as there is a voluntary safeguard in SQL: no cascade
		DB::getInstance()->preparedQuery('DELETE FROM acc_transactions WHERE id_year = ?;', $this->id());

		// Clean up files
		Files::deleteUnlinkedFiles();

		return parent::delete();
	}

	public function countTransactions(): int
	{
		$db = DB::getInstance();







>







 







>
>
>
>
>
>
>
>
>

|
<
<
<







4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
...
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

use KD2\DB\EntityManager;
use Garradin\DB;
use Garradin\Entity;
use Garradin\UserException;
use Garradin\Accounting\Accounts;
use Garradin\Files\Files;
use Garradin\Entities\Files\File;

class Year extends Entity
{
	const TABLE = 'acc_years';

	protected $id;
	protected $label;
................................................................................

		DB::getInstance()->preparedQuery('UPDATE acc_transactions SET id_year = ? WHERE id_year = ? AND date > ?;',
			$target->id(), $this->id(), $date->format('Y-m-d'));
	}

	public function delete(): bool
	{
		$db = DB::getInstance();
		$ids = $db->getAssoc('SELECT id, id FROM acc_transactions WHERE id_year = ?;', $this->id());


		// Delete all files
		foreach ($ids as $id) {
			Files::delete(File::CONTEXT_TRANSACTION . '/' . $id);
		}

		// Manual delete of transactions, as there is a voluntary safeguard in SQL: no cascade
		$db->preparedQuery('DELETE FROM acc_transactions WHERE id_year = ?;', $this->id());




		return parent::delete();
	}

	public function countTransactions(): int
	{
		$db = DB::getInstance();

Modified src/include/lib/Garradin/Entities/Files/File.php from [a1f4a749b2] to [82b7d1065e].

162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
...
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
...
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384



385
386
387
388
389
390
391
...
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
...
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
		if ($this->type == self::TYPE_DIRECTORY) {
			foreach (Files::list($this->path()) as $file) {
				$file->delete();
			}
		}

		// Delete actual file content
		$return = Files::callStorage('delete', $this);

		// clean up thumbnails
		foreach (self::ALLOWED_THUMB_SIZES as $size)
		{
			Static_Cache::remove(sprintf(self::THUMB_CACHE_ID, $this->pathHash(), $size));
		}

................................................................................
		self::ensureDirectoryExists($path);

		$finfo = \finfo_open(\FILEINFO_MIME_TYPE);
		$file = new self;
		$file->set('name', $name);
		$file->set('path', $path);

		$db = DB::getInstance();

		if ($source_path && !$source_content) {
			$file->set('mime', finfo_file($finfo, $source_path));
			$file->set('size', filesize($source_path));
		}
		else {
			$file->set('mime', finfo_buffer($finfo, $source_content));
			$file->set('size', strlen($source_content));
................................................................................

		$file->set('image', (int) in_array($file->mime, self::IMAGE_TYPES));

		return $file;
	}

	/**
	 * Upload de fichier à partir d'une chaîne en base64
	 * @param  string $name
	 * @param  string $content
	 * @return File
	 */
	static public function createFromBase64(string $path, string $name, string $encoded_content): self
	{
		$content = base64_decode($encoded_content);
		return self::createAndStore($path, $name, null, $content);
	}




	public function storeFromBase64(string $encoded_content): self
	{
		$content = base64_decode($encoded_content);
		return $this->store(null, $content);
	}

	/**
................................................................................

	/**
	 * 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, bool $download = false): void
	{
		if ($this->isPublic()) {
			Utils::HTTPCache(null, $this->modified->getTimestamp());
		}
		else {
................................................................................
			return \Garradin\Web\Render\HTML::render($this, null, $options);
		}*/

		if ($type == self::FILE_EXT_SKRIV) {
			return \Garradin\Web\Render\Skriv::render($this, null, $options);
		}
		else if ($type == self::FILE_EXT_ENCRYPTED) {
			return \Garradin\Web\Render\EncryptedSkriv::render($this, null, $options);
		}
		else if (substr($this->mime, 0, 5) == 'text/') {
			return $this->fetch();
		}

		throw new \LogicException('Cannot render file of this type');
	}







|







 







<
<







 







|
<
<
<







>
>
>







 







<







 







|







162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
...
350
351
352
353
354
355
356


357
358
359
360
361
362
363
...
365
366
367
368
369
370
371
372



373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
...
560
561
562
563
564
565
566

567
568
569
570
571
572
573
...
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
		if ($this->type == self::TYPE_DIRECTORY) {
			foreach (Files::list($this->path()) as $file) {
				$file->delete();
			}
		}

		// Delete actual file content
		Files::callStorage('delete', $this);

		// clean up thumbnails
		foreach (self::ALLOWED_THUMB_SIZES as $size)
		{
			Static_Cache::remove(sprintf(self::THUMB_CACHE_ID, $this->pathHash(), $size));
		}

................................................................................
		self::ensureDirectoryExists($path);

		$finfo = \finfo_open(\FILEINFO_MIME_TYPE);
		$file = new self;
		$file->set('name', $name);
		$file->set('path', $path);



		if ($source_path && !$source_content) {
			$file->set('mime', finfo_file($finfo, $source_path));
			$file->set('size', filesize($source_path));
		}
		else {
			$file->set('mime', finfo_buffer($finfo, $source_content));
			$file->set('size', strlen($source_content));
................................................................................

		$file->set('image', (int) in_array($file->mime, self::IMAGE_TYPES));

		return $file;
	}

	/**
	 * Create a file from an encoded base64 string



	 */
	static public function createFromBase64(string $path, string $name, string $encoded_content): self
	{
		$content = base64_decode($encoded_content);
		return self::createAndStore($path, $name, null, $content);
	}

	/**
	 * Modify a file from an encoded base64 string
	 */
	public function storeFromBase64(string $encoded_content): self
	{
		$content = base64_decode($encoded_content);
		return $this->store(null, $content);
	}

	/**
................................................................................

	/**
	 * 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)

	 */
	protected function _serve(?string $path, ?string $content, bool $download = false): void
	{
		if ($this->isPublic()) {
			Utils::HTTPCache(null, $this->modified->getTimestamp());
		}
		else {
................................................................................
			return \Garradin\Web\Render\HTML::render($this, null, $options);
		}*/

		if ($type == self::FILE_EXT_SKRIV) {
			return \Garradin\Web\Render\Skriv::render($this, null, $options);
		}
		else if ($type == self::FILE_EXT_ENCRYPTED) {
			return \Garradin\Web\Render\EncryptedSkriv::render($this, null);
		}
		else if (substr($this->mime, 0, 5) == 'text/') {
			return $this->fetch();
		}

		throw new \LogicException('Cannot render file of this type');
	}

Modified src/include/lib/Garradin/Entities/Web/Page.php from [cddf5728e0] to [107458ade3].

131
132
133
134
135
136
137
138
139


140
141
142
143
144
145
146
...
306
307
308
309
310
311
312

313
314
315
316
317
318
319

	public function render(array $options = []): string
	{
		if ($this->format == self::FORMAT_SKRIV) {
			return \Garradin\Web\Render\Skriv::render($this->file(), $this->content, $options);
		}
		else if ($this->format == self::FORMAT_ENCRYPTED) {
			return \Garradin\Web\Render\EncryptedSkriv::render($this->file(), $this->content, $options);
		}


	}

	public function preview(string $content): string
	{
		return Skriv::render($this->file(), $content, ['prefix' => '#']);
	}

................................................................................
	/**
	 * Return list of files
	 * If $all is FALSE then this will only return files that are not present in the content
	 */
	public function getAttachmentsGallery(bool $all = true, bool $images = false): array
	{
		$out = [];


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

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







|

>
>







 







>







131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
...
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322

	public function render(array $options = []): string
	{
		if ($this->format == self::FORMAT_SKRIV) {
			return \Garradin\Web\Render\Skriv::render($this->file(), $this->content, $options);
		}
		else if ($this->format == self::FORMAT_ENCRYPTED) {
			return \Garradin\Web\Render\EncryptedSkriv::render($this->file(), $this->content);
		}

		throw new \LogicException('Invalid format: ' . $this->format);
	}

	public function preview(string $content): string
	{
		return Skriv::render($this->file(), $content, ['prefix' => '#']);
	}

................................................................................
	/**
	 * Return list of files
	 * If $all is FALSE then this will only return files that are not present in the content
	 */
	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) {

Modified src/include/lib/Garradin/Files/Files.php from [c6c0a0aa02] to [82ad6d3a34].

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
...
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190

use KD2\DB\EntityManager as EM;

use const Garradin\{FILE_STORAGE_BACKEND, FILE_STORAGE_QUOTA, FILE_STORAGE_CONFIG};

class Files
{
	static public function redirectOldWikiPage(string $uri): ?File {
		$uri = Utils::transformTitleToURI($uri);

		$db = DB::getInstance();

		if ($db->test(Page::TABLE, 'uri = ?')) {
			Utils::redirect(ADMIN_URL . 'web/page.php?uri=' . $uri);
		}

		$file = self::get('documents/wiki/' . $uri . '.skriv');

		if ($file) {
			Utils::redirect(ADMIN_URL . 'documents/?p=' . $file->path);
		}

		return null;
	}

	static public function list(string $path = ''): array
	{
		File::validatePath($path);

		// Update this path
		self::callStorage('sync', $path);

		return EM::getInstance(File::class)->all('SELECT * FROM @TABLE WHERE path = ? ORDER BY type DESC, name COLLATE NOCASE ASC;', $path);
	}












	/**
	 * Creates a new temporary table files_tmp containg all files from the path argument
	 */
	static public function listToSQL(string $path): int
	{
		$db = DB::getInstance();
................................................................................
	static public function truncateStorage(string $backend, $config = null): void
	{
		$backend = __NAMESPACE__ . '\\Storage\\' . $backend;

		call_user_func([$backend, 'configure'], $config);

		if (!class_exists($backend)) {
			throw new \InvalidArgumentException('Invalid storage: ' . $from);
		}

		call_user_func([$backend, 'truncate']);
	}

	static public function getContextJoinClause(string $context): ?string
	{
		switch ($context) {
			case File::CONTEXT_TRANSACTION:
				return 'acc_transactions c ON c.id = f.context_ref';
			case File::CONTEXT_USER:
				return 'membres c ON c.id = f.context_ref';
			case File::CONTEXT_FILE:
				return 'files c ON c.id = f.context_ref';
			case File::CONTEXT_CONFIG:
				return 'config c ON c.key = f.context_ref AND c.value = f.id';
			case File::CONTEXT_WEB:
			case File::CONTEXT_DOCUMENTS:
			case File::CONTEXT_SKELETON:
			default:
				return null;
		}
	}

	static public function get(?string $path, ?string $name = null, int $type = null): ?File
	{
		if (null === $path) {
			return null;
		}

		$fullpath = $path;







|





|

<
<
<
<
<
<
<
<











>
>
>
>
>
>
>
>
>
>
>







 







|





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







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
...
155
156
157
158
159
160
161
162
163
164
165
166
167



















168
169
170
171
172
173
174

use KD2\DB\EntityManager as EM;

use const Garradin\{FILE_STORAGE_BACKEND, FILE_STORAGE_QUOTA, FILE_STORAGE_CONFIG};

class Files
{
	static public function redirectOldWikiPage(string $uri): void {
		$uri = Utils::transformTitleToURI($uri);

		$db = DB::getInstance();

		if ($db->test(Page::TABLE, 'uri = ?')) {
			Utils::redirect('!web/page.php?uri=' . $uri);
		}








	}

	static public function list(string $path = ''): array
	{
		File::validatePath($path);

		// Update this path
		self::callStorage('sync', $path);

		return EM::getInstance(File::class)->all('SELECT * FROM @TABLE WHERE path = ? ORDER BY type DESC, name COLLATE NOCASE ASC;', $path);
	}

	static public function delete(string $path): void
	{
		$file = self::get($path);

		if (!$file) {
			return;
		}

		$file->delete();
	}

	/**
	 * Creates a new temporary table files_tmp containg all files from the path argument
	 */
	static public function listToSQL(string $path): int
	{
		$db = DB::getInstance();
................................................................................
	static public function truncateStorage(string $backend, $config = null): void
	{
		$backend = __NAMESPACE__ . '\\Storage\\' . $backend;

		call_user_func([$backend, 'configure'], $config);

		if (!class_exists($backend)) {
			throw new \InvalidArgumentException('Invalid storage: ' . $backend);
		}

		call_user_func([$backend, 'truncate']);
	}




















	static public function get(?string $path, ?string $name = null, int $type = null): ?File
	{
		if (null === $path) {
			return null;
		}

		$fullpath = $path;

Modified src/include/lib/Garradin/Files/Storage/FileSystem.php from [8263e32ffb] to [514e751db8].

43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
...
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
...
146
147
148
149
150
151
152
153

154
155
156
157
158
159
160
...
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200

	static protected function ensureDirectoryExists(string $path): void
	{
		if (is_dir($path)) {
			return;
		}

		$permissions = fileperms(self::_getRoot(null));

		Utils::safe_mkdir($path, $permissions & 0777, true);
	}

	static public function storePath(File $file, string $path): bool
	{
		$target = self::getFullPath($file);
		self::ensureDirectoryExists(dirname($target));

		return copy($path, $target);
	}

	static public function storeContent(File $file, string $content): bool
	{
		$target = self::getFullPath($file);
		self::ensureDirectoryExists(dirname($target));

		return file_put_contents($target, $content) === false ? false : true;
	}

	static public function mkdir(File $file): bool
	{
		return Utils::safe_mkdir(self::getFullPath($file));
	}

................................................................................
		self::ensureDirectoryExists(dirname($target));

		return rename($source, $target);
	}

	static public function exists(string $path): bool
	{
		return (bool) file_exists(self::_getRealPath($path));
	}

	static public function modified(File $file): ?int
	{
		return filemtime(self::getFullPath($path)) ?: null;
	}

	static public function getTotalSize(): int
	{
		if (null !== self::$_size) {
			return self::$_size;
		}
................................................................................

	/**
	 * @see https://www.crazyws.fr/dev/fonctions-php/fonction-disk-free-space-et-disk-total-space-pour-ovh-2JMH9.html
	 * @see https://github.com/jdel/sspks/commit/a890e347f32e9e3e50a0dd82398947633872bf38
	 */
	static public function getQuota(): int
	{
		return @disk_total_space(self::_getRoot()) ?: \PHP_INT_MAX;

	}

	static public function truncate(): void
	{
		Utils::deleteRecursive(self::_getRoot());
	}

................................................................................
			return;
		}

		$db = DB::getInstance();

		$saved_files = $db->getGrouped('SELECT name, size, modified, type FROM files WHERE path = ?;', $path);
		$added = [];
		$deleted = [];
		$modified = [];
		$exists = [];

		foreach (new \FilesystemIterator($fullpath, \FilesystemIterator::SKIP_DOTS) as $file) {
			$name = $file->getFilename();

			$data = [







|




|




|


|




|







 







|




|







 







|
>







 







<







43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
...
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
...
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
...
187
188
189
190
191
192
193

194
195
196
197
198
199
200

	static protected function ensureDirectoryExists(string $path): void
	{
		if (is_dir($path)) {
			return;
		}

		$permissions = fileperms(self::_getRoot());

		Utils::safe_mkdir($path, $permissions & 0777, true);
	}

	static public function storePath(File $file, string $source_path): bool
	{
		$target = self::getFullPath($file);
		self::ensureDirectoryExists(dirname($target));

		return copy($source_path, $target);
	}

	static public function storeContent(File $file, string $source_content): bool
	{
		$target = self::getFullPath($file);
		self::ensureDirectoryExists(dirname($target));

		return file_put_contents($target, $source_content) === false ? false : true;
	}

	static public function mkdir(File $file): bool
	{
		return Utils::safe_mkdir(self::getFullPath($file));
	}

................................................................................
		self::ensureDirectoryExists(dirname($target));

		return rename($source, $target);
	}

	static public function exists(string $path): bool
	{
		return file_exists(self::_getRealPath($path));
	}

	static public function modified(File $file): ?int
	{
		return filemtime(self::getFullPath($file)) ?: null;
	}

	static public function getTotalSize(): int
	{
		if (null !== self::$_size) {
			return self::$_size;
		}
................................................................................

	/**
	 * @see https://www.crazyws.fr/dev/fonctions-php/fonction-disk-free-space-et-disk-total-space-pour-ovh-2JMH9.html
	 * @see https://github.com/jdel/sspks/commit/a890e347f32e9e3e50a0dd82398947633872bf38
	 */
	static public function getQuota(): int
	{
		$quota = @disk_total_space(self::_getRoot());
		return $quota === false ? \PHP_INT_MAX : (int) $quota;
	}

	static public function truncate(): void
	{
		Utils::deleteRecursive(self::_getRoot());
	}

................................................................................
			return;
		}

		$db = DB::getInstance();

		$saved_files = $db->getGrouped('SELECT name, size, modified, type FROM files WHERE path = ?;', $path);
		$added = [];

		$modified = [];
		$exists = [];

		foreach (new \FilesystemIterator($fullpath, \FilesystemIterator::SKIP_DOTS) as $file) {
			$name = $file->getFilename();

			$data = [

Modified src/include/lib/Garradin/Files/Storage/SQLite.php from [5f571e7f25] to [5f84090ade].

61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
...
137
138
139
140
141
142
143
144

145
146
147
148
149
150
151
	{
		if (!isset($source_path) && !isset($source_content)) {
			throw new \InvalidArgumentException('Either source_path or source_content must be supplied');
		}

		$db = DB::getInstance();

		$st = $db->preparedQuery('INSERT OR REPLACE INTO files_contents (id, content) VALUES (?, zeroblob(?));',
			$file->id(), $file->size);

		$blob = $db->openBlob('files_contents', 'content', $file->id(), 'main', \SQLITE3_OPEN_READWRITE);

		if (null !== $source_content) {
			fwrite($blob, $source_content);
		}
................................................................................

	/**
	 * @see https://www.crazyws.fr/dev/fonctions-php/fonction-disk-free-space-et-disk-total-space-pour-ovh-2JMH9.html
	 * @see https://github.com/jdel/sspks/commit/a890e347f32e9e3e50a0dd82398947633872bf38
	 */
	static public function getQuota(): int
	{
		return @disk_total_space(DATA_ROOT) ?: \PHP_INT_MAX;

	}

	static public function truncate(): void
	{
		$db = DB::getInstance();
		$db->exec('DELETE FROM files_contents; VACUUM;');
	}







|







 







|
>







61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
...
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
	{
		if (!isset($source_path) && !isset($source_content)) {
			throw new \InvalidArgumentException('Either source_path or source_content must be supplied');
		}

		$db = DB::getInstance();

		$db->preparedQuery('INSERT OR REPLACE INTO files_contents (id, content) VALUES (?, zeroblob(?));',
			$file->id(), $file->size);

		$blob = $db->openBlob('files_contents', 'content', $file->id(), 'main', \SQLITE3_OPEN_READWRITE);

		if (null !== $source_content) {
			fwrite($blob, $source_content);
		}
................................................................................

	/**
	 * @see https://www.crazyws.fr/dev/fonctions-php/fonction-disk-free-space-et-disk-total-space-pour-ovh-2JMH9.html
	 * @see https://github.com/jdel/sspks/commit/a890e347f32e9e3e50a0dd82398947633872bf38
	 */
	static public function getQuota(): int
	{
		$quota = @disk_total_space(DATA_ROOT);
		return $quota === false ? \PHP_INT_MAX : (int) $quota;
	}

	static public function truncate(): void
	{
		$db = DB::getInstance();
		$db->exec('DELETE FROM files_contents; VACUUM;');
	}

Modified src/include/lib/Garradin/Membres.php from [0a092a3ef6] to [5685ac30d0].

270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
...
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
...
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
                if ($user->id == $id)
                {
                    throw new UserException('Il n\'est pas possible de supprimer son propre compte.');
                }
            }
        }

        return self::_deleteMembres($ids);
    }

    public function getNom($id)
    {
        $db = DB::getInstance();
        $config = Config::getInstance();

................................................................................

    public function countAllButHidden()
    {
        $db = DB::getInstance();
        return $db->firstColumn('SELECT COUNT(*) FROM membres WHERE category_id NOT IN (SELECT id FROM users_categories WHERE hidden = 1);');
    }

    public function getFilesPath(int $id)
    {
        return File::CONTEXT_USER . '/' . $id;
    }

    static public function changeCategorie($id_cat, $membres)
    {
        foreach ($membres as &$id)
................................................................................
        $db = DB::getInstance();
        return $db->update('membres',
            ['category_id' => (int)$id_cat],
            sprintf('id IN (%s)', implode(',', $membres))
        );
    }

    static protected function _deleteMembres($membres)
    {
        foreach ($membres as &$id)
        {
            $id = (int) $id;

            Files::deleteLinkedFiles(File::CONTEXT_USER, $id);
        }

        Plugin::fireSignal('membre.suppression', $membres);

        $db = DB::getInstance();

        // Suppression du membre
        return $db->delete('membres', $db->where('id', $membres));
    }
}







|







 







|







 







|





|










270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
...
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
...
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
                if ($user->id == $id)
                {
                    throw new UserException('Il n\'est pas possible de supprimer son propre compte.');
                }
            }
        }

        return $this->_deleteMembres($ids);
    }

    public function getNom($id)
    {
        $db = DB::getInstance();
        $config = Config::getInstance();

................................................................................

    public function countAllButHidden()
    {
        $db = DB::getInstance();
        return $db->firstColumn('SELECT COUNT(*) FROM membres WHERE category_id NOT IN (SELECT id FROM users_categories WHERE hidden = 1);');
    }

    public function getAttachementsDirectory(int $id)
    {
        return File::CONTEXT_USER . '/' . $id;
    }

    static public function changeCategorie($id_cat, $membres)
    {
        foreach ($membres as &$id)
................................................................................
        $db = DB::getInstance();
        return $db->update('membres',
            ['category_id' => (int)$id_cat],
            sprintf('id IN (%s)', implode(',', $membres))
        );
    }

    protected function _deleteMembres(array $membres)
    {
        foreach ($membres as &$id)
        {
            $id = (int) $id;

            Files::delete($this->getAttachementsDirectory($id));
        }

        Plugin::fireSignal('membre.suppression', $membres);

        $db = DB::getInstance();

        // Suppression du membre
        return $db->delete('membres', $db->where('id', $membres));
    }
}

Modified src/include/lib/Garradin/Sauvegarde.php from [32d0dd5b29] to [e6a2ef9e78].

216
217
218
219
220
221
222

223
224
225
226
227
228
229
...
250
251
252
253
254
255
256

257

258
259
260
261
262
263
264

	/**
	 * Renvoie sur la sortie courante le contenu du fichier de base de données sélectionné ou courant
	 */
	public function dump(?string $file = null): void
	{
		$config = Config::getInstance();


		if (null === $file) {
			$file = DB_FILE;
			$name = sprintf('%s - Sauvegarde données - %s.sqlite', $config->get('nom_asso'), date('Y-m-d'));

			$tmp_file = tempnam(sys_get_temp_dir(), 'gdin');
			$this->make($tmp_file);
................................................................................
		header(sprintf('Content-Length: %d', filesize($file) + $hash_length));

		readfile($file);

		// Add integrity hash
		echo sha1_file($file);


		@unlink($tmp_file);

	}

	/**
	 * Restaure une sauvegarde locale
	 * @param  string $file Le nom de fichier à utiliser comme point de restauration
	 * @return boolean true si la restauration a fonctionné, false sinon
	 */







>







 







>
|
>







216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
...
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267

	/**
	 * Renvoie sur la sortie courante le contenu du fichier de base de données sélectionné ou courant
	 */
	public function dump(?string $file = null): void
	{
		$config = Config::getInstance();
		$tmp_file = null;

		if (null === $file) {
			$file = DB_FILE;
			$name = sprintf('%s - Sauvegarde données - %s.sqlite', $config->get('nom_asso'), date('Y-m-d'));

			$tmp_file = tempnam(sys_get_temp_dir(), 'gdin');
			$this->make($tmp_file);
................................................................................
		header(sprintf('Content-Length: %d', filesize($file) + $hash_length));

		readfile($file);

		// Add integrity hash
		echo sha1_file($file);

		if (null !== $tmp_file) {
			@unlink($tmp_file);
		}
	}

	/**
	 * Restaure une sauvegarde locale
	 * @param  string $file Le nom de fichier à utiliser comme point de restauration
	 * @return boolean true si la restauration a fonctionné, false sinon
	 */

Modified src/include/lib/Garradin/UserTemplate/Functions.php from [6793282125] to [3fe8fa9c3f].

3
4
5
6
7
8
9


10
11
12
13
14
15
16
namespace Garradin\UserTemplate;

use KD2\Brindille;
use KD2\Brindille_Exception;
use KD2\ErrorManager;

use Garradin\Web\Skeleton;



class Functions
{
	const FUNCTIONS_LIST = [
		'include',
		'http',
		'dump',







>
>







3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
namespace Garradin\UserTemplate;

use KD2\Brindille;
use KD2\Brindille_Exception;
use KD2\ErrorManager;

use Garradin\Web\Skeleton;

use const Garradin\WWW_URL;

class Functions
{
	const FUNCTIONS_LIST = [
		'include',
		'http',
		'dump',

Modified src/include/lib/Garradin/UserTemplate/Sections.php from [9cbc3d4da6] to [fc9dcd316e].

246
247
248
249
250
251
252
253
254


255
256
257
258
259
260
261
...
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
			$params['where'] ?? '',
			isset($params['group']) ? 'GROUP BY ' . $params['group'] : '',
			$params['order'],
			$params['begin'],
			$params['limit']
		);

		try {
			$db = DB::getInstance();


			$statement = $db->protectSelect(null, $sql);

			$args = [];

			foreach ($params as $key => $value) {
				if (substr($key, 0, 1) == ':') {
					$args[$key] = $value;
................................................................................
				$statement->bindValue($key, $value, $db->getArgType($value));
			}

			if (!empty($params['debug'])) {
				echo sprintf('<pre style="padding: 5px; background: yellow;">%s</pre>', htmlspecialchars($statement->getSQL(true)));
			}

			unset($params, $sql);

			$result = $statement->execute();
		}
		catch (\Exception $e) {
			throw new Brindille_Exception(sprintf("Erreur SQL à la ligne %d : %s\nRequête exécutée : %s", $line, $db->lastErrorMsg(), $sql));
		}

		while ($row = $result->fetchArray(\SQLITE3_ASSOC))
		{
			yield $row;
		}
	}
}







<
|
>
>







 







<
<












246
247
248
249
250
251
252

253
254
255
256
257
258
259
260
261
262
...
267
268
269
270
271
272
273


274
275
276
277
278
279
280
281
282
283
284
285
			$params['where'] ?? '',
			isset($params['group']) ? 'GROUP BY ' . $params['group'] : '',
			$params['order'],
			$params['begin'],
			$params['limit']
		);


		$db = DB::getInstance();

		try {
			$statement = $db->protectSelect(null, $sql);

			$args = [];

			foreach ($params as $key => $value) {
				if (substr($key, 0, 1) == ':') {
					$args[$key] = $value;
................................................................................
				$statement->bindValue($key, $value, $db->getArgType($value));
			}

			if (!empty($params['debug'])) {
				echo sprintf('<pre style="padding: 5px; background: yellow;">%s</pre>', htmlspecialchars($statement->getSQL(true)));
			}



			$result = $statement->execute();
		}
		catch (\Exception $e) {
			throw new Brindille_Exception(sprintf("Erreur SQL à la ligne %d : %s\nRequête exécutée : %s", $line, $db->lastErrorMsg(), $sql));
		}

		while ($row = $result->fetchArray(\SQLITE3_ASSOC))
		{
			yield $row;
		}
	}
}

Modified src/include/lib/Garradin/Web/Render/Skriv.php from [0829b3ce29] to [c03a773e3c].

13
14
15
16
17
18
19


20
21
22
23
24
25
26
..
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
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
use KD2\SkrivLite;

use const Garradin\WWW_URL;

class Skriv
{
	static protected $skriv;



	static public function render(File $file, ?string $content = null, array $options = []): string
	{
		if (!isset($options['prefix'])) {
			$options['prefix'] = WWW_URL;
		}

................................................................................
			self::$skriv->registerExtension('image', [self::class, 'SkrivImage']);

			// Enregistrer d'autres extensions éventuellement
			Plugin::fireSignal('skriv.init', ['skriv' => self::$skriv]);
		}

		$skriv =& self::$skriv;
		$skriv->_currentPath = str_replace(File::CONTEXT_WEB . '/', '', $file->path);
		$str = $content ?? $file->fetch();

		$str = preg_replace_callback('/#file:\[([^\]\h]+)\]/', function ($match) use ($skriv) {
			return WWW_URL . $skriv->_currentPath . '/' . $match[1];
		}, $str);

		$str = self::$skriv->render($str);

		$str = CommonModifiers::typo($str);

		$str = preg_replace_callback('!<a href="/(.+)">!i', function ($matches) use ($options) {
			return sprintf('<a href="%s">', $options['prefix'], Utils::transformTitleToURI($matches[1]));
		}, $str);

		return sprintf('<div class="web-content">%s</div>', $str);
	}

	/**
	 * Callback utilisé pour l'extension <<file>> dans le wiki-texte
	 * @param array $args    Arguments passés à l'extension
	 * @param string $content Contenu éventuel (en mode bloc)
	 * @param object $skriv   Objet SkrivLite
	 */
	static public function SkrivFile(array $args, ?string $content, SkrivLite $skriv): string
	{
		$name = $args[0] ?? null;
		$caption = $args[1] ?? null;

		if (!$name || !$skriv->_currentPath)
		{
			return $skriv->parseError('/!\ Tag file : aucun nom de fichier indiqué.');
		}

		if (empty($caption))
		{
			$caption = $name;
		}

		$url = WWW_URL . $skriv->_currentPath . '/' . $name;
		$ext = substr($name, strrpos($name, '.')+1);

		return sprintf(
			'<aside class="file" data-type="%s"><a href="%s" class="internal-file">%s</a> <small>(%s)</small></aside>',
			htmlspecialchars($ext), htmlspecialchars($url), htmlspecialchars($caption), htmlspecialchars(strtoupper($ext))
		);
	}

	/**
	 * Callback utilisé pour l'extension <<image>> dans le wiki-texte
	 * @param array $args    Arguments passés à l'extension
	 * @param string $content Contenu éventuel (en mode bloc)
	 * @param object $skriv   Objet SkrivLite
	 */
	static public function SkrivImage(array $args, ?string $content, SkrivLite $skriv): string
	{
		static $align_values = ['left', 'right', 'center'];

		$name = $args[0] ?? null;
		$align = $args[1] ?? null;
		$caption = $args[2] ?? null;

		if (!$name || !$skriv->_currentPath)
		{
			return $skriv->parseError('/!\ Tag image : aucun nom de fichier indiqué.');
		}

		$url = WWW_URL . $skriv->_currentPath . '/' . $name;
		$thumb_url = sprintf('%s?%dpx', $url, $align == 'center' ? 500 : 200);

		$out = sprintf('<a href="%s" class="internal-image" target="_image"><img src="%s" alt="%s" loading="lazy" /></a>',
			htmlspecialchars($url),
			htmlspecialchars($thumb_url),
			htmlspecialchars($caption ?? $name)
		);







>
>







 







|


|
|







|









|






|









|












|









|




|







13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
..
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
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
use KD2\SkrivLite;

use const Garradin\WWW_URL;

class Skriv
{
	static protected $skriv;

	static protected $current_path;

	static public function render(File $file, ?string $content = null, array $options = []): string
	{
		if (!isset($options['prefix'])) {
			$options['prefix'] = WWW_URL;
		}

................................................................................
			self::$skriv->registerExtension('image', [self::class, 'SkrivImage']);

			// Enregistrer d'autres extensions éventuellement
			Plugin::fireSignal('skriv.init', ['skriv' => self::$skriv]);
		}

		$skriv =& self::$skriv;
		self::$current_path = str_replace(File::CONTEXT_WEB . '/', '', $file->path);
		$str = $content ?? $file->fetch();

		$str = preg_replace_callback('/#file:\[([^\]\h]+)\]/', function ($match) {
			return WWW_URL . self::$current_path . '/' . $match[1];
		}, $str);

		$str = self::$skriv->render($str);

		$str = CommonModifiers::typo($str);

		$str = preg_replace_callback('!<a href="/(.+)">!i', function ($matches) use ($options) {
			return sprintf('<a href="%s">%s</a>', $options['prefix'], Utils::transformTitleToURI($matches[1]));
		}, $str);

		return sprintf('<div class="web-content">%s</div>', $str);
	}

	/**
	 * Callback utilisé pour l'extension <<file>> dans le wiki-texte
	 * @param array $args    Arguments passés à l'extension
	 * @param string $content Contenu éventuel (en mode bloc)
	 * @param SkrivLite $skriv   Objet SkrivLite
	 */
	static public function SkrivFile(array $args, ?string $content, SkrivLite $skriv): string
	{
		$name = $args[0] ?? null;
		$caption = $args[1] ?? null;

		if (!$name || !self::$current_path)
		{
			return $skriv->parseError('/!\ Tag file : aucun nom de fichier indiqué.');
		}

		if (empty($caption))
		{
			$caption = $name;
		}

		$url = WWW_URL . self::$current_path . '/' . $name;
		$ext = substr($name, strrpos($name, '.')+1);

		return sprintf(
			'<aside class="file" data-type="%s"><a href="%s" class="internal-file">%s</a> <small>(%s)</small></aside>',
			htmlspecialchars($ext), htmlspecialchars($url), htmlspecialchars($caption), htmlspecialchars(strtoupper($ext))
		);
	}

	/**
	 * Callback utilisé pour l'extension <<image>> dans le wiki-texte
	 * @param array $args    Arguments passés à l'extension
	 * @param string $content Contenu éventuel (en mode bloc)
	 * @param SkrivLite $skriv   Objet SkrivLite
	 */
	static public function SkrivImage(array $args, ?string $content, SkrivLite $skriv): string
	{
		static $align_values = ['left', 'right', 'center'];

		$name = $args[0] ?? null;
		$align = $args[1] ?? null;
		$caption = $args[2] ?? null;

		if (!$name || !self::$current_path)
		{
			return $skriv->parseError('/!\ Tag image : aucun nom de fichier indiqué.');
		}

		$url = WWW_URL . self::$current_path . '/' . $name;
		$thumb_url = sprintf('%s?%dpx', $url, $align == 'center' ? 500 : 200);

		$out = sprintf('<a href="%s" class="internal-image" target="_image"><img src="%s" alt="%s" loading="lazy" /></a>',
			htmlspecialchars($url),
			htmlspecialchars($thumb_url),
			htmlspecialchars($caption ?? $name)
		);

Modified src/include/lib/Garradin/Web/Skeleton.php from [cabedd0259] to [8323663708].

97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
			}

			$ut->assignArray($params);

			return $ut->fetch();
		}
		elseif ($this->file) {
			$this->file->fetch();
		}
		else {
			return file_get_contents($this->defaultPath());
		}
	}

	public function display(array $params = []): void







|







97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
			}

			$ut->assignArray($params);

			return $ut->fetch();
		}
		elseif ($this->file) {
			return $this->file->fetch();
		}
		else {
			return file_get_contents($this->defaultPath());
		}
	}

	public function display(array $params = []): void

Modified src/include/lib/Garradin/Web/Web.php from [cb11fd4078] to [26699f060b].

2
3
4
5
6
7
8

9
10
11
12
13
14
15
..
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
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
115
116
117
...
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184

namespace Garradin\Web;

use Garradin\Entities\Web\Page;
use Garradin\Web\Skeleton;
use Garradin\Files\Files;
use Garradin\Config;

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

use KD2\DB\EntityManager as EM;

use const Garradin\{WWW_URI, ADMIN_URL};
................................................................................
		}

		$order = $order_by_date ? 'published DESC' : 'title COLLATE NOCASE';
		$sql = sprintf('SELECT * FROM @TABLE WHERE %s AND type = %d ORDER BY %s;', $where, Page::TYPE_PAGE, $order);
		return EM::getInstance(Page::class)->all($sql, ...$params);
	}

	static public function listCategoriesTree(?int $current = null): array
	{
		$db = DB::getInstance();
		$flat = $db->get('SELECT id, parent_id, title FROM web_pages ORDER BY title COLLATE NOCASE;');

		$parents = [];

		foreach ($flat as $node) {
			if (!isset($parents[$node->parent_id])) {
				$parents[$node->parent_id] = [];
			}

			$parents[(int) $node->parent_id][] = $node;
		}

		$build_tree = function (int $parent_id, int $level) use ($build_tree, $parents): array {
			$nodes = [];

			if (!isset($parents[$parent_id])) {
				return $nodes;
			}

			foreach ($parents[$parent_id] as $node) {
				$node->level = $level;
				$node->children = $build_tree($node->id, $level + 1);
				$nodes[] = $node;
			}

			return $nodes;
		};

		return $build_tree(0, 0);
	}

	static public function getByURI(string $uri): ?Page
	{
		return EM::findOne(Page::class, 'SELECT * FROM @TABLE WHERE path = ?;', $uri);
	}

	static public function get(int $id): ?Page
	{
................................................................................
			return;
		}

		if (Config::getInstance()->get('desactiver_site')) {
			Utils::redirect(ADMIN_URL);
		}

		$skel = null;
		$page = null;

		if ($uri == '') {
			$skel = 'index.html';
		}
		elseif ($page = self::getByURI($uri, 1)) {
			$skel = $page->template();
			$page = $page->asTemplateArray();
		}
		else {
			// Trying to see if a custom template with this name exists
			if (preg_match('!^[\w\d_.-]+$!i', $uri)) {
				$s = new Skeleton($uri);







>







 







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







 







<





|







2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
..
71
72
73
74
75
76
77


































78
79
80
81
82
83
84
...
131
132
133
134
135
136
137

138
139
140
141
142
143
144
145
146
147
148
149
150

namespace Garradin\Web;

use Garradin\Entities\Web\Page;
use Garradin\Web\Skeleton;
use Garradin\Files\Files;
use Garradin\Config;
use Garradin\DB;
use Garradin\Utils;
use Garradin\UserException;
use Garradin\Membres\Session;

use KD2\DB\EntityManager as EM;

use const Garradin\{WWW_URI, ADMIN_URL};
................................................................................
		}

		$order = $order_by_date ? 'published DESC' : 'title COLLATE NOCASE';
		$sql = sprintf('SELECT * FROM @TABLE WHERE %s AND type = %d ORDER BY %s;', $where, Page::TYPE_PAGE, $order);
		return EM::getInstance(Page::class)->all($sql, ...$params);
	}



































	static public function getByURI(string $uri): ?Page
	{
		return EM::findOne(Page::class, 'SELECT * FROM @TABLE WHERE path = ?;', $uri);
	}

	static public function get(int $id): ?Page
	{
................................................................................
			return;
		}

		if (Config::getInstance()->get('desactiver_site')) {
			Utils::redirect(ADMIN_URL);
		}


		$page = null;

		if ($uri == '') {
			$skel = 'index.html';
		}
		elseif ($page = self::getByURI($uri)) {
			$skel = $page->template();
			$page = $page->asTemplateArray();
		}
		else {
			// Trying to see if a custom template with this name exists
			if (preg_match('!^[\w\d_.-]+$!i', $uri)) {
				$s = new Skeleton($uri);

Modified src/www/admin/_inc.php from [6413c8a1fb] to [9ce445a0ed].

5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
use Garradin\Membres\Session;

require_once __DIR__ . '/../../include/init.php';

// Redirection automatique en HTTPS si nécessaire
if (PREFER_HTTPS !== true && PREFER_HTTPS >= 2 && empty($_SERVER['HTTPS']) && empty($_POST))
{
    utils::redirect(str_replace('http://', 'https://', utils::getSelfURL()));
    exit;
}

function f($key)
{
    return \KD2\Form::get($key);
}







|







5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
use Garradin\Membres\Session;

require_once __DIR__ . '/../../include/init.php';

// Redirection automatique en HTTPS si nécessaire
if (PREFER_HTTPS !== true && PREFER_HTTPS >= 2 && empty($_SERVER['HTTPS']) && empty($_POST))
{
    Utils::redirect(str_replace('http://', 'https://', Utils::getSelfURL()));
    exit;
}

function f($key)
{
    return \KD2\Form::get($key);
}

Modified src/www/admin/acc/_inc.php from [800723c7b2] to [b6e3c3f264].

5
6
7
8
9
10
11

12
13
14
15
16
17
18
use Garradin\Accounting\Years;

require_once __DIR__ . '/../_inc.php';

$session->requireAccess($session::SECTION_ACCOUNTING, $session::ACCESS_READ);

$current_year_id = $session->get('acc_year');


if ($current_year_id) {
	// Check that the year is still valid
	$current_year = Years::get($current_year_id);

	if (!$current_year || $current_year->closed) {
		$current_year_id = null;







>







5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
use Garradin\Accounting\Years;

require_once __DIR__ . '/../_inc.php';

$session->requireAccess($session::SECTION_ACCOUNTING, $session::ACCESS_READ);

$current_year_id = $session->get('acc_year');
$current_year = null;

if ($current_year_id) {
	// Check that the year is still valid
	$current_year = Years::get($current_year_id);

	if (!$current_year || $current_year->closed) {
		$current_year_id = null;

Modified src/www/admin/acc/accounts/deposit.php from [602e85e219] to [0f05ff8022].

22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
$checked = f('deposit') ?: [];

$journal = $account->getDepositJournal(CURRENT_YEAR_ID, $checked);
$transaction = new Transaction;
$transaction->id_year = CURRENT_YEAR_ID;
$transaction->id_creator = $session->getUser()->id;

$form->runIf('save', function () use ($account, $checked, $transaction, $journal) {
	if (!count($checked)) {
		throw new UserException('Aucune ligne n\'a été cochée, impossible de créer un dépôt. Peut-être vouliez-vous saisir un virement ?');
	}

	$transaction->importFromDepositForm();
	Transactions::saveDeposit($transaction, $journal, $checked);








|







22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
$checked = f('deposit') ?: [];

$journal = $account->getDepositJournal(CURRENT_YEAR_ID, $checked);
$transaction = new Transaction;
$transaction->id_year = CURRENT_YEAR_ID;
$transaction->id_creator = $session->getUser()->id;

$form->runIf('save', function () use ($checked, $transaction, $journal) {
	if (!count($checked)) {
		throw new UserException('Aucune ligne n\'a été cochée, impossible de créer un dépôt. Peut-être vouliez-vous saisir un virement ?');
	}

	$transaction->importFromDepositForm();
	Transactions::saveDeposit($transaction, $journal, $checked);

Modified src/www/admin/common/saved_searches.php from [8d43e63210] to [b986545306].

2
3
4
5
6
7
8




9
10
11
12
13
14
15
namespace Garradin;

require_once __DIR__ . '/../_inc.php';

if (empty($target) || !in_array($target, Recherche::TARGETS)) {
    throw new UserException('Cible inconnue');
}





$access_section = $target == 'compta' ? $session::SECTION_ACCOUNTING : $session::SECTION_USERS;

$recherche = new Recherche;
$mode = null;

if (qg('edit') || qg('delete'))







>
>
>
>







2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
namespace Garradin;

require_once __DIR__ . '/../_inc.php';

if (empty($target) || !in_array($target, Recherche::TARGETS)) {
    throw new UserException('Cible inconnue');
}

if (empty($search_url)) {
	throw new \LogicException('Missing $search_url');
}

$access_section = $target == 'compta' ? $session::SECTION_ACCOUNTING : $session::SECTION_USERS;

$recherche = new Recherche;
$mode = null;

if (qg('edit') || qg('delete'))

Modified src/www/admin/login.php from [7cddf916d3] to [a530e4030b].

41
42
43
44
45
46
47
48
49
50
51
52
53
    if (!$session->login(f('_id'), f('password'), (bool) f('permanent'))) {
        throw new UserException(sprintf("Connexion impossible.\nVérifiez votre identifiant (%s) et votre mot de passe.", $id_field_name));
    }
}, 'login', ADMIN_URL);

$tpl->assign('ssl_enabled', empty($_SERVER['HTTPS']) ? false : true);
$tpl->assign('prefer_ssl', (bool)PREFER_HTTPS);
$tpl->assign('own_https_url', str_replace('http://', 'https://', utils::getSelfURI()));

$tpl->assign(compact('id_field_name'));
$tpl->assign('changed', qg('changed') !== null);

$tpl->display('admin/login.tpl');







|





41
42
43
44
45
46
47
48
49
50
51
52
53
    if (!$session->login(f('_id'), f('password'), (bool) f('permanent'))) {
        throw new UserException(sprintf("Connexion impossible.\nVérifiez votre identifiant (%s) et votre mot de passe.", $id_field_name));
    }
}, 'login', ADMIN_URL);

$tpl->assign('ssl_enabled', empty($_SERVER['HTTPS']) ? false : true);
$tpl->assign('prefer_ssl', (bool)PREFER_HTTPS);
$tpl->assign('own_https_url', str_replace('http://', 'https://', Utils::getSelfURI()));

$tpl->assign(compact('id_field_name'));
$tpl->assign('changed', qg('changed') !== null);

$tpl->display('admin/login.tpl');

Modified src/www/admin/membres/fiche.php from [25abfd606c] to [c1a81b79b7].

28
29
30
31
32
33
34
35
36
37

if ($session->canAccess($session::SECTION_ACCOUNTING, $session::ACCESS_READ)) {
	$tpl->assign('transactions_linked', Transactions::countForUser($membre->id));
	$tpl->assign('transactions_created', Transactions::countForCreator($membre->id));
}

$tpl->assign('membre', $membre);
$tpl->assign('user_files_path', $membres->getFilesPath($membre->id));

$tpl->display('admin/membres/fiche.tpl');







|


28
29
30
31
32
33
34
35
36
37

if ($session->canAccess($session::SECTION_ACCOUNTING, $session::ACCESS_READ)) {
	$tpl->assign('transactions_linked', Transactions::countForUser($membre->id));
	$tpl->assign('transactions_created', Transactions::countForCreator($membre->id));
}

$tpl->assign('membre', $membre);
$tpl->assign('user_files_path', $membres->getAttachementsDirectory($membre->id));

$tpl->display('admin/membres/fiche.tpl');

Modified src/www/admin/services/user.php from [a930be9f62] to [64386e9e96].

8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

$user = (new Membres)->get((int) qg('id'));

if (!$user) {
	throw new UserException("Cet utilisateur est introuvable");
}

$form->runIf($session->canAccess($session::SECTION_USERS, $session::ACCESS_WRITE) && null !== qg('paid') && qg('su_id'), function () use ($user) {
	$su = Services_User::get((int) qg('su_id'));

	if (!$su) {
		throw new UserException("Cette inscription est introuvable");
	}

	$su->paid = (bool)qg('paid');







|







8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

$user = (new Membres)->get((int) qg('id'));

if (!$user) {
	throw new UserException("Cet utilisateur est introuvable");
}

$form->runIf($session->canAccess($session::SECTION_USERS, $session::ACCESS_WRITE) && null !== qg('paid') && qg('su_id'), function () {
	$su = Services_User::get((int) qg('su_id'));

	if (!$su) {
		throw new UserException("Cette inscription est introuvable");
	}

	$su->paid = (bool)qg('paid');

Modified tests/phpstan.neon from [e5c89f56d6] to [9d90dee4cf].

10
11
12
13
14
15
16
17
18






    excludes_analyse:
      - ../src/include/lib/KD2
    reportUnmatchedIgnoredErrors: false
    ignoreErrors:
        - '#Access to protected property Garradin\\Entities#'
        - '#Access to an undefined property KD2\\DB\\AbstractEntity#'
        -
          message: '#Variable \$(tpl|form|session|user|session|wiki|config|membres) might not be defined#'
          path: ../src/www/*













|
|
>
>
>
>
>
>
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    excludes_analyse:
      - ../src/include/lib/KD2
    reportUnmatchedIgnoredErrors: false
    ignoreErrors:
        - '#Access to protected property Garradin\\Entities#'
        - '#Access to an undefined property KD2\\DB\\AbstractEntity#'
        -
          message: '#Variable \$(tpl|form|session|user|session|wiki|config|membres|current_year) might not be defined#'
          path: ../src/www/*
        -
          message: '#Constant CURRENT_YEAR_ID not found#'
          path: ../src/www/admin/acc/*

includes:
	- phar://phpstan.phar/conf/bleedingEdge.neon