Overview
Comment:Merge with trunk
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | dev
Files: files | file ages | folders
SHA3-256: 16b2ac559b363c5134e7766e30fc9730c73b2f3fc11fa66f3bed284efd18682c
User & Date: bohwaz on 2022-01-02 16:13:53
Other Links: branch diff | manifest | tags
Context
2022-01-02
18:28
Add ID to config_user_fields check-in: dcb692e174 user: bohwaz tags: dev
16:13
Merge with trunk check-in: 16b2ac559b user: bohwaz tags: dev
16:04
Fix checkbox fields: don't allow them to be NULL check-in: 7c23ffae73 user: bohwaz tags: dev
2022-01-01
18:13
More fixes for a future dark theme check-in: 1333ccbe5d user: bohwaz tags: trunk, stable
Changes

Modified src/config.dist.php from [1e502eab28] to [af2818b664].

220
221
222
223
224
225
226





227
228
229
230
231
232
233
 *
 * Si mis à TRUE, alors un bouton sera accessible depuis le menu "Configuration"
 * pour faire une mise à jour en deux clics.
 *
 * Il est conseillé de désactiver cette fonctionnalité si vous ne voulez pas
 * permettre à un utilisateur de casser l'installation !
 *





 * Défaut : true
 *
 * @var bool
 */

//const ENABLE_UPGRADES = true;








>
>
>
>
>







220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
 *
 * Si mis à TRUE, alors un bouton sera accessible depuis le menu "Configuration"
 * pour faire une mise à jour en deux clics.
 *
 * Il est conseillé de désactiver cette fonctionnalité si vous ne voulez pas
 * permettre à un utilisateur de casser l'installation !
 *
 * Si cette constante est désactivée, mais que ENABLE_TECH_DETAILS est activé,
 * la vérification de nouvelle version se fera quand même, mais plutôt que de proposer
 * la mise à jour, Garradin proposera de se rendre sur le site officiel pour
 * télécharger la mise à jour.
 *
 * Défaut : true
 *
 * @var bool
 */

//const ENABLE_UPGRADES = true;

445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
 *
 * %1$s sera remplacé par le chemin du fichier HTML, et %2$s par le chemin du fichier PDF.
 *
 * Exemple : chromium --headless --print-to-pdf=%2$s %1$s
 *
 * Défaut : null
 */
//const PDF_COMMAND = 'wkhtmltopdf %2$s %1$s';

/**
 * Clé de licence
 *
 * Cette clé permet de débloquer certaines fonctionnalités dans des extensions officielles.
 *
 * Pour l'obtenir il faut se créer un compte sur Garradin.eu







|







450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
 *
 * %1$s sera remplacé par le chemin du fichier HTML, et %2$s par le chemin du fichier PDF.
 *
 * Exemple : chromium --headless --print-to-pdf=%2$s %1$s
 *
 * Défaut : null
 */
//const PDF_COMMAND = 'wkhtmltopdf -q --print-media-type %s %s';

/**
 * Clé de licence
 *
 * Cette clé permet de débloquer certaines fonctionnalités dans des extensions officielles.
 *
 * Pour l'obtenir il faut se créer un compte sur Garradin.eu

Modified src/include/lib/Garradin/Accounting/Accounts.php from [9d40c3912e] to [be892e76e5].

81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
	}

	/**
	 * Return only analytical accounts
	 */
	public function listAnalytical(): array
	{
		return $this->em->DB()->getAssoc($this->em->formatQuery('SELECT id, label FROM @TABLE WHERE id_chart = ? AND type = ? ORDER BY code COLLATE NOCASE;'), $this->chart_id, Account::TYPE_ANALYTICAL);
	}

	/**
	 * Return only analytical accounts
	 */
	public function listVolunteering(): array
	{







|







81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
	}

	/**
	 * Return only analytical accounts
	 */
	public function listAnalytical(): array
	{
		return $this->em->DB()->getAssoc($this->em->formatQuery('SELECT id, label FROM @TABLE WHERE id_chart = ? AND type = ? ORDER BY label COLLATE NOCASE;'), $this->chart_id, Account::TYPE_ANALYTICAL);
	}

	/**
	 * Return only analytical accounts
	 */
	public function listVolunteering(): array
	{

Modified src/include/lib/Garradin/Accounting/Reports.php from [b455a0ddd1] to [9709a17af2].

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
		return implode(' AND ', $where);
	}

	/**
	 * Return account sums per year or per account
	 * @param  bool $by_year If true will return accounts grouped by year, if false it will return years grouped by account
	 */
	static public function getAnalyticalSums(bool $by_year = false): \Generator
	{
		$sql = 'SELECT a.label AS account_label, a.description AS account_description, a.id AS id_account,

			y.id AS id_year, y.label AS year_label, y.start_date, y.end_date,
			SUM(l.credit - l.debit) AS sum, SUM(l.credit) AS credit, SUM(l.debit) AS debit, 0 AS total,
			(SELECT SUM(l2.credit - l2.debit) FROM acc_transactions_lines l2
				INNER JOIN acc_transactions t2 ON t2.id = l2.id_transaction
				INNER JOIN acc_accounts a2 ON a2.id = l2.id_account
				WHERE a2.position = %d AND l2.id_analytical = l.id_analytical AND t2.id_year = t.id_year) * -1 AS sum_expense,
			(SELECT SUM(l2.credit - l2.debit) FROM acc_transactions_lines l2
				INNER JOIN acc_transactions t2 ON t2.id = l2.id_transaction
				INNER JOIN acc_accounts a2 ON a2.id = l2.id_account
				WHERE a2.position = %d AND l2.id_analytical = l.id_analytical AND t2.id_year = t.id_year) AS sum_revenue
			FROM acc_transactions_lines l
			INNER JOIN acc_transactions t ON t.id = l.id_transaction
			INNER JOIN acc_accounts a ON a.id = l.id_analytical
			INNER JOIN acc_years y ON y.id = t.id_year
			GROUP BY %s
			ORDER BY %s;';



		if ($by_year) {
			$group = 'y.id, a.id';
			$order = 'y.start_date DESC, a.code COLLATE NOCASE';
		}
		else {
			$group = 'a.id, y.id';
			$order = 'a.code COLLATE NOCASE, y.id';
		}

		$sql = sprintf($sql, Account::EXPENSE, Account::REVENUE, $group, $order);

		$current = null;

		static $sums = ['credit', 'debit', 'sum'];







|


>

















>
>


|



|







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
		return implode(' AND ', $where);
	}

	/**
	 * Return account sums per year or per account
	 * @param  bool $by_year If true will return accounts grouped by year, if false it will return years grouped by account
	 */
	static public function getAnalyticalSums(bool $by_year = false, bool $order_code = false): \Generator
	{
		$sql = 'SELECT a.label AS account_label, a.description AS account_description, a.id AS id_account,
			a.code AS account_code,
			y.id AS id_year, y.label AS year_label, y.start_date, y.end_date,
			SUM(l.credit - l.debit) AS sum, SUM(l.credit) AS credit, SUM(l.debit) AS debit, 0 AS total,
			(SELECT SUM(l2.credit - l2.debit) FROM acc_transactions_lines l2
				INNER JOIN acc_transactions t2 ON t2.id = l2.id_transaction
				INNER JOIN acc_accounts a2 ON a2.id = l2.id_account
				WHERE a2.position = %d AND l2.id_analytical = l.id_analytical AND t2.id_year = t.id_year) * -1 AS sum_expense,
			(SELECT SUM(l2.credit - l2.debit) FROM acc_transactions_lines l2
				INNER JOIN acc_transactions t2 ON t2.id = l2.id_transaction
				INNER JOIN acc_accounts a2 ON a2.id = l2.id_account
				WHERE a2.position = %d AND l2.id_analytical = l.id_analytical AND t2.id_year = t.id_year) AS sum_revenue
			FROM acc_transactions_lines l
			INNER JOIN acc_transactions t ON t.id = l.id_transaction
			INNER JOIN acc_accounts a ON a.id = l.id_analytical
			INNER JOIN acc_years y ON y.id = t.id_year
			GROUP BY %s
			ORDER BY %s;';

		$order = $order_code ? 'a.code COLLATE NOCASE' : 'a.label COLLATE NOCASE';

		if ($by_year) {
			$group = 'y.id, a.id';
			$order = 'y.start_date DESC, ' . $order;
		}
		else {
			$group = 'a.id, y.id';
			$order = $order . ', y.id';
		}

		$sql = sprintf($sql, Account::EXPENSE, Account::REVENUE, $group, $order);

		$current = null;

		static $sums = ['credit', 'debit', 'sum'];
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
				yield $current;
				$current = null;
			}

			if (null === $current) {
				$current = (object) [
					'id' => $by_year ? $row->id_year : $row->id_account,
					'label' => $by_year ? $row->year_label : $row->account_label,
					'description' => !$by_year ? $row->account_description : null,
					'items' => []
				];

				foreach ($sums as $s) {
					$current->$s = 0;
				}
			}

			$row->label = !$by_year ? $row->year_label : $row->account_label;
			$current->items[] = $row;

			foreach ($sums as $s) {
				$current->$s += $row->$s;
			}
		}








|









|







133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
				yield $current;
				$current = null;
			}

			if (null === $current) {
				$current = (object) [
					'id' => $by_year ? $row->id_year : $row->id_account,
					'label' => $by_year ? $row->year_label : ($order_code ? $row->account_code . ' - ' : '') . $row->account_label,
					'description' => !$by_year ? $row->account_description : null,
					'items' => []
				];

				foreach ($sums as $s) {
					$current->$s = 0;
				}
			}

			$row->label = !$by_year ? $row->year_label : ($order_code ? $row->account_code . ' - ' : '') . $row->account_label;
			$current->items[] = $row;

			foreach ($sums as $s) {
				$current->$s += $row->$s;
			}
		}

273
274
275
276
277
278
279
280

281
282
283
284
285
286
287
		if (isset($criterias['compare_year'])) {
			$where = self::getWhereClause(array_merge($criterias, ['year' => (int)$criterias['compare_year']]));
			$sql = sprintf($query, $reverse, Line::TABLE, Transaction::TABLE, Account::TABLE, $where, $remove_zero, $order);

			foreach ($db->iterate($sql) as $row) {
				if (!isset($out[$row->code])) {
					$row->sum2 = $row->sum;
					$row->sum = 0;

					$row->change = null;
					$out[$row->code] = $row;
				}
				else {
					$out[$row->code]->sum2 = $row->sum;
					$out[$row->code]->change = ($out[$row->code]->sum - $row->sum);
				}







|
>







276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
		if (isset($criterias['compare_year'])) {
			$where = self::getWhereClause(array_merge($criterias, ['year' => (int)$criterias['compare_year']]));
			$sql = sprintf($query, $reverse, Line::TABLE, Transaction::TABLE, Account::TABLE, $where, $remove_zero, $order);

			foreach ($db->iterate($sql) as $row) {
				if (!isset($out[$row->code])) {
					$row->sum2 = $row->sum;
					$row->sum = null;
					$row->id = null;
					$row->change = null;
					$out[$row->code] = $row;
				}
				else {
					$out[$row->code]->sum2 = $row->sum;
					$out[$row->code]->change = ($out[$row->code]->sum - $row->sum);
				}
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
	{
		$accounts = ['asset' => [], 'liability' => []];
		$sums = $sums2 = $change = ['asset' => 0, 'liability' => 0];

		$position_criteria = ['position' => [Account::ASSET, Account::LIABILITY, Account::ASSET_OR_LIABILITY]];
		$list = self::getClosingSumsWithAccounts($criterias + $position_criteria);

		//var_dump($list); exit;

		foreach ($list as $row) {
			if ($row->sum == 0) {
				// Ignore empty accounts
				continue;
			}

			$position = $row->position;

			if ($position == Account::ASSET_OR_LIABILITY) {







<


|







304
305
306
307
308
309
310

311
312
313
314
315
316
317
318
319
320
	{
		$accounts = ['asset' => [], 'liability' => []];
		$sums = $sums2 = $change = ['asset' => 0, 'liability' => 0];

		$position_criteria = ['position' => [Account::ASSET, Account::LIABILITY, Account::ASSET_OR_LIABILITY]];
		$list = self::getClosingSumsWithAccounts($criterias + $position_criteria);



		foreach ($list as $row) {
			if ($row->sum == 0 && $row->sum2 == 0) {
				// Ignore empty accounts
				continue;
			}

			$position = $row->position;

			if ($position == Account::ASSET_OR_LIABILITY) {

Modified src/include/lib/Garradin/Config.php from [bd9053bb32] to [2978dd1e2f].

177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
	public function importForm($source = null): void
	{
		if (null === $source) {
			$source = $_POST;
		}

		// N'enregistrer les couleurs que si ce ne sont pas les couleurs par défaut
		if (!isset($source['couleur1'], $source['couleur2'])
			|| ($source['couleur1'] == ADMIN_COLOR1 && $source['couleur2'] == ADMIN_COLOR2))
		{
			$source['couleur1'] = null;
			$source['couleur2'] = null;
		}

		parent::importForm($source);
	}







|
|







177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
	public function importForm($source = null): void
	{
		if (null === $source) {
			$source = $_POST;
		}

		// N'enregistrer les couleurs que si ce ne sont pas les couleurs par défaut
		if (isset($source['couleur1'], $source['couleur2'])
			&& ($source['couleur1'] == ADMIN_COLOR1 && $source['couleur2'] == ADMIN_COLOR2))
		{
			$source['couleur1'] = null;
			$source['couleur2'] = null;
		}

		parent::importForm($source);
	}
272
273
274
275
276
277
278
















279
280
281
282
283
284
285
	}


	public function hasFile(string $key): bool
	{
		return $this->files[$key] ? true : false;
	}

















	public function setFile(string $key, ?string $value, bool $upload = false): ?File
	{
		$f = Files::get(self::FILES[$key]);
		$files = $this->files;
		$type = self::FILES_TYPES[$key];
		$path = self::FILES[$key];







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







272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
	}


	public function hasFile(string $key): bool
	{
		return $this->files[$key] ? true : false;
	}

	public function updateFiles(): void
	{
		$files = $this->files;

		foreach (self::FILES as $key => $path) {
			if ($f = Files::get($path)) {
				$files[$key] = $f->modified->getTimestamp();
			}
			else {
				$files[$key] = null;
			}
		}

		$this->set('files', $files);
	}

	public function setFile(string $key, ?string $value, bool $upload = false): ?File
	{
		$f = Files::get(self::FILES[$key]);
		$files = $this->files;
		$type = self::FILES_TYPES[$key];
		$path = self::FILES[$key];

Modified src/include/lib/Garradin/DB.php from [f878c985d1] to [1efae1a655].

74
75
76
77
78
79
80

81
82




83
84
85
86
87
88
89

    static public function getVersion($db)
    {
        $v = (int) $db->querySingle('PRAGMA user_version;');
        $v = self::parseVersion($v);

        if (null === $v) {

            // For legacy version before 1.1.0
            $v = $db->querySingle('SELECT valeur FROM config WHERE cle = \'version\';');




        }

        return $v ?: null;
    }

    static public function parseVersion(int $v): ?string
    {







>
|
|
>
>
>
>







74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94

    static public function getVersion($db)
    {
        $v = (int) $db->querySingle('PRAGMA user_version;');
        $v = self::parseVersion($v);

        if (null === $v) {
            try {
                // For legacy version before 1.1.0
                $v = $db->querySingle('SELECT valeur FROM config WHERE cle = \'version\';');
            }
            catch (\Exception $e) {
                throw new \RuntimeException('Cannot find application version', 0, $e);
            }
        }

        return $v ?: null;
    }

    static public function parseVersion(int $v): ?string
    {

Modified src/include/lib/Garradin/DynamicList.php from [4a558ce327] to [19f54eeec5].

172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
			}

			// Skip columns that require a certain order AND paginated result
			if (isset($properties['only_with_order']) && $this->page > 1) {
				continue;
			}

			if (!isset($properties['label']) && !$include_hidden) {
				continue;
			}

			$select = array_key_exists('select', $properties) ? $properties['select'] : $alias;

			if (null === $select) {
				$select = 'NULL';
			}

			$columns[] = sprintf('%s AS %s', $select, $alias);







<
<
<
<







172
173
174
175
176
177
178




179
180
181
182
183
184
185
			}

			// Skip columns that require a certain order AND paginated result
			if (isset($properties['only_with_order']) && $this->page > 1) {
				continue;
			}





			$select = array_key_exists('select', $properties) ? $properties['select'] : $alias;

			if (null === $select) {
				$select = 'NULL';
			}

			$columns[] = sprintf('%s AS %s', $select, $alias);
211
212
213
214
215
216
217






218
219
220
221
222
223
224
			$sql .= sprintf(' LIMIT %d,%d', $start, $this->per_page);
		}

		foreach (DB::getInstance()->iterate($sql) as $row) {
			if ($this->modifier) {
				call_user_func_array($this->modifier, [&$row]);
			}







			yield $row;
		}
	}

	public function loadFromQueryString()
	{







>
>
>
>
>
>







207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
			$sql .= sprintf(' LIMIT %d,%d', $start, $this->per_page);
		}

		foreach (DB::getInstance()->iterate($sql) as $row) {
			if ($this->modifier) {
				call_user_func_array($this->modifier, [&$row]);
			}

			foreach ($this->columns as $key => $config) {
				if (empty($config['label']) && !$include_hidden) {
					unset($row->$key);
				}
			}

			yield $row;
		}
	}

	public function loadFromQueryString()
	{

Modified src/include/lib/Garradin/Entities/Accounting/Account.php from [cacd2727a4] to [71842e7b88].

184
185
186
187
188
189
190
191
192

193
194
195
196
197
198
199
200
201









202
203
204
205
206
207
208
		$this->assert(array_key_exists($this->type, self::TYPES_NAMES), 'Type invalide');
		$this->assert(array_key_exists($this->position, self::POSITIONS_NAMES), 'Position invalide');
		$this->assert($this->user === 0 || $this->user === 1);

		parent::selfCheck();
	}

	public function listJournal(int $year_id, bool $simple = false)
	{

		$columns = self::LIST_COLUMNS;

		$tables = 'acc_transactions_lines l
			INNER JOIN acc_transactions t ON t.id = l.id_transaction
			LEFT JOIN acc_accounts b ON b.id = l.id_analytical';
		$conditions = sprintf('l.id_account = %d AND t.id_year = %d', $this->id(), $year_id);

		$sum = 0;
		$reverse = $simple && self::isReversed($this->type) ? -1 : 1;










		if ($simple) {
			unset($columns['debit']['label'], $columns['credit']['label'], $columns['line_label']);
			$columns['line_reference']['label'] = 'Réf. paiement';
			$columns['change']['select'] = sprintf($columns['change']['select'], $reverse);
		}
		else {







|

>









>
>
>
>
>
>
>
>
>







184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
		$this->assert(array_key_exists($this->type, self::TYPES_NAMES), 'Type invalide');
		$this->assert(array_key_exists($this->position, self::POSITIONS_NAMES), 'Position invalide');
		$this->assert($this->user === 0 || $this->user === 1);

		parent::selfCheck();
	}

	public function listJournal(int $year_id, bool $simple = false, ?DateTimeInterface $start = null, ?DateTimeInterface $end = null)
	{
		$db = DB::getInstance();
		$columns = self::LIST_COLUMNS;

		$tables = 'acc_transactions_lines l
			INNER JOIN acc_transactions t ON t.id = l.id_transaction
			LEFT JOIN acc_accounts b ON b.id = l.id_analytical';
		$conditions = sprintf('l.id_account = %d AND t.id_year = %d', $this->id(), $year_id);

		$sum = 0;
		$reverse = $simple && self::isReversed($this->type) ? -1 : 1;

		if ($start) {
			$conditions .= sprintf(' AND t.date >= %s', $db->quote($start->format('Y-m-d')));
			$sum = $this->getSumAtDate($year_id, $start) * $reverse;
		}

		if ($end) {
			$conditions .= sprintf(' AND t.date <= %s', $db->quote($end->format('Y-m-d')));
		}

		if ($simple) {
			unset($columns['debit']['label'], $columns['credit']['label'], $columns['line_label']);
			$columns['line_reference']['label'] = 'Réf. paiement';
			$columns['change']['select'] = sprintf($columns['change']['select'], $reverse);
		}
		else {
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458


	public function getSumAtDate(int $year_id, DateTimeInterface $date, bool $reconciled_only = false): int
	{
		$sql = sprintf('SELECT SUM(l.credit) - SUM(l.debit)
			FROM acc_transactions_lines l
			INNER JOIN acc_transactions t ON t.id = l.id_transaction
			wHERE l.id_account = ? AND t.id_year = ? AND t.date < ? %s;',
			$reconciled_only ? 'AND l.reconciled = 1' : '');
		return (int) DB::getInstance()->firstColumn($sql, $this->id(), $year_id, $date->format('Y-m-d'));
	}

	public function importSimpleForm(array $translate_type_position, array $translate_type_codes, ?array $source = null)
	{
		if (null === $source) {







|







454
455
456
457
458
459
460
461
462
463
464
465
466
467
468


	public function getSumAtDate(int $year_id, DateTimeInterface $date, bool $reconciled_only = false): int
	{
		$sql = sprintf('SELECT SUM(l.credit) - SUM(l.debit)
			FROM acc_transactions_lines l
			INNER JOIN acc_transactions t ON t.id = l.id_transaction
			WHERE l.id_account = ? AND t.id_year = ? AND t.date < ? %s;',
			$reconciled_only ? 'AND l.reconciled = 1' : '');
		return (int) DB::getInstance()->firstColumn($sql, $this->id(), $year_id, $date->format('Y-m-d'));
	}

	public function importSimpleForm(array $translate_type_position, array $translate_type_codes, ?array $source = null)
	{
		if (null === $source) {

Modified src/include/lib/Garradin/Entities/Accounting/Transaction.php from [132d3419b2] to [c2407cd421].

818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
				$account->targets_string = implode(':', $account->targets);
			}
		}

		return $details;
	}

	public function payOffFrom(int $id): \stdClass
	{
		$this->_related = EntityManager::findOneById(self::class, $id);

		if (!$this->_related) {
			throw new \LogicException('Écriture d\'origine invalide');
		}

		$this->id_related = $this->_related->id();
		$this->label = ($this->_related->type == Transaction::TYPE_DEBT ? 'Règlement de dette : ' : 'Règlement de créance : ') . $this->_related->label;
		$this->type = $this->_related->type;

		$out = (object) [







|




|







818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
				$account->targets_string = implode(':', $account->targets);
			}
		}

		return $details;
	}

	public function payOffFrom(int $id): ?\stdClass
	{
		$this->_related = EntityManager::findOneById(self::class, $id);

		if (!$this->_related) {
			return null;
		}

		$this->id_related = $this->_related->id();
		$this->label = ($this->_related->type == Transaction::TYPE_DEBT ? 'Règlement de dette : ' : 'Règlement de créance : ') . $this->_related->label;
		$this->type = $this->_related->type;

		$out = (object) [

Modified src/include/lib/Garradin/Entities/Files/File.php from [0a495d2483] to [d1459da3c8].

451
452
453
454
455
456
457

458
459
460
461
462
463
464
	static public function create(string $path, string $name, ?string $source_path, ?string $source_content): self
	{
		if (!isset($source_path) && !isset($source_content)) {
			throw new \InvalidArgumentException('Either source path or source content should be set but not both');
		}

		self::validateFileName($name);

		self::ensureDirectoryExists($path);

		$finfo = \finfo_open(\FILEINFO_MIME_TYPE);

		$fullpath = $path . '/' . $name;
		$file = Files::callStorage('get', $fullpath) ?: new self;
		$file->set('path', $fullpath);







>







451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
	static public function create(string $path, string $name, ?string $source_path, ?string $source_content): self
	{
		if (!isset($source_path) && !isset($source_content)) {
			throw new \InvalidArgumentException('Either source path or source content should be set but not both');
		}

		self::validateFileName($name);
		self::validatePath($path);
		self::ensureDirectoryExists($path);

		$finfo = \finfo_open(\FILEINFO_MIME_TYPE);

		$fullpath = $path . '/' . $name;
		$file = Files::callStorage('get', $fullpath) ?: new self;
		$file->set('path', $fullpath);
939
940
941
942
943
944
945
946
947
948
949
950
951


952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
		if (preg_match(self::FORBIDDEN_EXTENSIONS, $extension)) {
			throw new ValidationException('Extension de fichier non autorisée, merci de renommer le fichier avant envoi.');
		}
	}

	static public function validatePath(string $path): array
	{
		$path = explode('/', $path);

		if (count($path) < 1) {
			throw new ValidationException('Chemin invalide');
		}



		if (!array_key_exists($path[0], self::CONTEXTS_NAMES)) {
			throw new ValidationException('Chemin invalide');
		}

		$context = array_shift($path);

		foreach ($path as $part) {
			if (substr($part, 0, 1) == '.') {
				throw new ValidationException('Chemin invalide');
			}
		}

		$name = array_pop($path);
		$ref = implode('/', $path);
		return [$context, $ref ?: null, $name];
	}

	public function renderFormat(): ?string
	{
		if (substr($this->name, -6) == '.skriv') {
			$format = Render::FORMAT_SKRIV;







|

|
|


>
>
|
|


<
<
|

|



|
|







940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958


959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
		if (preg_match(self::FORBIDDEN_EXTENSIONS, $extension)) {
			throw new ValidationException('Extension de fichier non autorisée, merci de renommer le fichier avant envoi.');
		}
	}

	static public function validatePath(string $path): array
	{
		$parts = explode('/', $path);

		if (count($parts) < 1) {
			throw new ValidationException('Chemin invalide: ' . $path);
		}

		$context = array_shift($parts);

		if (!array_key_exists($context, self::CONTEXTS_NAMES)) {
			throw new ValidationException('Chemin invalide: ' . $path);
		}



		foreach ($parts as $part) {
			if (substr($part, 0, 1) == '.') {
				throw new ValidationException('Chemin invalide: ' . $path);
			}
		}

		$name = array_pop($parts);
		$ref = implode('/', $parts);
		return [$context, $ref ?: null, $name];
	}

	public function renderFormat(): ?string
	{
		if (substr($this->name, -6) == '.skriv') {
			$format = Render::FORMAT_SKRIV;

Modified src/include/lib/Garradin/Entities/Services/Service.php from [1865b4e6a7] to [82c82246f7].

1
2
3
4
5

6
7
8
9
10
11
12
<?php

namespace Garradin\Entities\Services;

use Garradin\Config;

use Garradin\DynamicList;
use Garradin\Entity;
use Garradin\ValidationException;
use Garradin\Utils;
use Garradin\Services\Fees;

class Service extends Entity





>







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

namespace Garradin\Entities\Services;

use Garradin\Config;
use Garradin\DB;
use Garradin\DynamicList;
use Garradin\Entity;
use Garradin\ValidationException;
use Garradin\Utils;
use Garradin\Services\Fees;

class Service extends Entity
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
				'select' => 'su.date',
			],
		];

		$tables = 'services_users su
			INNER JOIN membres m ON m.id = su.id_user
			INNER JOIN services s ON s.id = su.id_service
			INNER JOIN services_fees sf ON sf.id = su.id_fee
			INNER JOIN (SELECT id, MAX(date) FROM services_users GROUP BY id_user, id_service) AS su2 ON su2.id = su.id';
		$conditions = sprintf('su.id_service = %d AND su.paid = 1 AND (su.expiry_date >= date() OR su.expiry_date IS NULL)
			AND m.id_category NOT IN (SELECT id FROM users_categories WHERE hidden = 1)', $this->id());

		$list = new DynamicList($columns, $tables, $conditions);
		$list->groupBy('su.id_user');
		$list->orderBy('date', true);







|







102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
				'select' => 'su.date',
			],
		];

		$tables = 'services_users su
			INNER JOIN membres m ON m.id = su.id_user
			INNER JOIN services s ON s.id = su.id_service
			LEFT JOIN services_fees sf ON sf.id = su.id_fee
			INNER JOIN (SELECT id, MAX(date) FROM services_users GROUP BY id_user, id_service) AS su2 ON su2.id = su.id';
		$conditions = sprintf('su.id_service = %d AND su.paid = 1 AND (su.expiry_date >= date() OR su.expiry_date IS NULL)
			AND m.id_category NOT IN (SELECT id FROM users_categories WHERE hidden = 1)', $this->id());

		$list = new DynamicList($columns, $tables, $conditions);
		$list->groupBy('su.id_user');
		$list->orderBy('date', true);
128
129
130
131
132
133
134
135







	public function expiredUsersList(): DynamicList
	{
		$list = $this->paidUsersList();
		$conditions = sprintf('su.id_service = %d AND su.expiry_date < date() AND m.id_category NOT IN (SELECT id FROM users_categories WHERE hidden = 1)', $this->id());
		$list->setConditions($conditions);
		return $list;
	}
}














|
>
>
>
>
>
>
>
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
	public function expiredUsersList(): DynamicList
	{
		$list = $this->paidUsersList();
		$conditions = sprintf('su.id_service = %d AND su.expiry_date < date() AND m.id_category NOT IN (SELECT id FROM users_categories WHERE hidden = 1)', $this->id());
		$list->setConditions($conditions);
		return $list;
	}

	public function getUsers(bool $paid_only = false) {
		$where = $paid_only ? 'AND paid = 1' : '';
		$id_field = Config::getInstance()->champ_identite;
		$sql = sprintf('SELECT su.id_user, u.%s FROM services_users su INNER JOIN membres u ON u.id = su.id_user WHERE su.id_service = ? %s;', $id_field, $where);
		return DB::getInstance()->getAssoc($sql, $this->id());
	}
}

Modified src/include/lib/Garradin/Entities/Services/Service_User.php from [70686305f9] to [32122d279b].

41
42
43
44
45
46
47
48

49



















50
51
52
53
54
55
56
	protected $_service, $_fee;

	public function selfCheck(): void
	{
		$this->paid = (bool) $this->paid;
		$this->assert($this->id_service, 'Aucune activité spécifiée');
		$this->assert($this->id_user, 'Aucun membre spécifié');
		$this->assert($this->exists() || !DB::getInstance()->test(self::TABLE, 'id_user = ? AND id_service = ? AND date = ?', $this->id_user, $this->id_service, $this->date->format('Y-m-d')), 'Cette activité a déjà été enregistrée pour ce membre et cette date');

		$this->assert(!$this->exists() || !DB::getInstance()->test(self::TABLE, 'id_user = ? AND id_service = ? AND date = ? AND id != ?', $this->id_user, $this->id_service, $this->date->format('Y-m-d'), $this->id()), 'Cette activité a déjà été enregistrée pour ce membre et cette date');



















	}

	public function importForm(?array $source = null)
	{
		if (null === $source) {
			$source = $_POST;
		}







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







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
	protected $_service, $_fee;

	public function selfCheck(): void
	{
		$this->paid = (bool) $this->paid;
		$this->assert($this->id_service, 'Aucune activité spécifiée');
		$this->assert($this->id_user, 'Aucun membre spécifié');
		$this->assert(!$this->isDuplicate(), 'Cette activité a déjà été enregistrée pour ce membre et cette date');
	}

	public function isDuplicate(bool $using_date = true): bool
	{
		$params = [
			'id_user' => $this->id_user,
			'id_service' => $this->id_service,
		];

		if ($using_date) {
			$params['date'] = $this->date->format('Y-m-d');
		}

		$where = array_map(fn($k) => sprintf('%s = ?', $k), array_keys($params));
		$where = implode(' AND ', $where);

		if ($this->exists()) {
			$where .= sprintf(' AND id != %d', $this->id());
		}

		return DB::getInstance()->test(self::TABLE, $where, array_values($params));
	}

	public function importForm(?array $source = null)
	{
		if (null === $source) {
			$source = $_POST;
		}
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
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
		$transaction->importFromNewForm($source);
		$transaction->save();
		$transaction->linkToUser($this->id_user, $this->id());

		return $transaction;
	}

	static public function saveFromForm(int $user_id, ?array $source = null)
	{
		if (null === $source) {
			$source = $_POST;
		}

		$db = DB::getInstance();
		$db->begin();






		$su = new self;
		$su->date = new \DateTime;
		$su->importForm($source);


		if ($su->id_fee && $su->fee()->id_account && $su->id_user) {
			$su->expected_amount = $su->fee()->getAmountForUser($su->id_user);
		}










		$su->save();

		if ($su->id_fee && $su->fee()->id_account
			&& !empty($source['amount'])
			&& !empty($source['create_payment'])) {
			try {
				$su->addPayment($user_id, $source);
			}
			catch (ValidationException $e) {
				if ($e->getMessage() == 'Il n\'est pas possible de créer ou modifier une écriture dans un exercice clôturé') {
					throw new ValidationException('Impossible d\'enregistrer l\'inscription : ce tarif d\'activité est lié à un exercice clôturé. Merci de modifier le tarif et choisir un autre exercice.', 0, $e);
				}
				else {
					throw $e;

				}
			}
		}

		$db->commit();

		return $su;
	}
}







|








>
>
>
>
>
|
|
|
>

|
|
|

>
>
>
>
>
>
>
>
>
|

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









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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
		$transaction->importFromNewForm($source);
		$transaction->save();
		$transaction->linkToUser($this->id_user, $this->id());

		return $transaction;
	}

	static public function createFromForm(array $users, int $creator_id, bool $from_copy = false, ?array $source = null): self
	{
		if (null === $source) {
			$source = $_POST;
		}

		$db = DB::getInstance();
		$db->begin();

		if (!count($users)) {
			throw new ValidationException('Aucun membre n\'a été sélectionné.');
		}

		foreach ($users as $id => $name) {
			$su = new self;
			$su->date = new \DateTime;
			$su->importForm($source);
			$su->id_user = (int) $id;

			if ($su->id_fee && $su->fee()->id_account && $su->id_user) {
				$su->expected_amount = $su->fee()->getAmountForUser($su->id_user);
			}

			if ($su->isDuplicate($from_copy ? false : true)) {
				if ($from_copy) {
					continue;
				}
				else {
					throw new ValidationException(sprintf('%s : Cette activité a déjà été enregistrée pour ce membre et cette date', $name));
				}
			}

			$su->save();

			if ($su->id_fee && $su->fee()->id_account
				&& !empty($source['amount'])
				&& !empty($source['create_payment'])) {
				try {
					$su->addPayment($creator_id, $source);
				}
				catch (ValidationException $e) {
					if ($e->getMessage() == 'Il n\'est pas possible de créer ou modifier une écriture dans un exercice clôturé') {
						throw new ValidationException('Impossible d\'enregistrer l\'inscription : ce tarif d\'activité est lié à un exercice clôturé. Merci de modifier le tarif et choisir un autre exercice.', 0, $e);
					}
					else {
						throw $e;
					}
				}
			}
		}

		$db->commit();

		return $su;
	}
}

Modified src/include/lib/Garradin/Files/Storage/SQLite.php from [69084d5de4] to [3cfb59d324].

124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
	{
		return EM::getInstance(File::class)->all('SELECT * FROM @TABLE WHERE parent = ? ORDER BY type DESC, name COLLATE NOCASE ASC;', $path);
	}

	static public function listDirectoriesRecursively(string $path): array
	{
		$files = [];
		$it = DB::getInstance()->iterate('SELECT path FROM files WHERE parent LIKE ? ORDER BY path;', $path . '/%');

		foreach ($it as $file) {
			$files[] = substr($file->path, strlen($path) + 1);
		}

		return $files;
	}







|







124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
	{
		return EM::getInstance(File::class)->all('SELECT * FROM @TABLE WHERE parent = ? ORDER BY type DESC, name COLLATE NOCASE ASC;', $path);
	}

	static public function listDirectoriesRecursively(string $path): array
	{
		$files = [];
		$it = DB::getInstance()->iterate('SELECT path FROM files WHERE (parent = ? OR parent LIKE ?) AND type = ? ORDER BY path;', $path, $path . '/%', File::TYPE_DIRECTORY);

		foreach ($it as $file) {
			$files[] = substr($file->path, strlen($path) + 1);
		}

		return $files;
	}

Modified src/include/lib/Garradin/Sauvegarde.php from [4511e6cf44] to [f6344f677b].

1
2
3
4
5
6

7
8
9
10
11
12
13
<?php

namespace Garradin;

use Garradin\Users\Session;
use Garradin\Files\Files;


use KD2\ZipWriter;

class Sauvegarde
{
	const NEED_UPGRADE = 0x01 << 2;
	const NOT_AN_ADMIN = 0x01 << 3;






>







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

namespace Garradin;

use Garradin\Users\Session;
use Garradin\Files\Files;
use Garradin\Entities\Files\File;

use KD2\ZipWriter;

class Sauvegarde
{
	const NEED_UPGRADE = 0x01 << 2;
	const NOT_AN_ADMIN = 0x01 << 3;
274
275
276
277
278
279
280

281







282
283
284
285
286
287
288
		header('Content-type: application/zip');
		header(sprintf('Content-Disposition: attachment; filename="%s"', $name));

		$zip = new ZipWriter('php://output');
		$zip->setCompression(0);

		$add_directory = function ($path) use ($zip, &$add_directory) {

			foreach (Files::list($path) as $file) {







				if ($file->type == $file::TYPE_DIRECTORY) {
					$add_directory($file->path);
				}
				else {
					$zip->add($file->path, null, $file->fullpath());
				}
			}







>
|
>
>
>
>
>
>
>







275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
		header('Content-type: application/zip');
		header(sprintf('Content-Disposition: attachment; filename="%s"', $name));

		$zip = new ZipWriter('php://output');
		$zip->setCompression(0);

		$add_directory = function ($path) use ($zip, &$add_directory) {
			try {
				$list = Files::list($path);
			}
			catch (ValidationException $e) {
				// Ignore invalid paths
				return;
			}

			foreach ($list as $file) {
				if ($file->type == $file::TYPE_DIRECTORY) {
					$add_directory($file->path);
				}
				else {
					$zip->add($file->path, null, $file->fullpath());
				}
			}

Modified src/include/lib/Garradin/Template.php from [e86659f8b4] to [ba06a7295b].

174
175
176
177
178
179
180
181
182

183

184
185
186
187
188
189
190
191
192
		}

		return '<p class="block error">' . $this->escape($params['message']) . '</p>';
	}

	protected function widgetIcon(array $params): string
	{
		if (empty($params['href'])) {
			return sprintf('<b class="icn">%s</b>', Utils::iconUnicode($params['shape']));

		}


		return sprintf('<a href="%s" class="icn" title="%s">%s</a>', $this->escape(ADMIN_URL . $params['href']), $this->escape($params['label']), Utils::iconUnicode($params['shape']));
	}

	protected function widgetLink(array $params): string
	{
		$href = $params['href'];
		$label = $params['label'];








|
|
>
|
>

|







174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
		}

		return '<p class="block error">' . $this->escape($params['message']) . '</p>';
	}

	protected function widgetIcon(array $params): string
	{
		$attributes = array_diff_key($params, ['shape']);
		$attributes = array_map(fn($v, $k) => sprintf('%s="%s"', $k, $this->escape($v)),
			$attributes, array_keys($attributes));

		$attributes = implode(' ', $attributes);

		return sprintf('<b class="icn" %s>%s</b>', $attributes, Utils::iconUnicode($params['shape']));
	}

	protected function widgetLink(array $params): string
	{
		$href = $params['href'];
		$label = $params['label'];

282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
		]);

		return $out;
	}

	protected function formInput(array $params)
	{
		static $params_list = ['value', 'default', 'type', 'help', 'label', 'name', 'options', 'source'];

		// Extract params and keep attributes separated
		$attributes = array_diff_key($params, array_flip($params_list));
		$params = array_intersect_key($params, array_flip($params_list));
		extract($params, \EXTR_SKIP);

		if (!isset($name, $type)) {







|







284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
		]);

		return $out;
	}

	protected function formInput(array $params)
	{
		static $params_list = ['value', 'default', 'type', 'help', 'label', 'name', 'options', 'source', 'no_size_limit'];

		// Extract params and keep attributes separated
		$attributes = array_diff_key($params, array_flip($params_list));
		$params = array_intersect_key($params, array_flip($params_list));
		extract($params, \EXTR_SKIP);

		if (!isset($name, $type)) {
368
369
370
371
372
373
374
375
376
377



378
379
380
381
382
383
384
			$attributes['data-input'] = 'time';
			$attributes['size'] = 8;
			$attributes['maxlength'] = 5;
			$attributes['pattern'] = '\d\d?:\d\d?';
		}

		// Create attributes string
		if (array_key_exists('required', $attributes)) {
			$attributes['required'] = 'required';
		}




		if (!empty($attributes['disabled'])) {
			$attributes['disabled'] = 'disabled';
			unset($attributes['required']);
		}
		else {
			unset($attributes['disabled']);







|


>
>
>







370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
			$attributes['data-input'] = 'time';
			$attributes['size'] = 8;
			$attributes['maxlength'] = 5;
			$attributes['pattern'] = '\d\d?:\d\d?';
		}

		// Create attributes string
		if (!empty($attributes['required'])) {
			$attributes['required'] = 'required';
		}
		else {
			unset($attributes['required']);
		}

		if (!empty($attributes['disabled'])) {
			$attributes['disabled'] = 'disabled';
			unset($attributes['required']);
		}
		else {
			unset($attributes['disabled']);
399
400
401
402
403
404
405







406
407
408
409
410
411
412

		array_walk($attributes_string, function (&$v, $k) {
			$v = sprintf('%s="%s"', $k, $v);
		});

		$attributes_string = implode(' ', $attributes_string);








		if ($type == 'select') {
			$input = sprintf('<select %s>', $attributes_string);

			foreach ($options as $_key => $_value) {
				$input .= sprintf('<option value="%s"%s>%s</option>', $_key, $current_value == $_key ? ' selected="selected"' : '', $this->escape($_value));
			}








>
>
>
>
>
>
>







404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424

		array_walk($attributes_string, function (&$v, $k) {
			$v = sprintf('%s="%s"', $k, $v);
		});

		$attributes_string = implode(' ', $attributes_string);

		if ($type == 'radio-btn') {
			$radio = self::formInput(array_merge($params, ['type' => 'radio', 'label' => null, 'help' => null]));
			$out = sprintf('<dd class="radio-btn">%s
				<label for="f_%s_%s"><div><h3>%s</h3>%s</div></label>
			</dd>', $radio, htmlspecialchars($name), htmlspecialchars($value), htmlspecialchars($label), isset($params['help']) ? '<p class="help">' . htmlspecialchars($params['help']) . '</p>' : '');
			return $out;
		}
		if ($type == 'select') {
			$input = sprintf('<select %s>', $attributes_string);

			foreach ($options as $_key => $_value) {
				$input .= sprintf('<option value="%s"%s>%s</option>', $_key, $current_value == $_key ? ' selected="selected"' : '', $this->escape($_value));
			}

489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
			}

			$out .= '</dd>';
		}
		else {
			$out = sprintf('<dt>%s%s</dt><dd>%s</dd>', $label, $required_label, $input);

			if ($type == 'file') {
				$out .= sprintf('<dd class="help"><small>Taille maximale : %s</small></dd>', Utils::format_bytes(Utils::getMaxUploadSize()));
			}

			if (isset($help)) {
				$out .= sprintf('<dd class="help">%s</dd>', $this->escape($help));
			}
		}







|







501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
			}

			$out .= '</dd>';
		}
		else {
			$out = sprintf('<dt>%s%s</dt><dd>%s</dd>', $label, $required_label, $input);

			if ($type == 'file' && empty($params['no_size_limit'])) {
				$out .= sprintf('<dd class="help"><small>Taille maximale : %s</small></dd>', Utils::format_bytes(Utils::getMaxUploadSize()));
			}

			if (isset($help)) {
				$out .= sprintf('<dd class="help">%s</dd>', $this->escape($help));
			}
		}

Modified src/include/lib/Garradin/Upgrade.php from [45aef26818] to [0e58f34637].

112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
				$db->begin();
				$db->import(ROOT . '/include/data/1.1.3_migration.sql');
				$db->commit();
			}

			if (version_compare($v, '1.1.4', '<')) {
				// Set config file names
				$file = Files::get(Config::DEFAULT_FILES['admin_background']);
				$db->update('config', ['value' => $file ? Config::DEFAULT_FILES['admin_background'] : null], 'key = \'admin_background\'');

				$file = Files::get(Config::DEFAULT_FILES['admin_homepage']);
				$db->update('config', ['value' => $file ? Config::DEFAULT_FILES['admin_homepage'] : null], 'key = \'admin_homepage\'');
			}

			if (version_compare($v, '1.1.7', '<')) {
				$db->begin();
				$db->import(ROOT . '/include/data/1.1.7_migration.sql');
				$db->commit();
			}







|
|

|
|







112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
				$db->begin();
				$db->import(ROOT . '/include/data/1.1.3_migration.sql');
				$db->commit();
			}

			if (version_compare($v, '1.1.4', '<')) {
				// Set config file names
				$file = Files::get(Config::FILES['admin_background']);
				$db->update('config', ['value' => $file ? Config::FILES['admin_background'] : null], 'key = :key', ['key' => 'admin_background']);

				$file = Files::get(Config::FILES['admin_homepage']);
				$db->update('config', ['value' => $file ? Config::FILES['admin_homepage'] : null], 'key = :key', ['key' => 'admin_homepage']);
			}

			if (version_compare($v, '1.1.7', '<')) {
				$db->begin();
				$db->import(ROOT . '/include/data/1.1.7_migration.sql');
				$db->commit();
			}
285
286
287
288
289
290
291




292
293
294
295
296
297
298
299
300
301
302





















303
304
305
306
307
308
309
		foreach ($files as $file) {
			rename($file, ROOT . '/data/' . basename($file));
		}
	}

	static public function getLatestVersion(): ?\stdClass
	{




		$config = Config::getInstance();
		$last = $config->get('last_version_check');

		if ($last) {
			$last = json_decode($last);
		}

		// Only check once every two weeks
		if ($last && $last->time > (time() - 3600 * 24 * 5)) {
			return $last;
		}






















		$current_version = garradin_version();
		$last = (object) ['time' => time(), 'version' => null];
		$config->set('last_version_check', json_encode($last));
		$config->save();

		$last->version = self::getInstaller()->latest();







>
>
>
>











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







285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
		foreach ($files as $file) {
			rename($file, ROOT . '/data/' . basename($file));
		}
	}

	static public function getLatestVersion(): ?\stdClass
	{
		if (!ENABLE_TECH_DETAILS && !ENABLE_UPGRADES) {
			return null;
		}

		$config = Config::getInstance();
		$last = $config->get('last_version_check');

		if ($last) {
			$last = json_decode($last);
		}

		// Only check once every two weeks
		if ($last && $last->time > (time() - 3600 * 24 * 5)) {
			return $last;
		}

		return null;
	}

	static public function fetchLatestVersion(): ?\stdClass
	{
		if (!ENABLE_TECH_DETAILS && !ENABLE_UPGRADES) {
			return null;
		}

		$config = Config::getInstance();
		$last = $config->get('last_version_check');

		if ($last) {
			$last = json_decode($last);
		}

		// Only check once every two weeks
		if ($last && $last->time > (time() - 3600 * 24 * 2)) {
			return $last;
		}

		$current_version = garradin_version();
		$last = (object) ['time' => time(), 'version' => null];
		$config->set('last_version_check', json_encode($last));
		$config->save();

		$last->version = self::getInstaller()->latest();

Modified src/include/lib/Garradin/Users/Session.php from [0fa03da91d] to [db4b34e90c].

238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
		if (!$user) {
			return false;
		}

		$query = $this->makePasswordRecoveryQuery($user);

		$message = "Bonjour,\n\nVous avez oublié votre mot de passe ? Pas de panique !\n\n";
		$message.= "Il vous suffit de cliquer sur le lien ci-dessous pour recevoir un nouveau mot de passe.\n\n";
		$message.= ADMIN_URL . 'password.php?c=' . $query;
		$message.= "\n\nSi vous n'avez pas demandé à recevoir ce message, ignorez-le, votre mot de passe restera inchangé.";

		return Utils::sendEmail(Utils::EMAIL_CONTEXT_SYSTEM, $user->email, 'Mot de passe perdu ?', $message, $user->id, $user->pgp_key);
	}

	protected function fetchUserForPasswordRecovery(int $id): ?\stdClass







|







238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
		if (!$user) {
			return false;
		}

		$query = $this->makePasswordRecoveryQuery($user);

		$message = "Bonjour,\n\nVous avez oublié votre mot de passe ? Pas de panique !\n\n";
		$message.= "Il vous suffit de cliquer sur le lien ci-dessous pour modifier votre mot de passe.\n\n";
		$message.= ADMIN_URL . 'password.php?c=' . $query;
		$message.= "\n\nSi vous n'avez pas demandé à recevoir ce message, ignorez-le, votre mot de passe restera inchangé.";

		return Utils::sendEmail(Utils::EMAIL_CONTEXT_SYSTEM, $user->email, 'Mot de passe perdu ?', $message, $user->id, $user->pgp_key);
	}

	protected function fetchUserForPasswordRecovery(int $id): ?\stdClass

Modified src/include/lib/Garradin/Utils.php from [04aa6a6cb3] to [f7279d34e4].

55
56
57
58
59
60
61




62
63
64
65
66
67
68
69



70
71
72
73
74
75
76
        'Fri' => 'ven',
        'Sat' => 'sam',
        'Sun' => 'dim',
    ];

    static public function get_datetime($ts)
    {




        if (is_object($ts) && $ts instanceof \DateTimeInterface) {
            return $ts;
        }
        elseif (is_numeric($ts)) {
            $ts = new \DateTime('@' . $ts);
            $ts->setTimezone(new \DateTimeZone(date_default_timezone_get()));
            return $ts;
        }



        elseif (strlen($ts) == 10) {
            return \DateTime::createFromFormat('!Y-m-d', $ts);
        }
        elseif (strlen($ts) == 19) {
            return \DateTime::createFromFormat('Y-m-d H:i:s', $ts);
        }
        else {







>
>
>
>








>
>
>







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
        'Fri' => 'ven',
        'Sat' => 'sam',
        'Sun' => 'dim',
    ];

    static public function get_datetime($ts)
    {
        if (null === $ts) {
            return null;
        }

        if (is_object($ts) && $ts instanceof \DateTimeInterface) {
            return $ts;
        }
        elseif (is_numeric($ts)) {
            $ts = new \DateTime('@' . $ts);
            $ts->setTimezone(new \DateTimeZone(date_default_timezone_get()));
            return $ts;
        }
        elseif (preg_match('!^\d{2}/\d{2}/\d{4}$!', $ts)) {
            return \DateTime::createFromFormat('!d/m/Y', $ts);
        }
        elseif (strlen($ts) == 10) {
            return \DateTime::createFromFormat('!Y-m-d', $ts);
        }
        elseif (strlen($ts) == 19) {
            return \DateTime::createFromFormat('Y-m-d H:i:s', $ts);
        }
        else {
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089


1090

1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
            case 'prince':
                $cmd = 'prince -o %2$s %1$s';
                break;
            case 'chromium':
                $cmd = 'chromium --headless --disable-gpu --run-all-compositor-stages-before-draw --print-to-pdf-no-header --print-to-pdf=%s %s';
                break;
            case 'wkhtmltopdf':
                $cmd = 'wkhtmltopdf %1$s %2$s';
                break;
            case 'weasyprint':
                $cmd = 'weasyprint %1$s %2$s';
                break;
            default:
                break;
        }



        exec(sprintf($cmd, escapeshellarg($source), escapeshellarg($target)));

        Utils::safe_unlink($source);

        if (!file_exists($target)) {
            throw new \RuntimeException('PDF command failed');
        }

        return $target;
    }

    /**
     * Integer to A-Z, AA-ZZ, AAA-ZZZ, etc.







|








>
>
|
>



|







1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
            case 'prince':
                $cmd = 'prince -o %2$s %1$s';
                break;
            case 'chromium':
                $cmd = 'chromium --headless --disable-gpu --run-all-compositor-stages-before-draw --print-to-pdf-no-header --print-to-pdf=%s %s';
                break;
            case 'wkhtmltopdf':
                $cmd = 'wkhtmltopdf -q --print-media-type %s %s';
                break;
            case 'weasyprint':
                $cmd = 'weasyprint %1$s %2$s';
                break;
            default:
                break;
        }

        $cmd .= ' 2>&1';

        $cmd = sprintf($cmd, escapeshellarg($source), escapeshellarg($target));
        $output = shell_exec($cmd);
        Utils::safe_unlink($source);

        if (!file_exists($target)) {
            throw new \RuntimeException('PDF command failed: ' . $output);
        }

        return $target;
    }

    /**
     * Integer to A-Z, AA-ZZ, AAA-ZZZ, etc.

Modified src/include/lib/Garradin/Web/Web.php from [ce817c70b7] to [d2a80c19dc].

65
66
67
68
69
70
71
72



73
74
75
76
77
78
79
			$deleted = array_map(function ($page) {
				return $page->path;
			}, $deleted);

			$db->exec(sprintf('DELETE FROM web_pages WHERE %s;', $db->where('path', $deleted)));
		}

		foreach (array_keys($new) as $path) {



			$f = Files::get(File::CONTEXT_WEB . '/' . $path . '/index.txt');

			if (!$f) {
				// This is a directory without content, ignore
				continue;
			}








|
>
>
>







65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
			$deleted = array_map(function ($page) {
				return $page->path;
			}, $deleted);

			$db->exec(sprintf('DELETE FROM web_pages WHERE %s;', $db->where('path', $deleted)));
		}

		$new = array_keys($new);
		ksort($new);

		foreach ($new as $path) {
			$f = Files::get(File::CONTEXT_WEB . '/' . $path . '/index.txt');

			if (!$f) {
				// This is a directory without content, ignore
				continue;
			}

Modified src/templates/acc/accounts/journal.tpl from [92b1619c26] to [f4432495e8].

61
62
63
64
65
66
67







68
69
















70
71
72
73
74
75
76
			{linkbutton href="!acc/transactions/new.php?account=%d"|args:$account.id label="Saisir une écriture dans ce compte" shape="plus"}
		{/if}
		</aside>
		<ul>
			<li{if $simple} class="current"{/if}><a href="?id={$account.id}&amp;simple=1&amp;year={$year.id}">Vue simplifiée</a></li>
			<li{if !$simple} class="current"{/if}><a href="?id={$account.id}&amp;simple=0&amp;year={$year.id}">Vue comptable</a></li>
		</ul>







	</nav>
{/if}

















<form method="post" action="{$admin_url}acc/transactions/actions.php">

{include file="common/dynamic_list_head.tpl" check=$can_edit}

	{foreach from=$list->iterate() item="line"}
		<tr>







>
>
>
>
>
>
>


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







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
			{linkbutton href="!acc/transactions/new.php?account=%d"|args:$account.id label="Saisir une écriture dans ce compte" shape="plus"}
		{/if}
		</aside>
		<ul>
			<li{if $simple} class="current"{/if}><a href="?id={$account.id}&amp;simple=1&amp;year={$year.id}">Vue simplifiée</a></li>
			<li{if !$simple} class="current"{/if}><a href="?id={$account.id}&amp;simple=0&amp;year={$year.id}">Vue comptable</a></li>
		</ul>

	{if !$filter.start && !$filter.end}
	<aside>
		{linkbutton shape="search" href="?start=1" label="Filtrer" onclick="g.toggle('#filterForm', true); this.remove(); return false;"}
	</aside>
	{/if}

	</nav>
{/if}

<form method="get" action="{$self_url}"{if !$filter.start && !$filter.end} class="hidden"{/if} id="filterForm">
	<fieldset>
		<legend>Filtrer par date</legend>
		<p>
			Du
			{input type="date" name="start" source=$filter default=$year.start_date}
			au
			{input type="date" name="end" source=$filter default=$year.end_date}
			<input type="hidden" name="id" value="{$account.id}" />
			<input type="hidden" name="year" value="{$year.id}" />
			<input type="hidden" name="simple" value="{$simple}" />
			<input type="submit" value="Filtrer" />
		</p>
	</fieldset>
</form>

<form method="post" action="{$admin_url}acc/transactions/actions.php">

{include file="common/dynamic_list_head.tpl" check=$can_edit}

	{foreach from=$list->iterate() item="line"}
		<tr>
111
112
113
114
115
116
117

118
119



120
121
122
123
124
125
126
	</tbody>
	<tfoot>
		<tr>
			{if $can_edit}
				<td class="check"><input type="checkbox" value="Tout cocher / décocher" id="f_all2" /><label for="f_all2"></label></td>
			{/if}
			{if !$simple}<td></td>{/if}

			<td colspan="3">Solde</td>
			<td class="money">{$sum|raw|money:false}</td>



			{if !$simple}<td></td>{/if}
			<td class="actions" colspan="5">
				{if $can_edit}
					<em>Pour les écritures cochées :</em>
					<input type="hidden" name="from" value="{$self_url}" />
					<input type="hidden" name="year" value="{$year.id}" />
					{csrf_field key="projects_action"}







>
|
|
>
>
>







134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
	</tbody>
	<tfoot>
		<tr>
			{if $can_edit}
				<td class="check"><input type="checkbox" value="Tout cocher / décocher" id="f_all2" /><label for="f_all2"></label></td>
			{/if}
			{if !$simple}<td></td>{/if}
			{if null !== $sum}
				<td colspan="3">Solde</td>
				<td class="money">{$sum|raw|money:false}</td>
			{else}
				<td colspan="4"></td>
			{/if}
			{if !$simple}<td></td>{/if}
			<td class="actions" colspan="5">
				{if $can_edit}
					<em>Pour les écritures cochées :</em>
					<input type="hidden" name="from" value="{$self_url}" />
					<input type="hidden" name="year" value="{$year.id}" />
					{csrf_field key="projects_action"}

Modified src/templates/acc/reports/_statement_table.tpl from [29eca66656] to [0f100ae5a3].

13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
			</tr>
		</thead>
	{/if}
	<tbody>
	{foreach from=$accounts item="account"}
		<tr class="compte{if isset($year2) && !$account.sum} disabled{/if}">
			<td class="num">
				{if !empty($year)}<a href="{$admin_url}acc/accounts/journal.php?id={$account.id}&amp;year={$year.id}">{$account.code}</a>
				{else}{$account.code}
				{/if}
			</td>
			<th>{$account.label}</th>
			<td class="money">{$account.sum|raw|money:false}</td>
			{if isset($year2)}
				<td class="money">{$account.sum2|raw|money:false}</td>
				<td class="money">{$account.change|raw|money:true:true}</td>
			{/if}
		</tr>
	{/foreach}
	</tbody>
</table>







|













13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
			</tr>
		</thead>
	{/if}
	<tbody>
	{foreach from=$accounts item="account"}
		<tr class="compte{if isset($year2) && !$account.sum} disabled{/if}">
			<td class="num">
				{if !empty($year) && $account.id}<a href="{$admin_url}acc/accounts/journal.php?id={$account.id}&amp;year={$year.id}">{$account.code}</a>
				{else}{$account.code}
				{/if}
			</td>
			<th>{$account.label}</th>
			<td class="money">{$account.sum|raw|money:false}</td>
			{if isset($year2)}
				<td class="money">{$account.sum2|raw|money:false}</td>
				<td class="money">{$account.change|raw|money:true:true}</td>
			{/if}
		</tr>
	{/foreach}
	</tbody>
</table>

Modified src/templates/acc/reports/balance_sheet.tpl from [b458502c4d] to [14beea7ea6].

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
{include file="admin/_head.tpl" title="Bilan" current="acc/years"}

{include file="acc/reports/_header.tpl" current="balance_sheet" title="Bilan" allow_compare=true}

{if $balance.sums.asset != $balance.sums.liability}
	<p class="alert block">
		<strong>Le bilan n'est pas équilibré&nbsp;!</strong><br />
		Vérifiez que vous n'avez pas oublié de reporter des soldes depuis le précédent exercice.
	</p>
{/if}

<table class="statement">
	<colgroup>
		<col width="50%" />
		<col width="50%" />
	</colgroup>
	<tbody>
		<tr>
			<td>
				{include file="acc/reports/_statement_table.tpl" accounts=$balance.accounts.asset caption="Actif"}
			</td>
			<td>
				{include file="acc/reports/_statement_table.tpl" accounts=$balance.accounts.liability caption="Passif"}
			</td>
		</tr>
	</tbody>
	<tfoot>
		<tr>
			<td>












<
<
<
<


|


|







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
{include file="admin/_head.tpl" title="Bilan" current="acc/years"}

{include file="acc/reports/_header.tpl" current="balance_sheet" title="Bilan" allow_compare=true}

{if $balance.sums.asset != $balance.sums.liability}
	<p class="alert block">
		<strong>Le bilan n'est pas équilibré&nbsp;!</strong><br />
		Vérifiez que vous n'avez pas oublié de reporter des soldes depuis le précédent exercice.
	</p>
{/if}

<table class="statement">




	<tbody>
		<tr>
			<td width="50%">
				{include file="acc/reports/_statement_table.tpl" accounts=$balance.accounts.asset caption="Actif"}
			</td>
			<td width="50%">
				{include file="acc/reports/_statement_table.tpl" accounts=$balance.accounts.liability caption="Passif"}
			</td>
		</tr>
	</tbody>
	<tfoot>
		<tr>
			<td>

Modified src/templates/acc/reports/projects.tpl from [c7e14dfc07] to [b9dfb20af8].

11
12
13
14
15
16
17








18
19
20
21
22
23
24
25
26
27
		<li><a href="{$admin_url}acc/years/">Exercices</a></li>
		{if $session->canAccess($session::SECTION_ACCOUNTING, $session::ACCESS_ADMIN)}
		<li><a href="{$admin_url}acc/years/new.php">Nouvel exercice</a></li>
		{/if}
		<li class="current"><a href="{$admin_url}acc/reports/projects.php">Projets <em>(compta analytique)</em></a></li>
	</ul>









	<ul class="sub">
		<li{if !$by_year} class="current"{/if}><a href="{$self_url_no_qs}">Par projet</a></li>
		<li{if $by_year} class="current"{/if}><a href="{$self_url_no_qs}?by_year=1">Par exercice</a></li>
	</ul>
</nav>

<div class="year-header">
	<h2>{$config.nom_asso} — Projets</h2>

	<p class="noprint print-btn">







>
>
>
>
>
>
>
>

|
|







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
		<li><a href="{$admin_url}acc/years/">Exercices</a></li>
		{if $session->canAccess($session::SECTION_ACCOUNTING, $session::ACCESS_ADMIN)}
		<li><a href="{$admin_url}acc/years/new.php">Nouvel exercice</a></li>
		{/if}
		<li class="current"><a href="{$admin_url}acc/reports/projects.php">Projets <em>(compta analytique)</em></a></li>
	</ul>

	<aside>
	{if $order_code}
		{linkbutton href="%s?by_year=%d"|args:$self_url_no_qs,$by_year label="Trier les projets par libellé" shape="menu"}
	{else}
		{linkbutton href="%s?by_year=%d&order_code=1"|args:$self_url_no_qs,$by_year label="Trier les projets par code" shape="menu"}
	{/if}
	</aside>

	<ul class="sub">
		<li{if !$by_year} class="current"{/if}><a href="{$self_url_no_qs}?order_code={$order_code}">Par projet</a></li>
		<li{if $by_year} class="current"{/if}><a href="{$self_url_no_qs}?by_year=1&order_code={$order_code}">Par exercice</a></li>
	</ul>
</nav>

<div class="year-header">
	<h2>{$config.nom_asso} — Projets</h2>

	<p class="noprint print-btn">

Modified src/templates/acc/transactions/new.tpl from [757fa62572] to [acd090fd81].

19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
		{foreach from=$types_details item="type"}
			<dd class="radio-btn">
				{input type="radio" name="type" value=$type.id source=$transaction label=null}
				<label for="f_type_{$type.id}">
					<div>
						<h3>{$type.label}</h3>
						{if !empty($type.help)}
							<p>{$type.help}</p>
						{/if}
					</div>
				</label>
			</dd>
		{/foreach}
		</dl>
	</fieldset>







|







19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
		{foreach from=$types_details item="type"}
			<dd class="radio-btn">
				{input type="radio" name="type" value=$type.id source=$transaction label=null}
				<label for="f_type_{$type.id}">
					<div>
						<h3>{$type.label}</h3>
						{if !empty($type.help)}
							<p class="help">{$type.help}</p>
						{/if}
					</div>
				</label>
			</dd>
		{/foreach}
		</dl>
	</fieldset>

Modified src/templates/admin/_head.tpl from [0c365a5631] to [847f263765].

1
2
3
4
5
6
7
8
9
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr" lang="fr"{if array_key_exists('_dialog', $_GET)} class="dialog"{/if} data-version="{$version_hash}">
<head>
    <meta charset="utf-8" />
    <meta name="v" content="{$version_hash}" />
    <title>{$title}</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" type="text/css" href="{$admin_url}static/admin.css?{$version_hash}" media="all" />
    <script type="text/javascript" src="{$admin_url}static/scripts/global.js?{$version_hash}"></script>

|







1
2
3
4
5
6
7
8
9
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr" lang="fr" class="{if $dialog}dialog{/if}" data-version="{$version_hash}">
<head>
    <meta charset="utf-8" />
    <meta name="v" content="{$version_hash}" />
    <title>{$title}</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" type="text/css" href="{$admin_url}static/admin.css?{$version_hash}" media="all" />
    <script type="text/javascript" src="{$admin_url}static/scripts/global.js?{$version_hash}"></script>

Modified src/templates/admin/config/backup/_menu.tpl from [6843e6c837] to [18d2cda81c].

1
2
3
4
5

6
7
<nav class="tabs">
	<ul class="sub">
		<li{if $current == 'index'} class="current"{/if}><a href="{$admin_url}config/backup/">Informations</a></li>
		<li{if $current == 'save'} class="current"{/if}><a href="{$admin_url}config/backup/save.php">Sauvegarder</a></li>
		<li{if $current == 'restore'} class="current"{/if}><a href="{$admin_url}config/backup/restore.php">Restaurer</a></li>

	</ul>
</nav>





>


1
2
3
4
5
6
7
8
<nav class="tabs">
	<ul class="sub">
		<li{if $current == 'index'} class="current"{/if}><a href="{$admin_url}config/backup/">Informations</a></li>
		<li{if $current == 'save'} class="current"{/if}><a href="{$admin_url}config/backup/save.php">Sauvegarder</a></li>
		<li{if $current == 'restore'} class="current"{/if}><a href="{$admin_url}config/backup/restore.php">Restaurer</a></li>
		<li{if $current == 'documents'} class="current"{/if}><a href="{$admin_url}config/backup/documents.php">Documents</a></li>
	</ul>
</nav>

Added src/templates/admin/config/backup/documents.tpl version [bfb17f7477].



























































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
59
60
61
{include file="admin/_head.tpl" title="Documents" current="config"}

{include file="admin/config/_menu.tpl" current="backup"}

{include file="admin/config/backup/_menu.tpl" current="documents"}

{if $ok}
<p class="confirm block">La restauration a été effectuée.</p>
{/if}

{if $failed}
<p class="alert block">{$failed} fichiers n'ont pas pu être restaurés car ils dépassaient la taille autorisée.</p>
{/if}

{form_errors}

<form method="post" action="{$self_url_no_qs}">

<fieldset>
	<legend>Téléchargement des documents</legend>
	<p class="help">
		Les documents font {$files_size|size_in_bytes}.
	</p>
	{if $files_size > 0}
	<p class="submit">
		{csrf_field key="files_download"}
		{button type="submit" name="download_files" label="Télécharger une archive ZIP des documents sur mon ordinateur" shape="download" class="main"}
	</p>
	{/if}
</fieldset>

</form>

<form method="post" action="{$self_url_no_qs}" id="restoreDocuments" style="display: none;" enctype="multipart/form-data">

<fieldset>
	<legend>Restaurer les documents</legend>
	<p class="help">
		Sélectionner ici une sauvegarde (archive ZIP) des documents pour les restaurer.
	</p>
	<dl>
		{input type="file" name="file" label="Archive ZIP à restaurer" no_size_limit=true required=true}
	</dl>
	<p class="alert block">
		Les fichiers existants qui portent le même nom seront écrasés. Les documents existants qui ne figurent pas dans la sauvegarde ne seront pas affectés.
	</p>
	<p class="submit">
		{csrf_field key="files_restore"}
		{button type="submit" name="restore" label="Restaurer cette sauvegarde des documents" shape="upload" class="main"}
	</p>
	<span class="progress-status"></span>
</fieldset>

</form>

<script type="text/javascript">
g.script('scripts/lib/unzipit.min.js');
g.script('scripts/unzip_restore.js');
</script>

{include file="admin/_foot.tpl"}

Modified src/templates/admin/config/backup/restore.tpl from [79ea96c80e] to [cb8b7573a9].

50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
		{input type="checkbox" name="force_import" value="1" label="Ignorer les erreurs, je sais ce que je fait"}
	</p>
	{/if}
</fieldset>

</form>

{if ENABLE_AUTOMATIC_BACKUPS}

<form method="post" action="{$self_url_no_qs}">

<fieldset>
	<legend>Sauvegardes disponibles</legend>
	{if empty($list)}
		<p class="help">Aucune copie de sauvegarde disponible.</p>
	{else}







<
<







50
51
52
53
54
55
56


57
58
59
60
61
62
63
		{input type="checkbox" name="force_import" value="1" label="Ignorer les erreurs, je sais ce que je fait"}
	</p>
	{/if}
</fieldset>

</form>



<form method="post" action="{$self_url_no_qs}">

<fieldset>
	<legend>Sauvegardes disponibles</legend>
	{if empty($list)}
		<p class="help">Aucune copie de sauvegarde disponible.</p>
	{else}
98
99
100
101
102
103
104
105
106
107
			{button type="submit" name="remove" label="Supprimer cette sauvegarde" shape="delete"}
		</p>
	{/if}
</fieldset>

</form>

{/if}

{include file="admin/_foot.tpl"}







<
<

96
97
98
99
100
101
102


103
			{button type="submit" name="remove" label="Supprimer cette sauvegarde" shape="delete"}
		</p>
	{/if}
</fieldset>

</form>



{include file="admin/_foot.tpl"}

Modified src/templates/admin/config/backup/save.tpl from [4752a2358e] to [04540ba7a1].

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
		Info : la base de données fait actuellement {$db_size|size_in_bytes}.
		{if FILE_STORAGE_BACKEND == 'SQLite'} (Dont {$files_size|size_in_bytes} pour les documents.){/if}
	</p>
	<p class="submit">
		{csrf_field key="backup_download"}
		{button type="submit" name="download" label="Télécharger une copie de la base de données sur mon ordinateur" shape="download" class="main"}
	</p>
</fieldset>

<fieldset>
	<legend>Téléchargement des fichiers</legend>
	<p class="help">
		Les documents font {$files_size|size_in_bytes}.
	</p>
	{if $files_size > 0}
	<p class="submit">
		{csrf_field key="files_download"}
		{button type="submit" name="download_files" label="Télécharger une archive ZIP des documents sur mon ordinateur" shape="download" class="main"}
	</p>
	{/if}
</fieldset>

</form>

<form method="post" action="{$self_url_no_qs}">

<fieldset>







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







22
23
24
25
26
27
28













29
30
31
32
33
34
35
		Info : la base de données fait actuellement {$db_size|size_in_bytes}.
		{if FILE_STORAGE_BACKEND == 'SQLite'} (Dont {$files_size|size_in_bytes} pour les documents.){/if}
	</p>
	<p class="submit">
		{csrf_field key="backup_download"}
		{button type="submit" name="download" label="Télécharger une copie de la base de données sur mon ordinateur" shape="download" class="main"}
	</p>













</fieldset>

</form>

<form method="post" action="{$self_url_no_qs}">

<fieldset>

Modified src/templates/admin/config/index.tpl from [6e42e917da] to [c89efc01fe].

69
70
71
72
73
74
75
76






77

	<p class="submit">
		{csrf_field key="config"}
		{button type="submit" name="save" label="Enregistrer" shape="right" class="main"}
	</p>

</form>







{include file="admin/_foot.tpl"}








>
>
>
>
>
>

69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

	<p class="submit">
		{csrf_field key="config"}
		{button type="submit" name="save" label="Enregistrer" shape="right" class="main"}
	</p>

</form>

{if ENABLE_TECH_DETAILS}
	<script type="text/javascript" async="async">
	fetch(g.admin_url + 'config/?check_version');
	</script>
{/if}

{include file="admin/_foot.tpl"}

Modified src/templates/admin/config/upgrade.tpl from [f0a3b1ba46] to [876a754ddf].

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
			</dl>
		</details>
		<dl class="block error">
			{input type="checkbox" name="upgrade" value=$version label="Je confirme vouloir procéder à la mise à jour" help="Cette action peut casser votre installation !"}
		</dl>
	</fieldset>

	<p class="alert block">N'oubliez pas d'aller {link href="%swiki/?name=Changelog"|args:WEBSITE target="_blank" label="lire le journal des changements"} avant d'effectuer la mise à jour&nbsp;!</p>
	<p class="submit">
		{csrf_field key=$csrf_key}
		{button type="submit" name="next" label="Effectuer la mise à jour" shape="right" class="main"}
	</p>
{else}
	<fieldset>
		<legend>Mise à jour</legend>
		<dl>
		{foreach from=$releases key="version" item="release"}
			{input type="radio" name="download" value=$version label=$version}
			{if $version == $latest}
			<dd class="help">
				Dernière version stable, conseillée.
			</dd>
			{/if}
		{/foreach}
		</dl>
	</fieldset>

	<p class="alert block">N'oubliez pas d'aller {link href="%swiki/?name=Changelog"|args:WEBSITE target="_blank" label="lire le journal des changements"} avant d'effectuer la mise à jour&nbsp;!</p>
	<p class="submit">
		{csrf_field key=$csrf_key}
		{button type="submit" name="next" label="Télécharger" shape="right" class="main"}
	</p>
{/if}

</form>

{include file="admin/_foot.tpl"}







|



















|









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
			</dl>
		</details>
		<dl class="block error">
			{input type="checkbox" name="upgrade" value=$version label="Je confirme vouloir procéder à la mise à jour" help="Cette action peut casser votre installation !"}
		</dl>
	</fieldset>

	<p class="alert block">N'oubliez pas d'aller {link href="%swiki/?name=Changelog"|args:$website target="_blank" label="lire le journal des changements"} avant d'effectuer la mise à jour&nbsp;!</p>
	<p class="submit">
		{csrf_field key=$csrf_key}
		{button type="submit" name="next" label="Effectuer la mise à jour" shape="right" class="main"}
	</p>
{else}
	<fieldset>
		<legend>Mise à jour</legend>
		<dl>
		{foreach from=$releases key="version" item="release"}
			{input type="radio" name="download" value=$version label=$version}
			{if $version == $latest}
			<dd class="help">
				Dernière version stable, conseillée.
			</dd>
			{/if}
		{/foreach}
		</dl>
	</fieldset>

	<p class="alert block">N'oubliez pas d'aller {link href="%swiki/?name=Changelog"|args:$website target="_blank" label="lire le journal des changements"} avant d'effectuer la mise à jour&nbsp;!</p>
	<p class="submit">
		{csrf_field key=$csrf_key}
		{button type="submit" name="next" label="Télécharger" shape="right" class="main"}
	</p>
{/if}

</form>

{include file="admin/_foot.tpl"}

Modified src/templates/admin/membres/_list_actions.tpl from [9f86dc3d7a] to [899906bde5].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
		<tfoot>
			<tr>
				{if $session->canAccess($session::SECTION_USERS, $session::ACCESS_ADMIN)}<td class="check"><input type="checkbox" value="Tout cocher / décocher" id="f_all2" /><label for="f_all2"></label></td>{/if}
				<td class="actions" colspan="{$colspan}">
					<em>Pour les membres cochés :</em>
					{csrf_field key="membres_action"}
					<select name="action">
						<option value="">— Choisir une action à effectuer —</option>
						<option value="move">Changer de catégorie</option>
						{if !isset($export) || $export != false}
						<option value="csv">Exporter en tableau CSV</option>
						<option value="ods">Exporter en classeur Office</option>
						{/if}
						<option value="delete">Supprimer</option>
					</select>
					<noscript>
						<input type="submit" value="OK" />
					</noscript>
				</td>
			</tr>
		</tfoot>













|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
		<tfoot>
			<tr>
				{if $session->canAccess($session::SECTION_USERS, $session::ACCESS_ADMIN)}<td class="check"><input type="checkbox" value="Tout cocher / décocher" id="f_all2" /><label for="f_all2"></label></td>{/if}
				<td class="actions" colspan="{$colspan}">
					<em>Pour les membres cochés :</em>
					{csrf_field key="membres_action"}
					<select name="action">
						<option value="">— Choisir une action à effectuer —</option>
						<option value="move">Changer de catégorie</option>
						{if !isset($export) || $export != false}
						<option value="csv">Exporter en tableau CSV</option>
						<option value="ods">Exporter en classeur Office</option>
						{/if}
						<option value="delete">Supprimer le membre</option>
					</select>
					<noscript>
						<input type="submit" value="OK" />
					</noscript>
				</td>
			</tr>
		</tfoot>

Modified src/templates/admin/membres/fiche.tpl from [858a55f227] to [56a79db109].

28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
    </dd>
    {/foreach}
    <dd>
        {if count($services)}
            {linkbutton href="!services/user/?id=%d"|args:$membre.id label="Liste des inscriptions aux activités" shape="menu"}
        {/if}
        {if $session->canAccess($session::SECTION_USERS, $session::ACCESS_WRITE)}
            {linkbutton href="!services/user/add.php?user=%d"|args:$membre.id label="Inscrire à une activité" shape="plus"}
        {/if}
    </dd>
    {if count($services)}
    <dd>
        {linkbutton shape="alert" label="Liste des rappels envoyés" href="!services/reminders/user.php?id=%d"|args:$membre.id}
    </dd>
    {/if}







|







28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
    </dd>
    {/foreach}
    <dd>
        {if count($services)}
            {linkbutton href="!services/user/?id=%d"|args:$membre.id label="Liste des inscriptions aux activités" shape="menu"}
        {/if}
        {if $session->canAccess($session::SECTION_USERS, $session::ACCESS_WRITE)}
            {linkbutton href="!services/user/subscribe.php?user=%d"|args:$membre.id label="Inscrire à une activité" shape="plus"}
        {/if}
    </dd>
    {if count($services)}
    <dd>
        {linkbutton shape="alert" label="Liste des rappels envoyés" href="!services/reminders/user.php?id=%d"|args:$membre.id}
    </dd>
    {/if}

Modified src/templates/services/user/_service_user_form.tpl from [10a2edc910] to [75a8977fdd].

1
2
3
4
5
6
7
8
9
10
11
12
<?php
assert(isset($create) && is_bool($create));
assert(isset($has_past_services) && is_bool($has_past_services));
assert(isset($current_only) && is_bool($current_only));
assert(!empty($user_name) && !empty($user_id));
assert(isset($form_url) && is_string($form_url));
assert(isset($today) && $today instanceof \DateTimeInterface);
assert($create === false || isset($account_targets));
assert(isset($grouped_services) && is_array($grouped_services));
?>

<form method="post" action="{$self_url}" data-focus="1" data-create="{$create|escape:json}">




<







1
2
3
4

5
6
7
8
9
10
11
<?php
assert(isset($create) && is_bool($create));
assert(isset($has_past_services) && is_bool($has_past_services));
assert(isset($current_only) && is_bool($current_only));

assert(isset($form_url) && is_string($form_url));
assert(isset($today) && $today instanceof \DateTimeInterface);
assert($create === false || isset($account_targets));
assert(isset($grouped_services) && is_array($grouped_services));
?>

<form method="post" action="{$self_url}" data-focus="1" data-create="{$create|escape:json}">
20
21
22
23
24
25
26

27


28









29
30
31
32
33
34
35
	</nav>
	{/if}

	<fieldset>
		<legend>Inscrire un membre à une activité</legend>

		<dl>

			<dt>Membre sélectionné</dt>


			<dd><h3>{$user_name}</h3><input type="hidden" name="id_user" value="{$user_id}" /></dd>










			<dt><label for="f_service_ID">Activité</label> <b>(obligatoire)</b></dt>

			{foreach from=$grouped_services item="service"}
				<dd class="radio-btn">
					{input type="radio" name="id_service" value=$service.id data-duration=$service.duration data-expiry=$service.expiry_date|date_short label=null source=$service_user}
					<label for="f_id_service_{$service.id}">







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







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
	</nav>
	{/if}

	<fieldset>
		<legend>Inscrire un membre à une activité</legend>

		<dl>
		{if $create && $users}
			<dt>Membres inscrits</dt>
			{if count($users) <= 10}
				{foreach from=$users key="id" item="name"}
				<dd>{$name}<input type="hidden" name="users[{$id}]" value="{$name}" /></dd>
				{/foreach}
			{else}
				<dd>{$users|count} membres sélectionnés</dd>
			{/if}
		{elseif $create && $copy_service}
			<dt>Recopier depuis l'activité</dt>
			<dd><strong>{$copy_service.label}</strong><input type="hidden" name="copy_service" value="{$copy_service.id}" /></dd>
			<dd><em>{if $copy_service_only_paid}(seulement les inscriptions marquées comme payées){else}(toutes les inscriptions){/if}</em><input type="hidden" name="copy_service_only_paid" value="{$copy_service_only_paid}" /></dd>
		{/if}

			<dt><label for="f_service_ID">Activité</label> <b>(obligatoire)</b></dt>

			{foreach from=$grouped_services item="service"}
				<dd class="radio-btn">
					{input type="radio" name="id_service" value=$service.id data-duration=$service.duration data-expiry=$service.expiry_date|date_short label=null source=$service_user}
					<label for="f_id_service_{$service.id}">
65
66
67
68
69
70
71
72
73
74
75


76
77


78
79
80
81
82
83
84
			{foreach from=$service.fees key="service_id" item="fee"}
			<dd class="radio-btn">
				{input type="radio" name="id_fee" value=$fee.id data-user-amount=$fee.user_amount data-account=$fee.id_account data-year=$fee.id_year label=null source=$service_user }
				<label for="f_id_fee_{$fee.id}">
					<div>
						<h3>{$fee.label}</h3>
						<p>
							{if !$fee.user_amount}
								prix libre ou gratuit
							{elseif $fee.user_amount && $fee.formula}
								<strong>{$fee.user_amount|raw|money_currency}</strong> (montant calculé)


							{elseif $fee.user_amount}
								<strong>{$fee.user_amount|raw|money_currency}</strong>


							{/if}
						</p>
						{if $fee.description}
						<p class="help">
							{$fee.description|escape|nl2br}
						</p>
						{/if}







<
<
|

>
>


>
>







76
77
78
79
80
81
82


83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
			{foreach from=$service.fees key="service_id" item="fee"}
			<dd class="radio-btn">
				{input type="radio" name="id_fee" value=$fee.id data-user-amount=$fee.user_amount data-account=$fee.id_account data-year=$fee.id_year label=null source=$service_user }
				<label for="f_id_fee_{$fee.id}">
					<div>
						<h3>{$fee.label}</h3>
						<p>


							{if $fee.user_amount && $fee.formula}
								<strong>{$fee.user_amount|raw|money_currency}</strong> (montant calculé)
							{elseif $fee.formula}
								montant calculé, variable selon les membres
							{elseif $fee.user_amount}
								<strong>{$fee.user_amount|raw|money_currency}</strong>
							{else}
								prix libre ou gratuit
							{/if}
						</p>
						{if $fee.description}
						<p class="help">
							{$fee.description|escape|nl2br}
						</p>
						{/if}
118
119
120
121
122
123
124
125
126
127
128
129
130
	</fieldset>
	{/if}

	<p class="submit">
		{csrf_field key=$csrf_key}
		{button type="submit" name="save" label="Enregistrer" shape="right" class="main"}

		{if $create}
			{button type="submit" name="save_and_add_payment" class="accounting" label="Enregistrer et ajouter un autre règlement" shape="plus"}
		{/if}
	</p>

</form>







|





131
132
133
134
135
136
137
138
139
140
141
142
143
	</fieldset>
	{/if}

	<p class="submit">
		{csrf_field key=$csrf_key}
		{button type="submit" name="save" label="Enregistrer" shape="right" class="main"}

		{if $create && $users && count($users) == 1}
			{button type="submit" name="save_and_add_payment" class="accounting" label="Enregistrer et ajouter un autre règlement" shape="plus"}
		{/if}
	</p>

</form>

Modified src/templates/services/user/add.tpl from [e38a83f3e8] to [9109d7951b].

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
{include file="admin/_head.tpl" title="Inscrire à une activité" current="membres/services"}

{include file="services/_nav.tpl" current="save" fee=null service=null}

{form_errors}

{if !$user_id}
<form method="post" action="{$self_url}">

	<fieldset>
		<legend>Inscrire un membre à une activité</legend>
		<dl>




			{input type="list" name="user" required=1 label="Sélectionner un membre" default=$selected_user target="membres/selector.php"}












		</dl>
	</fieldset>

	<p class="submit">
		{csrf_field key=$csrf_key}
		{button type="submit" name="next" label="Continuer" shape="right" class="main"}
	</p>
</form>
{else}







	{include file="services/user/_service_user_form.tpl" create=true}




{/if}


{include file="admin/_foot.tpl"}






<
|


|

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




<



|
>
>
>
>
>
>
|
<

>
>
>
|
>


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
{include file="admin/_head.tpl" title="Inscrire à une activité" current="membres/services"}

{include file="services/_nav.tpl" current="save" fee=null service=null}

{form_errors}


<form method="post" action="subscribe.php" data-focus="button">

	<fieldset>
		<legend>Inscrire à une activité</legend>
		<dl>
			{input type="radio-btn" name="choice" value="1" label="Sélectionner des membres" default=1}
			{input type="radio-btn" name="choice" value="2" label="Recopier depuis une activité" help="Utile si vous avez une cotisation par année civile par exemple : copie les membres inscrits l'année précédente dans la nouvelle année."}
		</dl>
	</fieldset>

	<fieldset class="c1">
		<legend>Inscrire des membres</legend>
		<dl>
			{input type="list" name="users" required=true label="Membres à inscrire" target="membres/selector.php" multiple=true}
		</dl>
	</fieldset>

	<fieldset class="c2">
		<legend>Recopier depuis une activité</legend>
		<dl>
			{input type="select" name="copy_service" label="Activité à recopier" options=$services required=true default=0}
			{input type="checkbox" name="copy_service_only_paid" value="1" label="Ne recopier que les membres dont l'inscription est payée"}
		</dl>
	</fieldset>

	<p class="submit">

		{button type="submit" name="next" label="Continuer" shape="right" class="main"}
	</p>
</form>

<script type="text/javascript">
{literal}
function selectChoice() {
	let choice = $('#f_choice_1').form.choice.value;
	g.toggle('.c1', choice == 1);
	g.toggle('.c2', choice == 2);
}


selectChoice();
$('#f_choice_1').onchange = selectChoice;
$('#f_choice_2').onchange = selectChoice;
{/literal}
</script>

{include file="admin/_foot.tpl"}

Modified src/templates/services/user/index.tpl from [0bde5bd459] to [43943e4257].

1
2
3
4
5
6
7
8
9
10
11
12
{include file="admin/_head.tpl" title="%s — Inscriptions aux activités et cotisations"|args:$user.identite current="membres/services"}

<p>
	{linkbutton href="!membres/fiche.php?id=%d"|args:$user.id label="Retour à la fiche membre" shape="user"}
	{linkbutton href="!services/user/add.php?user=%d"|args:$user.id label="Inscrire à une activité" shape="plus"}
</p>

{form_errors}

<dl class="cotisation">
	<dt>Statut des inscriptions</dt>
	{foreach from=$services item="service"}




|







1
2
3
4
5
6
7
8
9
10
11
12
{include file="admin/_head.tpl" title="%s — Inscriptions aux activités et cotisations"|args:$user.identite current="membres/services"}

<p>
	{linkbutton href="!membres/fiche.php?id=%d"|args:$user.id label="Retour à la fiche membre" shape="user"}
	{linkbutton href="!services/user/subscribe.php?user=%d"|args:$user.id label="Inscrire à une activité" shape="plus"}
</p>

{form_errors}

<dl class="cotisation">
	<dt>Statut des inscriptions</dt>
	{foreach from=$services item="service"}

Added src/templates/services/user/subscribe.tpl version [8f9805b504].



















>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
{include file="admin/_head.tpl" title="Inscrire à une activité" current="membres/services"}

{include file="services/_nav.tpl" current="save" fee=null service=null}

{form_errors}

{include file="services/user/_service_user_form.tpl" create=true}

{include file="admin/_foot.tpl"}

Modified src/templates/web/_attach.tpl from [dbe6b7661f] to [1e5c124ad7].

27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
			<dt>Alignement&nbsp;:</dt>
			<dd class="align">
				<input type="button" name="left" value="À gauche" />
				<input type="button" name="center" value="Au centre" />
				<input type="button" name="right" value="À droite" />
			</dd>
			<dd class="cancel">
				<input type="reset" value="Annuler" />
			</dd>
		</dl>
	</fieldset>
</form>

{if !empty($images)}
<ul class="gallery">







|







27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
			<dt>Alignement&nbsp;:</dt>
			<dd class="align">
				<input type="button" name="left" value="À gauche" />
				<input type="button" name="center" value="Au centre" />
				<input type="button" name="right" value="À droite" />
			</dd>
			<dd class="cancel">
				<button type="reset">Annuler</button>
			</dd>
		</dl>
	</fieldset>
</form>

{if !empty($images)}
<ul class="gallery">

Modified src/www/admin/acc/accounts/journal.php from [da00ba6416] to [af0fa51c71].

47
48
49
50
51
52
53




54
55
56
57



58


59
60
61
$simple = qg('simple');

// Use simplified view for favourite accounts
if (null === $simple) {
	$simple = (bool) $account->type;
}





$list = $account->listJournal($year_id, $simple);
$list->setTitle(sprintf('Journal - %s - %s', $account->code, $account->label));
$list->loadFromQueryString();




$sum = $account->getSum($year_id, $simple);


$tpl->assign(compact('simple', 'year', 'account', 'list', 'sum', 'can_edit'));

$tpl->display('acc/accounts/journal.tpl');







>
>
>
>
|



>
>
>
|
>
>
|


47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
$simple = qg('simple');

// Use simplified view for favourite accounts
if (null === $simple) {
	$simple = (bool) $account->type;
}

$filter = new \stdClass;
$filter->start = Utils::get_datetime(qg('start'));
$filter->end = Utils::get_datetime(qg('end'));

$list = $account->listJournal($year_id, $simple, $filter->start, $filter->end);
$list->setTitle(sprintf('Journal - %s - %s', $account->code, $account->label));
$list->loadFromQueryString();

$sum = null;

if (!$filter->start && !$filter->end) {
	$sum = $account->getSum($year_id, $simple);
}

$tpl->assign(compact('simple', 'year', 'account', 'list', 'sum', 'can_edit', 'filter'));

$tpl->display('acc/accounts/journal.tpl');

Modified src/www/admin/acc/reports/projects.php from [a9751b3536] to [2489bf7ddc].

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

13
14
15
16
17
18
19
20
<?php
namespace Garradin;

use Garradin\Accounting\Accounts;
use Garradin\Accounting\Reports;
use Garradin\Entities\Accounting\Account;

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

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

$by_year = (bool)qg('by_year');


$tpl->assign(compact('by_year'));
$tpl->assign('list', Reports::getAnalyticalSums($by_year));

$tpl->assign('analytical_type', Account::TYPE_ANALYTICAL);
$tpl->assign('analytical_accounts_count', CURRENT_YEAR_ID ? $current_year->accounts()->countByType(Account::TYPE_ANALYTICAL) : null);

$tpl->display('acc/reports/projects.tpl');












>

|
|





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

use Garradin\Accounting\Accounts;
use Garradin\Accounting\Reports;
use Garradin\Entities\Accounting\Account;

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

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

$by_year = (bool)qg('by_year');
$order_code = (bool)qg('order_code');

$tpl->assign(compact('by_year', 'order_code'));
$tpl->assign('list', Reports::getAnalyticalSums($by_year, $order_code));

$tpl->assign('analytical_type', Account::TYPE_ANALYTICAL);
$tpl->assign('analytical_accounts_count', CURRENT_YEAR_ID ? $current_year->accounts()->countByType(Account::TYPE_ANALYTICAL) : null);

$tpl->display('acc/reports/projects.tpl');

Modified src/www/admin/acc/transactions/new.php from [c612c4dc3a] to [0cd4c89e75].

63
64
65
66
67
68
69

70
71
72
73
74
75
76
77
	$account = $accounts::get($id);

	if (!$account || $account->id_chart != $current_year->id_chart) {
		throw new UserException('Ce compte ne correspond pas à l\'exercice comptable ou n\'existe pas');
	}

	$transaction->type = Transaction::getTypeFromAccountType($account->type);

	$key = sprintf('account_%d_%d', $transaction->type, 0);

	if (!isset($_POST[$key])) {
		$lines[0]['account'] = $_POST[$key] = [$account->id => sprintf('%s — %s', $account->code, $account->label)];
	}
}
elseif (!empty($_POST['lines']) && is_array($_POST['lines'])) {
	$lines = Utils::array_transpose($_POST['lines']);







>
|







63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
	$account = $accounts::get($id);

	if (!$account || $account->id_chart != $current_year->id_chart) {
		throw new UserException('Ce compte ne correspond pas à l\'exercice comptable ou n\'existe pas');
	}

	$transaction->type = Transaction::getTypeFromAccountType($account->type);
	$index = $transaction->type == Transaction::TYPE_DEBT || $transaction->type == Transaction::TYPE_CREDIT ? 1 : 0;
	$key = sprintf('account_%d_%d', $transaction->type, $index);

	if (!isset($_POST[$key])) {
		$lines[0]['account'] = $_POST[$key] = [$account->id => sprintf('%s — %s', $account->code, $account->label)];
	}
}
elseif (!empty($_POST['lines']) && is_array($_POST['lines'])) {
	$lines = Utils::array_transpose($_POST['lines']);

Added src/www/admin/config/backup/documents.php version [185acbc75a].









































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
<?php
namespace Garradin;

use Garradin\Web\Web;
use Garradin\Files\Files;
use Garradin\Entities\Files\File;

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

$form->runIf('restore', function () {
	try {
		// Decompress (inflate) raw data
		if (isset($_FILES['file1']['tmp_name']) && f('compressed')) {
			$f = $_FILES['file1']['tmp_name'];
			file_put_contents($f, gzinflate(file_get_contents($f), 1024*1024*1024));
		}

		File::upload(Utils::dirname(f('target')), 'file1');
	}
	catch (UserException $e) {
		die(json_encode(['success' => false, 'error' => f('target') . ': '. $e->getMessage()]));
	}

	die(json_encode(['success' => true, 'error' => null]));
}, 'files_restore');


// Download all files as ZIP
$form->runIf('download_files', function () {
	(new Sauvegarde)->dumpFilesZip();
	exit;
}, 'files_download');

$ok = qg('ok') !== null;
$failed = (int) qg('failed');

if ($ok) {
	// Reset
	$config->updateFiles();
	$config->save();
	$tpl->assign(compact('config'));

	Web::sync(true);

	Static_Cache::clean(0);
}

$files_size = Files::getUsedQuota();

$tpl->assign(compact('files_size', 'failed', 'ok'));

$tpl->display('admin/config/backup/documents.tpl');

Modified src/www/admin/config/backup/save.php from [0defc760cc] to [9f5c67bf3b].

9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

// Download database
$form->runIf('download', function () use ($s) {
	$s->dump();
	exit;
}, 'backup_download');

// Download all files as ZIP
$form->runIf('download_files', function () use ($s) {
	$s->dumpFilesZip();
	exit;
}, 'files_download');

// Create local backup
$form->runIf('create', function () use ($s) {
	$s->create();
}, 'backup_create', Utils::getSelfURI(['ok' => 'create']));

$form->runIf('config', function () {
	if (!ENABLE_AUTOMATIC_BACKUPS) {







<
<
<
<
<
<







9
10
11
12
13
14
15






16
17
18
19
20
21
22

// Download database
$form->runIf('download', function () use ($s) {
	$s->dump();
	exit;
}, 'backup_download');







// Create local backup
$form->runIf('create', function () use ($s) {
	$s->create();
}, 'backup_create', Utils::getSelfURI(['ok' => 'create']));

$form->runIf('config', function () {
	if (!ENABLE_AUTOMATIC_BACKUPS) {
44
45
46
47
48
49
50
51
52
53
54
55
56
57
	$config = Config::getInstance();
	$config->set('frequence_sauvegardes', $frequency);
	$config->set('nombre_sauvegardes', $number);
	$config->save();
}, 'backup_config', Utils::getSelfURI(['ok' => 'config']));

$db_size = $s->getDBSize();
$files_size = Files::getUsedQuota();

$ok = qg('ok'); // return message

$tpl->assign(compact('ok', 'db_size', 'files_size'));

$tpl->display('admin/config/backup/save.tpl');







|






38
39
40
41
42
43
44
45
46
47
48
49
50
51
	$config = Config::getInstance();
	$config->set('frequence_sauvegardes', $frequency);
	$config->set('nombre_sauvegardes', $number);
	$config->save();
}, 'backup_config', Utils::getSelfURI(['ok' => 'config']));

$db_size = $s->getDBSize();
$files_size = (FILE_STORAGE_BACKEND == 'SQLite') ? Files::getUsedQuota() : null;

$ok = qg('ok'); // return message

$tpl->assign(compact('ok', 'db_size', 'files_size'));

$tpl->display('admin/config/backup/save.tpl');

Modified src/www/admin/config/index.php from [a6385e7542] to [746271d6a0].

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;

use Garradin\Users\Categories;
use Garradin\Files\Files;
use Garradin\Entities\Files\File;

require_once __DIR__ . '/_inc.php';






$config = Config::getInstance();

$form->runIf('save', function () use ($config) {
	$config->importForm();
	$config->save();
}, 'config', Utils::getSelfURI(['ok' => '']));

$latest = ENABLE_TECH_DETAILS ? Upgrade::getLatestVersion() : null;

if (null !== $latest) {
	$latest = $latest->version;
}

$tpl->assign([
	'garradin_version' => garradin_version() . ' [' . (garradin_manifest() ?: 'release') . ']',








>
>
>
>
>








|







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
<?php
namespace Garradin;

use Garradin\Users\Categories;
use Garradin\Files\Files;
use Garradin\Entities\Files\File;

require_once __DIR__ . '/_inc.php';

if (qg('check_version') !== null) {
	echo json_encode(Upgrade::fetchLatestVersion());
	exit;
}

$config = Config::getInstance();

$form->runIf('save', function () use ($config) {
	$config->importForm();
	$config->save();
}, 'config', Utils::getSelfURI(['ok' => '']));

$latest = Upgrade::getLatestVersion();

if (null !== $latest) {
	$latest = $latest->version;
}

$tpl->assign([
	'garradin_version' => garradin_version() . ' [' . (garradin_manifest() ?: 'release') . ']',

Modified src/www/admin/config/upgrade.php from [423a4f1508] to [da1b2c3223].

39
40
41
42
43
44
45

46
47
$form->runIf('upgrade', function () use ($i) {
	$url = ADMIN_URL . 'upgrade.php';
	$i->upgrade(f('upgrade'));
	header('Location: ' . $url);
	exit;
}, $csrf_key);


$tpl->assign(compact('releases', 'latest', 'csrf_key'));
$tpl->display('admin/config/upgrade.tpl');







>


39
40
41
42
43
44
45
46
47
48
$form->runIf('upgrade', function () use ($i) {
	$url = ADMIN_URL . 'upgrade.php';
	$i->upgrade(f('upgrade'));
	header('Location: ' . $url);
	exit;
}, $csrf_key);

$tpl->assign('website', WEBSITE);
$tpl->assign(compact('releases', 'latest', 'csrf_key'));
$tpl->display('admin/config/upgrade.tpl');

Modified src/www/admin/manifest.php from [97a146fba4] to [1e14f72078].

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

const LOGIN_PROCESS = true;
require_once __DIR__ . '/_inc.php';

$manifest = [
	'background_color' => $config->couleur2,
	'theme_color'      => $config->couleur1,
	'description'      => 'Gestion de l\'association',
	'display'          => 'fullscreen',
	'name'             => $config->nom_asso,
	'start_url'        => ADMIN_URL,
	'icons'            => [
		[
			'sizes' => '32x32',
			'src'   => $config->fileURL('favicon'),
			'type'  => 'image/png',







|
|

|







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

const LOGIN_PROCESS = true;
require_once __DIR__ . '/_inc.php';

$manifest = [
	'background_color' => $config->couleur2 ?? ADMIN_COLOR2,
	'theme_color'      => $config->couleur1 ?? ADMIN_COLOR1,
	'description'      => 'Gestion de l\'association',
	'display'          => 'standalone',
	'name'             => $config->nom_asso,
	'start_url'        => ADMIN_URL,
	'icons'            => [
		[
			'sizes' => '32x32',
			'src'   => $config->fileURL('favicon'),
			'type'  => 'image/png',

Modified src/www/admin/plugin.php from [306b922a17] to [3da4ea03c5].

1
2
3
4
5
6
7




8
9
10
11
12
13
14
15
16
17
18
19
<?php

namespace Garradin;

require_once __DIR__ . '/_inc.php';

$page = qg('_u') ?: 'index.php';





$plugin = new Plugin(qg('_p'));

define('Garradin\PLUGIN_ROOT', $plugin->path());
define('Garradin\PLUGIN_URL', ADMIN_URL . 'plugin/' . $plugin->id() . '/');
define('Garradin\PLUGIN_QSP', '?');

$tpl->assign('plugin', $plugin->getInfos());
$tpl->assign('plugin_url', PLUGIN_URL);
$tpl->assign('plugin_root', PLUGIN_ROOT);

$plugin->call('admin/' . $page);







>
>
>
>












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;

require_once __DIR__ . '/_inc.php';

$page = qg('_u') ?: 'index.php';

if (substr($page, -1) == '/') {
	$page .= 'index.php';
}

$plugin = new Plugin(qg('_p'));

define('Garradin\PLUGIN_ROOT', $plugin->path());
define('Garradin\PLUGIN_URL', ADMIN_URL . 'plugin/' . $plugin->id() . '/');
define('Garradin\PLUGIN_QSP', '?');

$tpl->assign('plugin', $plugin->getInfos());
$tpl->assign('plugin_url', PLUGIN_URL);
$tpl->assign('plugin_root', PLUGIN_ROOT);

$plugin->call('admin/' . $page);

Modified src/www/admin/services/user/_form.php from [355c6ae19e] to [f5cd3da56d].

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;

use Garradin\Services\Services;


if (!defined('\Garradin\ROOT')) {
	die();
}

assert(isset($tpl, $form_url));

$current_only = !qg('past_services');







$grouped_services = Services::listGroupedWithFees($user_id, $current_only);

if (!count($grouped_services)) {
	Utils::redirect($form_url . 'past_services=' . (int) $current_only);

}

if (!isset($count_all)) {
	$count_all = Services::count();
}

$has_past_services = count($grouped_services) != $count_all;

$today = new \DateTime;

$tpl->assign([
	'custom_js' => ['service_form.js'],
]);

$tpl->assign(compact('form_url', 'today', 'grouped_services', 'user_name', 'user_id', 'current_only', 'has_past_services'));












|



>
>
>
>
>
>
|


|
>














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

namespace Garradin;

use Garradin\Services\Services;


if (!defined('\Garradin\ROOT')) {
	die();
}

assert(isset($tpl, $form_url, $create));

$current_only = !qg('past_services');

// If there is only one user selected we can calculate the amount
$single_user_id = isset($users) && count($users) == 1 ? key($users) : null;
$copy_service ??= null;
$copy_service_only_paid ??= null;
$users ??= null;

$grouped_services = Services::listGroupedWithFees($single_user_id, $current_only);

if (!count($grouped_services)) {
	$current_only = false;
	$grouped_services = Services::listGroupedWithFees($single_user_id, $current_only);
}

if (!isset($count_all)) {
	$count_all = Services::count();
}

$has_past_services = count($grouped_services) != $count_all;

$today = new \DateTime;

$tpl->assign([
	'custom_js' => ['service_form.js'],
]);

$tpl->assign(compact('form_url', 'today', 'grouped_services', 'current_only', 'has_past_services',
	'create', 'copy_service', 'copy_service_only_paid', 'users'));

Modified src/www/admin/services/user/add.php from [614b57c4f4] to [97a4cefe05].

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
59
60
61
62
63
64
65
66
67
68
69
70
<?php
namespace Garradin;

use Garradin\Services\Services;
use Garradin\Entities\Services\Service_User;
use Garradin\Entities\Accounting\Account;
use Garradin\Entities\Accounting\Transaction;

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

$session->requireAccess($session::SECTION_USERS, $session::ACCESS_WRITE);

// This controller allows to either select a user if none has been provided in the query string
// or subscribe a user to an activity (create a new Service_User entity)
// If $user_id is null then the form is just a select to choose a user

$count_all = Services::count();

if (!$count_all) {
	Utils::redirect(ADMIN_URL . 'services/?CREATE');
}

$user_id = qg('user');

if (!$user_id && ($user_id = f('user'))) {
	$user_id = @key($user_id);
}

if (!$user_id) {
	$user_id = f('id_user');
}

$user_id = (int) $user_id ?: null;
$user_name = $user_id ? (new Membres)->getNom($user_id) : null;

if (!$user_name) {
	$user_id = null;
}

$form_url  = sprintf('?user=%d&', $user_id);
$csrf_key = 'service_save';

// Only load the form if a user has been selected
if ($user_id) {
	require __DIR__ . '/_form.php';
}

$form->runIf(f('save') || f('save_and_add_payment'), function () use ($session) {
	$su = Service_User::saveFromForm($session->getUser()->id);

	if (f('save_and_add_payment')) {
		$url = ADMIN_URL . 'services/user/payment.php?id=' . $su->id;
	}
	else {
		$url = ADMIN_URL . 'services/user/?id=' . $su->id_user;
	}

	Utils::redirect($url);
}, $csrf_key);

$selected_user = $user_name ? [$user_id => $user_name] : null;

$types_details = Transaction::getTypesDetails();
$account_targets = $types_details[Transaction::TYPE_REVENUE]->accounts[1]->targets_string;

$service_user = null;

$tpl->assign(compact('csrf_key', 'selected_user', 'account_targets', 'service_user', 'user_id'));

$tpl->display('services/user/add.tpl');




<
<
<















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


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;

use Garradin\Services\Services;




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

$session->requireAccess($session::SECTION_USERS, $session::ACCESS_WRITE);

// This controller allows to either select a user if none has been provided in the query string
// or subscribe a user to an activity (create a new Service_User entity)
// If $user_id is null then the form is just a select to choose a user

$count_all = Services::count();

if (!$count_all) {
	Utils::redirect(ADMIN_URL . 'services/?CREATE');
}


$services = array_merge([0 => '-- Sélectionner une activité'], Services::listAssoc());











































$tpl->assign(compact('services'));

$tpl->display('services/user/add.tpl');

Modified src/www/admin/services/user/edit.php from [b0b3db3c77] to [f09c2d97e9].

10
11
12
13
14
15
16
17
18
19

20
21
22
23
24
25
26
27
28
29
30
31
32
$su = Services_User::get((int) qg('id'));

if (!$su) {
	throw new UserException("Cette inscription n'existe pas");
}

$csrf_key = 'su_edit_' . $su->id();
$user_id = $su->id_user;
$user_name = (new Membres)->getNom($user_id);
$form_url = sprintf('edit.php?id=%d&', $su->id());


require __DIR__ . '/_form.php';

$form->runIf('save', function () use ($su) {
	$su->importForm();
	$su->save();
}, $csrf_key, ADMIN_URL . 'services/user/?id=' . $user_id);

$service_user = $su;

$tpl->assign(compact('csrf_key', 'service_user'));

$tpl->display('services/user/edit.tpl');







<
|

>






|






10
11
12
13
14
15
16

17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
$su = Services_User::get((int) qg('id'));

if (!$su) {
	throw new UserException("Cette inscription n'existe pas");
}

$csrf_key = 'su_edit_' . $su->id();

$users = [$su->id_user => (new Membres)->getNom($su->id_user)];
$form_url = sprintf('edit.php?id=%d&', $su->id());
$create = false;

require __DIR__ . '/_form.php';

$form->runIf('save', function () use ($su) {
	$su->importForm();
	$su->save();
}, $csrf_key, ADMIN_URL . 'services/user/?id=' . $su->id_user);

$service_user = $su;

$tpl->assign(compact('csrf_key', 'service_user'));

$tpl->display('services/user/edit.tpl');

Added src/www/admin/services/user/subscribe.php version [92ce32bd63].

























































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
<?php
namespace Garradin;

use Garradin\Services\Services;
use Garradin\Entities\Services\Service_User;
use Garradin\Entities\Accounting\Account;
use Garradin\Entities\Accounting\Transaction;

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

$session->requireAccess($session::SECTION_USERS, $session::ACCESS_WRITE);

// This controller allows to either select a user if none has been provided in the query string
// or subscribe a user to an activity (create a new Service_User entity)
// If $user_id is null then the form is just a select to choose a user

$count_all = Services::count();

if (!$count_all) {
	Utils::redirect(ADMIN_URL . 'services/?CREATE');
}

$users = null;
$copy_service = null;
$copy_service_only_paid = null;

if (qg('user') && ($name = (new Membres)->getNom((int)qg('user')))) {
	$users = [(int)qg('user') => $name];
}
elseif (f('users') && is_array(f('users')) && count(f('users'))) {
	$users = f('users');
	$users = array_filter($users, 'intval', \ARRAY_FILTER_USE_KEY);
}
elseif (f('copy_service')
	&& $copy_service = Services::get((int)f('copy_service'))) {
	$copy_service_only_paid = (bool) f('copy_service_only_paid');
}
else {
	throw new UserException('Aucun membre n\'a été sélectionné');
}

$form_url = '?';
$csrf_key = 'service_save';
$create = true;

// Only load the form if a user has been selected
require __DIR__ . '/_form.php';

$form->runIf(f('save') || f('save_and_add_payment'), function () use ($session, $users, $copy_service, $copy_service_only_paid) {
	if ($copy_service) {
		$users = $copy_service->getUsers($copy_service_only_paid);
	}

	$su = Service_User::createFromForm($users, $session->getUser()->id, $copy_service ? true : false);

	if (count($users) > 1) {
		$url = ADMIN_URL . 'services/details.php?id=' . $su->id_service;
	}
	elseif (f('save_and_add_payment')) {
		$url = ADMIN_URL . 'services/user/payment.php?id=' . $su->id;
	}
	else {
		$url = ADMIN_URL . 'services/user/?id=' . $su->id_user;
	}

	Utils::redirect($url);
}, $csrf_key);

$types_details = Transaction::getTypesDetails();
$account_targets = $types_details[Transaction::TYPE_REVENUE]->accounts[1]->targets_string;

$service_user = null;

$tpl->assign(compact('csrf_key', 'users', 'account_targets', 'service_user'));

$tpl->display('services/user/subscribe.tpl');

Modified src/www/admin/static/handheld.css from [bb9608a6af] to [9b9dce4df7].

169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
}

.filterCategory, .searchMember {
	width: auto;
	float: none;
}

.wikiChildren, fieldset.wikiMain, fieldset.wikiRights, fieldset.wikiEncrypt {
	float: none;
	width: auto;
}

dl.describe {
	margin: 0 .5em;
}







|







169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
}

.filterCategory, .searchMember {
	width: auto;
	float: none;
}

fieldset.wikiMain, fieldset.wikiRights, fieldset.wikiEncrypt {
	float: none;
	width: auto;
}

dl.describe {
	margin: 0 .5em;
}

Modified src/www/admin/static/print.css from [cf1afc3c77] to [4b92aef442].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@page {
    size: A4 landscape;
    margin: 1cm;
}

html {
    height: auto;
}

body {
    background: #fff;
    padding: 0;
    margin: 1cm;
    font-size: 10pt;
}

header.header {
    display: none;
}













|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@page {
    size: A4 landscape;
    margin: 1cm;
}

html {
    height: auto;
}

body {
    background: #fff;
    padding: 0;
    margin: 0;
    font-size: 10pt;
}

header.header {
    display: none;
}

Modified src/www/admin/static/scripts/code_editor.css from [7c23f5ec10] to [e8169a1071].

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
.codeEditor {
	width: 100%;
	height: 600px;
	border: 1px solid #999;
	background: #eee;
	position: relative;
	display: block;
}

.codeEditor .sk_help {
	background: #ccc;
	border-top: 2px solid #999;
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	height: 15px;
	padding: 5px 1em 0;
	font-family: "Deja Vu Sans Mono", "Droid Sans Mono", "Courier New", Courier, monospace;
	font-size: 12px;
}

.codeEditor .sk_toolbar {
	background: #ccc;
	border-bottom: 2px solid #999;
	height: 32px;
}

.codeEditor .sk_toolbar select {
	float: right;
	border: none;
	border-radius: .2em;
	height: 24px;
	padding: 1px;
	padding-left: 24px;
	margin: 4px .5em;
	background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAMFBMVEUAAACZm4leYFyOkX1RU09MTkp9f2+ys6KwsZrn5+KanIz///+6u6vGxryDhXtVV1Otpz1oAAAAA3RSTlMA+7omiqY6AAAAgklEQVQI12NI+f9fgQEEfO7e+QBm/BGU+P8fJPrd2LgLCD4AGSCBLCDD8Pfu3bu9QCK/Z86e/esDw4/Hv+/euf0ZyEj7PXPnbCDDI+33kZDQkE8M/11D1969e9eEwRNIlpe332f421Ne0dHRcYnh75k7Z8/cudPEoOIS4uriAlQMAwDpN0taA/g97gAAAABJRU5ErkJggg==") no-repeat 5px center;
	cursor: pointer;
}

.codeEditor .sk_toolbar select:hover {
	background-color: #fff;
}

.codeEditor .sk_toolbar p {
	display: inline;
	padding: .3em .5em;
	border-radius: .5em;
	font-size: .9em;



|
|





|
|











|
|
















|







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
.codeEditor {
	width: 100%;
	height: 600px;
	border: 1px solid var(--gBorderColor);
	background: var(--gLightBackgroundColor);
	position: relative;
	display: block;
}

.codeEditor .sk_help {
	background: var(--gLightBorderColor);
	border-top: 2px solid var(--gBorderColor);
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	height: 15px;
	padding: 5px 1em 0;
	font-family: "Deja Vu Sans Mono", "Droid Sans Mono", "Courier New", Courier, monospace;
	font-size: 12px;
}

.codeEditor .sk_toolbar {
	background: var(--gLightBorderColor);
	border-bottom: 2px solid var(--gBorderColor);
	height: 32px;
}

.codeEditor .sk_toolbar select {
	float: right;
	border: none;
	border-radius: .2em;
	height: 24px;
	padding: 1px;
	padding-left: 24px;
	margin: 4px .5em;
	background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAMFBMVEUAAACZm4leYFyOkX1RU09MTkp9f2+ys6KwsZrn5+KanIz///+6u6vGxryDhXtVV1Otpz1oAAAAA3RSTlMA+7omiqY6AAAAgklEQVQI12NI+f9fgQEEfO7e+QBm/BGU+P8fJPrd2LgLCD4AGSCBLCDD8Pfu3bu9QCK/Z86e/esDw4/Hv+/euf0ZyEj7PXPnbCDDI+33kZDQkE8M/11D1969e9eEwRNIlpe332f421Ne0dHRcYnh75k7Z8/cudPEoOIS4uriAlQMAwDpN0taA/g97gAAAABJRU5ErkJggg==") no-repeat 5px center;
	cursor: pointer;
}

.codeEditor .sk_toolbar select:hover {
	background-color: var(--gHoverLinkColor);
}

.codeEditor .sk_toolbar p {
	display: inline;
	padding: .3em .5em;
	border-radius: .5em;
	font-size: .9em;
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
	border-radius: .2em;
	cursor: pointer;
	text-indent: -70em;
	overflow: hidden;
	background: transparent no-repeat center center;
}

.codeEditor .sk_toolbar input:hover { background-color: #fff; }

.codeEditor .sk_toolbar .save { margin-left: 2em; background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAMFBMVEU6XIthfJfOXAD8rz6PpsDe3t7+/v6YrsZhe5NVV1Py8vL09PTMzc6+0upyn88gSod8oq0HAAAAAXRSTlOZyTXzhgAAAGxJREFUCNdj4P+kBAT6Hxj+XjYGAtv7DH+fbQOCfSBGKBDkgRi7gQAschQoAmbsXrVq1ToQIw0IQGregQGQ8bCjo6OvDsQ4czS04jmQ8eLMzJiDUMYZkMj3ilAIg/9h6PEjJ4AMhp8zgeD/BwBY4VdD5HZlvAAAAABJRU5ErkJggg=="); }
.codeEditor .sk_toolbar .reset { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QAAAAAAAD5Q7t/AAABCklEQVR42p2TsUrDUBSGv4RM2qlLXXwBHdoncG3u5tpZx059gojgZAtC3bqqa7cQKH2FWiFZHUSIg5lChi7Xpak312Ma/eHA5Zzz/5fzHw5UMQZ0wxgDOCb5fnQ8Glxe0AR31zdcPW0mpoDOkoDPJKRI41rywdEpRRrTHeZ4drFIY15nh5w/f4jkea/D2aPafeJKTVuyI4Ut7G0NkVDmnbpxXMAHyJJgl1xPW+XT32emC0SA3z75FugO85Ic8Qf0jR33a/p0lgR6PW1pQJtbiPbNK8GzDLPhNBVAKVUphGGIJD7vdWQBi1SBuSGA/P1FFvgNb8vbH7nFaoO5Ja2Uki6Ommt8ME36t4lfRLtlZDAJ4ScAAAAASUVORK5CYII="); }
.codeEditor .sk_toolbar .search { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAABGdBTUEAAK/INwWK6QAAADBQTFRF////epWwPGiTdYKPdHqB1tbWWW6Hp6enR2yQc6b/dqncME9tmcz/i5CX5e32w9PmM3gGSwAAAAd0Uk5TAAAAQoTswgXk1s4AAABySURBVAjXYxA0v+4syAAEIqXvQh1BDPN3J88Vgxj3/r07EwtmnDw58y2YcebMHLBI6p/358NADN/3Z75eFhQUZFC5/i4WqIaRwcjtdsrWa0AGEDByr2oAM5i4V82AMBh2nGmCMIBCEAbDrhVghpKS9jYA43soYFw0gPcAAAAASUVORK5CYII="); }
.codeEditor .sk_toolbar .search_replace { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAMFBMVEUAAABnVEEAAACEhoVsaWX4liHmz2/8/Pzf398uNDawrKfn5+fOXADj4+P+/v6IioXFYmG9AAAAAnRSTlMAWv0tddIAAACKSURBVAjXY2Dg/w8EAgwMDP/evSuvWwhi7N69Y8UrATDjxurbC4CMn/P3rv9vwMDw3+/J31s9BxkYOl/vm3vjaCiEsSc0hoGhxe+J7dHUgwxckz0nAwUCGDotO3qPph4zYJg8q+P20Zg+AwbJ9rt3VE60f2BgML+76VgHiPH93bt3z8uBjP9g8AEArPJMCYP5JmIAAAAASUVORK5CYII="); }
.codeEditor .sk_toolbar .gotoline { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAMFBMVEX///9BQUEVFRWysrREREQ4ODhVVVX7+/vu7u7q6uqcnJy+vr/m5ub19fXc3Nz///89lwBDAAAAB3RSTlMAAILOakNmMhUGwgAAAHtJREFUCNdjYIABQbE0EEhkEMx793r37tdmDILZu1fv3rVqEYNgfv3/u/X/vwEZu4/Xl5eXARnbd96trS1hcPNaWdd7924Jg+///7UdHR1XGIK01lSfnDmzhUFQY8W6N2fOABl+q/a8OXfmCYOg3jugwJlJDIJKEMCAAQCfIDck8YzyWAAAAABJRU5ErkJggg=="); }
.codeEditor .sk_toolbar .fullscreen { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAADFBMVEUAAABMTFFOTlBOTlCQ1uMHAAAAA3RSTlMAOcKBmOr4AAAAQUlEQVQI12P4/4D7P8N/B0Yo8XsC23+GZw+4pzM4TmBzYsAGgBKODNcecM9m+D+B7T3DPwfG/Qz/G5iABlzg+g8ANzMax/3kkQoAAAAASUVORK5CYII="); }







|







59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
	border-radius: .2em;
	cursor: pointer;
	text-indent: -70em;
	overflow: hidden;
	background: transparent no-repeat center center;
}

.codeEditor .sk_toolbar input:hover { background-color: var(--gLightBackgroundColor); }

.codeEditor .sk_toolbar .save { margin-left: 2em; background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAMFBMVEU6XIthfJfOXAD8rz6PpsDe3t7+/v6YrsZhe5NVV1Py8vL09PTMzc6+0upyn88gSod8oq0HAAAAAXRSTlOZyTXzhgAAAGxJREFUCNdj4P+kBAT6Hxj+XjYGAtv7DH+fbQOCfSBGKBDkgRi7gQAschQoAmbsXrVq1ToQIw0IQGregQGQ8bCjo6OvDsQ4czS04jmQ8eLMzJiDUMYZkMj3ilAIg/9h6PEjJ4AMhp8zgeD/BwBY4VdD5HZlvAAAAABJRU5ErkJggg=="); }
.codeEditor .sk_toolbar .reset { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QAAAAAAAD5Q7t/AAABCklEQVR42p2TsUrDUBSGv4RM2qlLXXwBHdoncG3u5tpZx059gojgZAtC3bqqa7cQKH2FWiFZHUSIg5lChi7Xpak312Ma/eHA5Zzz/5fzHw5UMQZ0wxgDOCb5fnQ8Glxe0AR31zdcPW0mpoDOkoDPJKRI41rywdEpRRrTHeZ4drFIY15nh5w/f4jkea/D2aPafeJKTVuyI4Ut7G0NkVDmnbpxXMAHyJJgl1xPW+XT32emC0SA3z75FugO85Ic8Qf0jR33a/p0lgR6PW1pQJtbiPbNK8GzDLPhNBVAKVUphGGIJD7vdWQBi1SBuSGA/P1FFvgNb8vbH7nFaoO5Ja2Uki6Ommt8ME36t4lfRLtlZDAJ4ScAAAAASUVORK5CYII="); }
.codeEditor .sk_toolbar .search { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAABGdBTUEAAK/INwWK6QAAADBQTFRF////epWwPGiTdYKPdHqB1tbWWW6Hp6enR2yQc6b/dqncME9tmcz/i5CX5e32w9PmM3gGSwAAAAd0Uk5TAAAAQoTswgXk1s4AAABySURBVAjXYxA0v+4syAAEIqXvQh1BDPN3J88Vgxj3/r07EwtmnDw58y2YcebMHLBI6p/358NADN/3Z75eFhQUZFC5/i4WqIaRwcjtdsrWa0AGEDByr2oAM5i4V82AMBh2nGmCMIBCEAbDrhVghpKS9jYA43soYFw0gPcAAAAASUVORK5CYII="); }
.codeEditor .sk_toolbar .search_replace { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAMFBMVEUAAABnVEEAAACEhoVsaWX4liHmz2/8/Pzf398uNDawrKfn5+fOXADj4+P+/v6IioXFYmG9AAAAAnRSTlMAWv0tddIAAACKSURBVAjXY2Dg/w8EAgwMDP/evSuvWwhi7N69Y8UrATDjxurbC4CMn/P3rv9vwMDw3+/J31s9BxkYOl/vm3vjaCiEsSc0hoGhxe+J7dHUgwxckz0nAwUCGDotO3qPph4zYJg8q+P20Zg+AwbJ9rt3VE60f2BgML+76VgHiPH93bt3z8uBjP9g8AEArPJMCYP5JmIAAAAASUVORK5CYII="); }
.codeEditor .sk_toolbar .gotoline { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAMFBMVEX///9BQUEVFRWysrREREQ4ODhVVVX7+/vu7u7q6uqcnJy+vr/m5ub19fXc3Nz///89lwBDAAAAB3RSTlMAAILOakNmMhUGwgAAAHtJREFUCNdjYIABQbE0EEhkEMx793r37tdmDILZu1fv3rVqEYNgfv3/u/X/vwEZu4/Xl5eXARnbd96trS1hcPNaWdd7924Jg+///7UdHR1XGIK01lSfnDmzhUFQY8W6N2fOABl+q/a8OXfmCYOg3jugwJlJDIJKEMCAAQCfIDck8YzyWAAAAABJRU5ErkJggg=="); }
.codeEditor .sk_toolbar .fullscreen { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAADFBMVEUAAABMTFFOTlBOTlCQ1uMHAAAAA3RSTlMAOcKBmOr4AAAAQUlEQVQI12P4/4D7P8N/B0Yo8XsC23+GZw+4pzM4TmBzYsAGgBKODNcecM9m+D+B7T3DPwfG/Qz/G5iABlzg+g8ANzMax/3kkQoAAAAASUVORK5CYII="); }
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
.codeEditor .lineCount {
	position: absolute;
	top: 34px;
	left: 0;
	bottom: 22px;
	width: 46px;
	text-align: right;
	border-right: 2px solid #999;
	overflow: hidden;
}

.codeEditor .lineCount i {
	display: block;
	padding-right: 2px;
	font-weight: normal;
}	

.codeEditor .lineCount b {
	display: block;
	padding-right: 2px;
	font-weight: normal;
}

.codeEditor .lineCount b.current {
	background: #ccc;
}

.codeEditor .container {
	position: absolute;
	right: 4px;
	top: 34px;
	bottom: 22px;







|







|








|







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
.codeEditor .lineCount {
	position: absolute;
	top: 34px;
	left: 0;
	bottom: 22px;
	width: 46px;
	text-align: right;
	border-right: 2px solid var(--gLightBorderColor);
	overflow: hidden;
}

.codeEditor .lineCount i {
	display: block;
	padding-right: 2px;
	font-weight: normal;
}

.codeEditor .lineCount b {
	display: block;
	padding-right: 2px;
	font-weight: normal;
}

.codeEditor .lineCount b.current {
	background: var(--gLightBorderColor);
}

.codeEditor .container {
	position: absolute;
	right: 4px;
	top: 34px;
	bottom: 22px;

Modified src/www/admin/static/scripts/color_helper.js from [65ca233b3d] to [e68352a65f].

1
2
3
4
5
6
7
8
9






10
11
12
13
14
15
16
(function () {
	if (!document.documentElement.style.setProperty 
		|| !window.CSS || !window.CSS.supports
		|| !window.CSS.supports('--var', 0))
	{
		return;
	}

	var logo_limit_x = 170;







	function colorToRGB(color, type)
	{
		// Conversion vers décimal RGB
		return color.replace(/^#/, '').match(/.{1,2}/g).map(function (el) {
			// On limite la luminosité comme ça, c'est pas parfait mais ça marche
			return Math.min(parseInt(el, 16), type == 'gMainColor' ? 180 : 220);








|
>
>
>
>
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(function () {
	if (!document.documentElement.style.setProperty 
		|| !window.CSS || !window.CSS.supports
		|| !window.CSS.supports('--var', 0))
	{
		return;
	}

	const logo_limit_x = 170;
	const bg_color = getVariable('gBgColor').split(',').map(e => parseInt(e, 10)) || [255, 255, 255];
	const text_color = getVariable('gTextColor').split(',').map(e => parseInt(e, 10)) || [0, 0, 0];

	function getVariable(var_name) {
		return getComputedStyle(document.documentElement).getPropertyValue('--' + var_name);
	}

	function colorToRGB(color, type)
	{
		// Conversion vers décimal RGB
		return color.replace(/^#/, '').match(/.{1,2}/g).map(function (el) {
			// On limite la luminosité comme ça, c'est pas parfait mais ça marche
			return Math.min(parseInt(el, 16), type == 'gMainColor' ? 180 : 220);
24
25
26
27
28
29
30
31

32
33
34
35
36
37
38
39







40
41
42
43
44
45
46
		}).join('');
	}

	function changeColor(element, color)
	{
		let new_color = colorToRGB(color, element);

		let text_color = element == 'gMainColor' ? [255, 255, 255] : [0, 0, 0];

		let change = element == 'gMainColor' ? -5 : 5;

		while (!checkContrast(new_color, text_color)) {
			new_color[0] += change;
			new_color[1] += change;
			new_color[2] += change;
		}








		// Mise à jour variable CSS
		document.documentElement.style.setProperty('--' + element, new_color.join(','));

		applyColors();
		return new_color.join(',');
	}








|
>
|

|





>
>
>
>
>
>
>







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
		}).join('');
	}

	function changeColor(element, color)
	{
		let new_color = colorToRGB(color, element);

		let contrast_color = element == 'gMainColor' ? bg_color : text_color;
		let sum = contrast_color.reduce((pv, cv) => pv + cv, 0);
		let change = sum < (127*3) ? 5 : -5;

		while (!checkContrast(new_color, contrast_color)) {
			new_color[0] += change;
			new_color[1] += change;
			new_color[2] += change;
		}

		for (i in new_color) {
			new_color[i] = Math.max(new_color[i], 0);
			new_color[i] = Math.min(new_color[i], 255);
		}

		console.log(new_color, contrast_color, change);

		// Mise à jour variable CSS
		document.documentElement.style.setProperty('--' + element, new_color.join(','));

		applyColors();
		return new_color.join(',');
	}

158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
			for(var y = 0; y < imgData.height; y++) {
				for(var x = 0; x < imgData.width; x++) {
					var avg = (data[i] * 0.3 + data[i+1] * 0.59 + data[i+2] * 0.11);
					var b = avg < 127 && (data[i+3] > 127);
					data[i] = b ? avg : 255; // red
					data[i+1] = b ? avg : 255; // green
					data[i+2] = b ? avg : 255; // blue
					data[i+3] = b ? (x > 170 ? 50 : 150) : 0;
					i += 4;
				}
			}

			ctx.putImageData(imgData, 0, 0);

			var i = canvas.toDataURL('image/png');







|







172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
			for(var y = 0; y < imgData.height; y++) {
				for(var x = 0; x < imgData.width; x++) {
					var avg = (data[i] * 0.3 + data[i+1] * 0.59 + data[i+2] * 0.11);
					var b = avg < 127 && (data[i+3] > 127);
					data[i] = b ? avg : 255; // red
					data[i+1] = b ? avg : 255; // green
					data[i+2] = b ? avg : 255; // blue
					data[i+3] = b ? (x >> logo_limit_x ? 50 : 150) : 0;
					i += 4;
				}
			}

			ctx.putImageData(imgData, 0, 0);

			var i = canvas.toDataURL('image/png');

Modified src/www/admin/static/scripts/datepicker2.js from [6f612d2aa2] to [317a9279e3].

14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
			this.button = button;
			this.input = input;
			this.date = null;

			Object.assign(this, {
				format: 0, // 0 = Y-m-d, 1 = d/m/Y
				lang: 'fr',
				class: 'datepicker',
				onchange: null
			}, config);

			var c = document.createElement('dialog');
			c.className = this.class;
			this.container = button.parentNode.insertBefore(c, button.nextSibling);

			button.onclick = () => { this.container.hasAttribute('open') ? this.close() : this.open() };







|
<







14
15
16
17
18
19
20
21

22
23
24
25
26
27
28
			this.button = button;
			this.input = input;
			this.date = null;

			Object.assign(this, {
				format: 0, // 0 = Y-m-d, 1 = d/m/Y
				lang: 'fr',
				class: 'datepicker'

			}, config);

			var c = document.createElement('dialog');
			c.className = this.class;
			this.container = button.parentNode.insertBefore(c, button.nextSibling);

			button.onclick = () => { this.container.hasAttribute('open') ? this.close() : this.open() };
183
184
185
186
187
188
189

190
191
192

193
194
195
196
197
198
199

			if (this.input) {
				this.input.value = v;
			}

			this.close();


			if (this.onchange) {
				this.onchange(v, this);
			}

		}

		focus()
		{
			this.container.querySelectorAll('tbody td').forEach((cell) => {
				var v = parseInt(cell.innerHTML, 10);








>
|
|
<
>







182
183
184
185
186
187
188
189
190
191

192
193
194
195
196
197
198
199

			if (this.input) {
				this.input.value = v;
			}

			this.close();

			event = document.createEvent('HTMLEvents');
			event.initEvent('change', true, true);
			event.eventName = 'change';

			this.input.dispatchEvent(event);
		}

		focus()
		{
			this.container.querySelectorAll('tbody td').forEach((cell) => {
				var v = parseInt(cell.innerHTML, 10);

Modified src/www/admin/static/scripts/file_input.js from [c1bd753cc3] to [4e17f68bb7].

33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
			if (total >= max_size - 1000) {
				alert("Les fichiers sélectionnés dépassent la taille maximale autorisée. Merci de choisir moins de fichiers.");
				e.preventDefault();
				e.stopPropagation();
				return false;
			}

			container.classList.add('uploading');
		};

		const updateLabel = () => {
			let l;

			if (dt.files.length == 0) {
				l = label_unselected;







|







33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
			if (total >= max_size - 1000) {
				alert("Les fichiers sélectionnés dépassent la taille maximale autorisée. Merci de choisir moins de fichiers.");
				e.preventDefault();
				e.stopPropagation();
				return false;
			}

			input.form.firstElementChild.classList.add('progressing');
		};

		const updateLabel = () => {
			let l;

			if (dt.files.length == 0) {
				l = label_unselected;

Added src/www/admin/static/scripts/lib/unzipit.min.js version [8729f4e48a].





>
>
1
2
/* unzipit@1.3.6, license MIT */
!function(){function e(e){return e.arrayBuffer?e.arrayBuffer():new Promise(((t,r)=>{const n=new FileReader;n.addEventListener("loadend",(()=>{t(n.result)})),n.addEventListener("error",r),n.readAsArrayBuffer(e)}))}function t(e){return"undefined"!=typeof SharedArrayBuffer&&e instanceof SharedArrayBuffer}"undefined"!=typeof process&&process.versions&&void 0!==process.versions.node&&process.versions.electron;class r{constructor(e){this.blob=e}async getLength(){return this.blob.size}async read(t,r){const n=this.blob.slice(t,t+r),i=await e(n);return new Uint8Array(i)}async sliceAsBlob(e,t,r=""){return this.blob.slice(e,e+t,r)}}function n(e,t){for(var r,n,i,o,a=e.length,f=s.bl_count,c=0;c<=t;c++)f[c]=0;for(c=1;c<a;c+=2)f[e[c]]++;var d=s.next_code;for(r=0,f[0]=0,n=1;n<=t;n++)r=r+f[n-1]<<1,d[n]=r;for(i=0;i<a;i+=2)0!=(o=e[i+1])&&(e[i]=d[o],d[o]++)}function i(e,t,r){for(var n=e.length,i=s.rev15,o=0;o<n;o+=2)if(0!=e[o+1])for(var a=o>>1,f=e[o+1],c=a<<4|f,d=t-f,l=e[o]<<d,u=l+(1<<d);l!=u;){r[i[l]>>>15-t]=c,l++}}function o(e,t){for(var r=s.rev15,n=15-t,i=0;i<e.length;i+=2){var o=e[i]<<t-e[i+1];e[i]=r[o]>>>n}}const s=(a=Uint16Array,f=Uint32Array,{next_code:new a(16),bl_count:new a(16),ordr:[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],of0:[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,999,999,999],exb:[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0],ldef:new a(32),df0:[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,65535,65535],dxb:[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0],ddef:new f(32),flmap:new a(512),fltree:[],fdmap:new a(32),fdtree:[],lmap:new a(32768),ltree:[],ttree:[],dmap:new a(32768),dtree:[],imap:new a(512),itree:[],rev15:new a(32768),lhst:new f(286),dhst:new f(30),ihst:new f(19),lits:new f(15e3),strt:new a(65536),prev:new a(32768)});var a,f;!function(){for(var e=0;e<32768;e++){var t=e;t=(4278255360&(t=(4042322160&(t=(3435973836&(t=(2863311530&t)>>>1|(1431655765&t)<<1))>>>2|(858993459&t)<<2))>>>4|(252645135&t)<<4))>>>8|(16711935&t)<<8,s.rev15[e]=(t>>>16|t<<16)>>>17}function r(e,t,r){for(;0!=t--;)e.push(0,r)}for(e=0;e<32;e++)s.ldef[e]=s.of0[e]<<3|s.exb[e],s.ddef[e]=s.df0[e]<<4|s.dxb[e];r(s.fltree,144,8),r(s.fltree,112,9),r(s.fltree,24,7),r(s.fltree,8,8),n(s.fltree,9),i(s.fltree,9,s.flmap),o(s.fltree,9),r(s.fdtree,32,5),n(s.fdtree,5),i(s.fdtree,5,s.fdmap),o(s.fdtree,5),r(s.itree,19,0),r(s.ltree,286,0),r(s.dtree,30,0),r(s.ttree,320,0)}();const c={table:function(){for(var e=new Uint32Array(256),t=0;t<256;t++){for(var r=t,n=0;n<8;n++)1&r?r=3988292384^r>>>1:r>>>=1;e[t]=r}return e}(),update:function(e,t,r,n){for(var i=0;i<n;i++)e=c.table[255&(e^t[r+i])]^e>>>8;return e},crc:function(e,t,r){return 4294967295^c.update(4294967295,e,t,r)}};class d{constructor(e,t){this._reader=e,this._rawEntry=t,this.name=t.name,this.nameBytes=t.nameBytes,this.size=t.uncompressedSize,this.compressedSize=t.compressedSize,this.comment=t.comment,this.commentBytes=t.commentBytes,this.compressionMethod=t.compressionMethod,this.lastModDate=null,this.isDirectory=0===t.uncompressedSize&&t.name.endsWith("/"),this.encrypted=!!(1&t.generalPurposeBitFlag)}async blob(e="application/octet-stream"){return await async function(e,r,n){const{decompress:i,fileDataStart:o}=await async function(e,t){if(1&t.generalPurposeBitFlag)throw new Error("encrypted entries not supported");const r=await l(e,t.relativeOffsetOfLocalHeader,30),n=await e.getLength(),i=p(r,0);if(67324752!==i)throw new Error(`invalid local file header signature: 0x${i.toString(16)}`);const o=m(r,26),s=m(r,28),a=t.relativeOffsetOfLocalHeader+r.length+o+s;let f;if(0===t.compressionMethod)f=!1;else{if(8!==t.compressionMethod)throw new Error(`unsupported compression method: ${t.compressionMethod}`);f=!0}const c=a,d=c+t.compressedSize;if(0!==t.compressedSize&&d>n)throw new Error(`file data overflows file bounds: ${c} + ${t.compressedSize} > ${n}`);return{decompress:f,fileDataStart:c}}(e,r),s=await async function(e,t,r,n){if(e.sliceAsBlob)return await e.sliceAsBlob(t,r,n);return await e.read(t,r)}(e,o,r.compressedSize,n);if(a=s,"undefined"!=typeof Blob&&a instanceof Blob)return s;var a;return new Blob([t(s.buffer)?new Uint8Array(s):s],{type:n})}(this._reader,this._rawEntry,e)}async arrayBuffer(){return await readEntryDataAsArrayBuffer(this._reader,this._rawEntry)}async text(){const e=await this.arrayBuffer();return y(new Uint8Array(e))}}async function l(e,t,r){return await e.read(t,r)}const u={unsigned:()=>0};function m(e,t){return e[t]+256*e[t+1]}function p(e,t){return e[t]+256*e[t+1]+65536*e[t+2]+16777216*e[t+3]}function w(e,t){return p(e,t)+4294967296*p(e,t+4)}const h=new TextDecoder;function y(e,r){return t(e.buffer)&&(e=new Uint8Array(e)),h.decode(e)}async function g(e,t,r,n){const i=t-20,o=await l(e,i,20);if(117853008!==p(o,0))throw new Error("invalid zip64 end of central directory locator signature");const s=w(o,8),a=await l(e,s,56);if(101075792!==p(a,0))throw new Error("invalid zip64 end of central directory record signature");const f=w(a,32),c=w(a,40);return b(e,w(a,48),c,f,r,n)}async function b(e,t,r,n,i,o){let s=0;const a=await l(e,t,r),f=[];for(let e=0;e<n;++e){const e=a.subarray(s,s+46),t=p(e,0);if(33639248!==t)throw new Error(`invalid central directory file header signature: 0x${t.toString(16)}`);const r={versionMadeBy:m(e,4),versionNeededToExtract:m(e,6),generalPurposeBitFlag:m(e,8),compressionMethod:m(e,10),lastModFileTime:m(e,12),lastModFileDate:m(e,14),crc32:p(e,16),compressedSize:p(e,20),uncompressedSize:p(e,24),fileNameLength:m(e,28),extraFieldLength:m(e,30),fileCommentLength:m(e,32),internalFileAttributes:m(e,36),externalFileAttributes:p(e,38),relativeOffsetOfLocalHeader:p(e,42)};if(64&r.generalPurposeBitFlag)throw new Error("strong encryption is not supported");s+=46;const n=a.subarray(s,s+r.fileNameLength+r.extraFieldLength+r.fileCommentLength);r.nameBytes=n.slice(0,r.fileNameLength),r.name=y(r.nameBytes);const i=r.fileNameLength+r.extraFieldLength,o=n.slice(r.fileNameLength,i);r.extraFields=[];let c=0;for(;c<o.length-3;){const e=m(o,c+0),t=c+4,n=t+m(o,c+2);if(n>o.length)throw new Error("extra field length exceeds extra field buffer size");r.extraFields.push({id:e,data:o.slice(t,n)}),c=n}if(r.commentBytes=n.slice(i,i+r.fileCommentLength),r.comment=y(r.commentBytes),s+=n.length,4294967295===r.uncompressedSize||4294967295===r.compressedSize||4294967295===r.relativeOffsetOfLocalHeader){const e=r.extraFields.find((e=>1===e.id));if(!e)return new Error("expected zip64 extended information extra field");const t=e.data;let n=0;if(4294967295===r.uncompressedSize){if(n+8>t.length)throw new Error("zip64 extended information extra field does not include uncompressed size");r.uncompressedSize=w(t,n),n+=8}if(4294967295===r.compressedSize){if(n+8>t.length)throw new Error("zip64 extended information extra field does not include compressed size");r.compressedSize=w(t,n),n+=8}if(4294967295===r.relativeOffsetOfLocalHeader){if(n+8>t.length)throw new Error("zip64 extended information extra field does not include relative header offset");r.relativeOffsetOfLocalHeader=w(t,n),n+=8}}const d=r.extraFields.find((e=>28789===e.id&&e.data.length>=6&&1===e.data[0]&&p(e.data,1)),u.unsigned(r.nameBytes));if(d&&(r.fileName=y(d.data.slice(5))),0===r.compressionMethod){let e=r.uncompressedSize;if(0!=(1&r.generalPurposeBitFlag)&&(e+=12),r.compressedSize!==e)throw new Error(`compressed size mismatch for stored file: ${r.compressedSize} != ${e}`)}f.push(r)}return{zip:{comment:i,commentBytes:o},entries:f.map((t=>new d(e,t)))}}async function v(e){let t;if(!("undefined"!=typeof Blob&&e instanceof Blob))throw new Error("unsupported source type");t=new r(e);const n=await t.getLength();if(n>Number.MAX_SAFE_INTEGER)throw new Error(`file too large. size: ${n}. Only file sizes up 4503599627370496 bytes are supported`);return await async function(e,t){const r=Math.min(65557,t),n=t-r,i=await l(e,n,r);for(let t=r-22;t>=0;--t){if(101010256!==p(i,t))continue;const r=new Uint8Array(i.buffer,i.byteOffset+t,i.byteLength-t),o=m(r,4);if(0!==o)throw new Error(`multi-volume zip files are not supported. This is volume: ${o}`);const s=m(r,10),a=p(r,12),f=p(r,16),c=m(r,20),d=r.length-22;if(c!==d)throw new Error(`invalid comment length. expected: ${d}, actual: ${c}`);const l=new Uint8Array(r.buffer,r.byteOffset+22,c),u=y(l);return 65535===s||4294967295===f?await g(e,n+t,u,l):await b(e,f,a,s,u,l)}throw new Error("could not find end of central directory. maybe not zip file")}(t,n)}window.unzipit=async function(e){const{zip:t,entries:r}=await v(e);return{zip:t,entries:Object.fromEntries(r.map((e=>[e.name,e])))}}}();

Added src/www/admin/static/scripts/unzip_restore.js version [82f3020360].









































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
(function () {
	let form = document.getElementById('restoreDocuments');
	form.style.display = 'block';

	const MAX_UNCOMPRESSED_SIZE = 1024*1024*1024*10; // 10 GB
	const MAX_FILE_SIZE = form.querySelector('input[name="MAX_FILE_SIZE"]').value - 250;

	let status = document.querySelector('#' + form.id + ' .progress-status');

	let input = document.getElementById('f_file');

	let failed_entries = [];
	let fd = new FormData(form);
	fd.delete('file');
	fd.append('restore', 1);

	form.addEventListener('submit', processZip);

	async function processZip(e) {
		form.firstElementChild.classList.add('progressing');
		form.firstElementChild.disabled = true;
		e.preventDefault();

		if (!input.files.length) {
			alert('Aucun fichier sélectionné');
			return false;
		}

		if (!input.files[0].name.match(/\.zip$/i)) {
			alert('Le fichier sélectionné n\'est pas un fichier ZIP.');
			return false;
		}

		try {
			var {entries} = await unzipit(input.files[0]);
		}
		catch (error) {
			console.log(error);
			alert(error);
			location.href = location.href;
			return;
		}

		let total_size = 0;

		// print all entries and their sizes
		for (const [name, entry] of Object.entries(entries)) {
			total_size += entry.size;
		}

		if (total_size > MAX_UNCOMPRESSED_SIZE) {
			alert('Archive ZIP trop grosse !');
			return;
		}

		for (const [name, entry] of Object.entries(entries)) {
			// Skip directories
			if (entry.isDirectory) {
				continue;
			}

			if (entry.size > MAX_FILE_SIZE) {
				// Skip files that are too large
				failed_entries.push(name);
				continue;
			}

			if (entry.size == 0) {
				// Skip empty files
				continue;
			}

			try {
				const blob = await entry.blob();
				const r = await upload(name, blob, entry.compressionMethod == 8 ? true : false);
			}
			catch (error) {
				console.log(error);
				alert(error);
				location.href = location.href;
				return;
			}
		}

		location.href = form.action + '?ok&failed=' + failed_entries.length;
		return false;
	}

	async function upload(name, blob, compressed) {
		let dt = new DataTransfer();
		let file_name = name.replace(/^.*\/([^\/]+)$/, '$1');

		let f = new File([blob], file_name);

		status.innerHTML = name;

		dt.items.add(f);

		fd.delete('file1');
		fd.delete('target');
		fd.delete('compressed');
		fd.append('target', name);
		fd.append('compressed', compressed ? 1 : 0);
		fd.append('file1', dt.files[0], file_name);

		const response = await fetch(form.action, {
			method: 'POST',
			body: fd
		});

		if (!response.ok) {
			const message = `Erreur du serveur : ${response.status}`;
			throw new Error(message);
		}

		const body = await response.text();

		if (body.substr(0, 1) != '{') {
			console.log(body);
			throw "Réponse invalide du serveur";
		}

		let result = JSON.parse(body);

		if (result.error) {
			console.log(body);
			throw result.error;
		}

		return true;
	}
}());

Modified src/www/admin/static/scripts/wiki_editor.css from [531ffd0eea] to [57a2c80c94].

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
.textEditor {
    border-radius: .5em;
    background: #ccc;
    padding: 1%;
    overflow: hidden;
    position: relative;
}

.textEditor textarea {
    border: none;
    margin: 0;
    background: #eee;
    border-radius: .5em;
    height: 96%;
    width: 98%;
}

nav.te {
    margin-bottom: .5em;
    height: 30px;
}

nav.te button {
    text-decoration: none;
    cursor: pointer;
    background: #eee no-repeat center center;
    display: inline-block;
    vertical-align: bottom;
    transition: all .2s;
    border: 1px solid #999;
    box-shadow: 2px 2px 5px #999;
}

nav.te .bold, nav.te .italic, nav.te .title, nav.te .link {
    font-family: Georgia, "Times New Roman", serif;
}

nav.te .bold { font-weight: bold; }
nav.te .italic { font-style: italic; }
nav.te .link { text-decoration: underline; color: blue; }

nav.te .fullscreen {
    text-indent: -70em;
    width: 32px;
    overflow: hidden;
}



|








|













|



|
|








|







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
.textEditor {
    border-radius: .5em;
    background: var(--gLightBorderColor);
    padding: 1%;
    overflow: hidden;
    position: relative;
}

.textEditor textarea {
    border: none;
    margin: 0;
    background: var(--gLightBackgroundColor);
    border-radius: .5em;
    height: 96%;
    width: 98%;
}

nav.te {
    margin-bottom: .5em;
    height: 30px;
}

nav.te button {
    text-decoration: none;
    cursor: pointer;
    background: var(--gLightBackgroundColor) no-repeat center center;
    display: inline-block;
    vertical-align: bottom;
    transition: all .2s;
    border: 1px solid var(--gBorderColor);
    box-shadow: 2px 2px 5px var(--gBorderColor);
}

nav.te .bold, nav.te .italic, nav.te .title, nav.te .link {
    font-family: Georgia, "Times New Roman", serif;
}

nav.te .bold { font-weight: bold; }
nav.te .italic { font-style: italic; }
nav.te .link { text-decoration: underline; color: var(--gLinkColor); }

nav.te .fullscreen {
    text-indent: -70em;
    width: 32px;
    overflow: hidden;
}

115
116
117
118
119
120
121
122
123
124
125
126
127
128
129

.textEditor.iframe nav button.close, .textEditor.iframe nav button.reload {
    display: inline-block;
}

.textEditor iframe {
    border: none;
    background: #eee;
    border-radius: .5em;
    padding: 1%;
    width: 98%;
}

.textEditor iframe.hidden {
    visibility: hidden;







|







115
116
117
118
119
120
121
122
123
124
125
126
127
128
129

.textEditor.iframe nav button.close, .textEditor.iframe nav button.reload {
    display: inline-block;
}

.textEditor iframe {
    border: none;
    background: rgb(var(--gBgColor));
    border-radius: .5em;
    padding: 1%;
    width: 98%;
}

.textEditor iframe.hidden {
    visibility: hidden;
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
191
192
    background: rgba(0, 0, 0, 0.75);
}

form#insertImage fieldset {
    box-shadow: 5px 5px 10px #000;
    margin: 1em;
    padding: 1em;
    background: #ddd;
    border-radius: .5em;
    text-align: center;
}

form#insertImage dt {
    margin: .2em 0;
}

form#insertImage .align input {
    font-size: 1.2em;
    line-height: 32px;
    background: #fff;
    background-position: 5px center;
    background-repeat: no-repeat;
    padding: 5px;
    padding-left: 42px;
    border: 1px solid #999;
    border-radius: 5px;
    margin: .5em;
}

form#insertImage .align input[name=""] {
    background-image: url("../pics/img_flow.png");
}







|











<




<







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
    background: rgba(0, 0, 0, 0.75);
}

form#insertImage fieldset {
    box-shadow: 5px 5px 10px #000;
    margin: 1em;
    padding: 1em;
    background: var(--gLightBackgroundColor);
    border-radius: .5em;
    text-align: center;
}

form#insertImage dt {
    margin: .2em 0;
}

form#insertImage .align input {
    font-size: 1.2em;
    line-height: 32px;

    background-position: 5px center;
    background-repeat: no-repeat;
    padding: 5px;
    padding-left: 42px;

    border-radius: 5px;
    margin: .5em;
}

form#insertImage .align input[name=""] {
    background-image: url("../pics/img_flow.png");
}
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229

form#insertImage .align input[name="center"] {
    background-image: url("../pics/img_center.png");
}

form#insertImage .cancel input {
    font-size: 0.8em;
    background: transparent;
    padding: 5px;
    border: 1px solid #ccc;
    border-radius: 5px;
    margin: .5em;
    color: #666;
    cursor: pointer;
}

form#insertImage .align input:hover, form#insertImage .cancel input:hover  {
    cursor: pointer;
    background-color: #eee;
    color: darkred;
}

#confirm_saved {
    position: absolute;
    top: .5em;
    right: 10em;
    text-align: center;
    transition: all .5s, opacity 2s;
}







<
<
<
<

<
<
<
|
<
<
<
<









199
200
201
202
203
204
205




206



207




208
209
210
211
212
213
214
215
216

form#insertImage .align input[name="center"] {
    background-image: url("../pics/img_center.png");
}

form#insertImage .cancel input {
    font-size: 0.8em;




    margin: .5em;



    opacity: 0.8;




}

#confirm_saved {
    position: absolute;
    top: .5em;
    right: 10em;
    text-align: center;
    transition: all .5s, opacity 2s;
}

Modified src/www/admin/static/scripts/wiki_fichiers.js from [6d13b317ce] to [88a6ff9b9e].

21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

            f.querySelector('dd.image').innerHTML = '';
            var img = document.createElement('img');
            img.src = file.thumb;
            img.alt = '';
            f.querySelector('dd.image').appendChild(img);

            f.querySelector('dd.cancel input[type=reset]').onclick = function() {
                f.style.display = 'none';

                if (from_upload)
                {
                    location.href = location.href;
                }
            };







|







21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

            f.querySelector('dd.image').innerHTML = '';
            var img = document.createElement('img');
            img.src = file.thumb;
            img.alt = '';
            f.querySelector('dd.image').appendChild(img);

            f.querySelector('dd.cancel [type=reset]').onclick = function() {
                f.style.display = 'none';

                if (from_upload)
                {
                    location.href = location.href;
                }
            };

Modified src/www/admin/static/styles/01-layout.css from [9046a79977] to [9c3400eb48].

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
/*
    marron : #9c4f15 rgb(156, 79, 21)
    orange : #d98628 rgb(217, 134, 40)
*/


:root {
    --gBgColor: 255, 255, 255;







    --gMainColor: 156, 79, 21;
    --gSecondColor: 217, 134, 40;
    --gBgImage: url("../gdin_bg.png");
}












html {
    width: 100%;
    height: 100%;
}

body {
    font-size: 100%;
    color: #000;
    font-family: "Trebuchet MS", Arial, Helvetica, Sans-serif;
    padding-bottom: 1em;
    background: rgb(var(--gBgColor)) var(--gBgImage) no-repeat 0px 0px fixed;
}

main {
    margin: 0px 1em 1em 180px;
    position: relative;
}

main img {
    max-width: 100%;
}









.header h1 {
    color: rgb(var(--gMainColor));
    margin-left: 180px;
    margin-bottom: 0.4em;
}






>


>
>
>
>
>
>
>




>
>
>
>
>
>
>
>
>
>
>








|














>
>
|
>
>
>
>







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
59
60
61
62
63
64
65
66
67
/*
    marron : #9c4f15 rgb(156, 79, 21)
    orange : #d98628 rgb(217, 134, 40)
*/

/* Light colors */
:root {
    --gBgColor: 255, 255, 255;
    --gTextColor: 0, 0, 0;
    --gBorderColor: #666;
    --gLightBorderColor: #ccc;
    --gLightBackgroundColor: #eee;
    --gLinkColor: blue;
    --gHoverLinkColor: 127, 0, 0;

    --gMainColor: 156, 79, 21;
    --gSecondColor: 217, 134, 40;
    --gBgImage: url("../gdin_bg.png");
}

/* Dark colors */
html.dark {
    --gBgColor: 30, 30, 30;
    --gTextColor: 225, 225, 225;
    --gBorderColor: #999;
    --gLightBorderColor: #333;
    --gLightBackgroundColor: #222;
    --gLinkColor: #99f;
    --gHoverLinkColor: 250, 127, 127;
}

html {
    width: 100%;
    height: 100%;
}

body {
    font-size: 100%;
    color: rgb(var(--gTextColor));
    font-family: "Trebuchet MS", Arial, Helvetica, Sans-serif;
    padding-bottom: 1em;
    background: rgb(var(--gBgColor)) var(--gBgImage) no-repeat 0px 0px fixed;
}

main {
    margin: 0px 1em 1em 180px;
    position: relative;
}

main img {
    max-width: 100%;
}

a {
    color: var(--gLinkColor);
}

a:hover {
    color: rgb(var(--gHoverLinkColor));
}

.header h1 {
    color: rgb(var(--gMainColor));
    margin-left: 180px;
    margin-bottom: 0.4em;
}

66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
    top: 0;
    bottom: 0;
    background: rgb(var(--gMainColor)) var(--gBgImage) no-repeat 0px 0px;
}

.header .menu::-webkit-scrollbar {
    width: 8px;
    background: rgba(255, 255, 255, 0.25);
    box-shadow: inset 0px 0px 10px #666;
}

.header .menu::-webkit-scrollbar-thumb {
    background: rgba(255, 255, 255, 0.5);
    border-radius: 10px;
}

.header .menu i {
    font-style: normal;
}








|




|







91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
    top: 0;
    bottom: 0;
    background: rgb(var(--gMainColor)) var(--gBgImage) no-repeat 0px 0px;
}

.header .menu::-webkit-scrollbar {
    width: 8px;
    background: rgba(var(--gBgColor), 0.25);
    box-shadow: inset 0px 0px 10px #666;
}

.header .menu::-webkit-scrollbar-thumb {
    background: rgba(var(--gBgColor), 0.5);
    border-radius: 10px;
}

.header .menu i {
    font-style: normal;
}

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

.header .menu a b {
    float: right;
    text-decoration: none;
    margin-top: -.2em;
    font-size: 20pt;
    color: rgba(255, 255, 255, .5);
}

.header .menu li.current > a b {
    color: rgba(var(--gSecondColor), 0.5);
}









|







135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
}

.header .menu a b {
    float: right;
    text-decoration: none;
    margin-top: -.2em;
    font-size: 20pt;
    color: rgba(var(--gBgColor), .5);
}

.header .menu li.current > a b {
    color: rgba(var(--gSecondColor), 0.5);
}


Modified src/www/admin/static/styles/02-common.css from [72a91c6a02] to [e0f654a9f7].

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

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
.block table {
    margin: 1rem 0;
}

.block table th, .block table td {
    vertical-align: top;
    padding: .2rem .4rem;
    border: 1px solid #666;
}

.alert.block, .error.block, .confirm.block, .help.block {
    border: 1px solid #ccc;
    padding: .5em;
    margin: .5em 0;
    border-radius: .3em;
    padding-left: 3em;
    position: relative;
    clear: both;

}

.alert.block {
    border-color: #cc0;
    background-color: #ffc;
}

.error.block {
    border-color: #c00;
    background-color: #fcc;
}

.confirm.block {
    border-color: #0c0;
    background-color: #cfc;
}

.help.block {

    border-color: #999;
    background-color: #eee;
}

.confirm.block::before, .alert.block::before, .error.block::before, .help.block::before {
    font-family: "gicon";
    left: .5em;
    top: .2em;
    position: absolute;
    font-size: 1.5em;
    text-shadow: 2px 2px 5px #666;
}

.confirm.block::before {
    content: "☑";
    color: green;
}

.alert.block::before {
    content: "⚠";
    color: yellow;
}

.error.block::before {
    content: "⚠";
    color: red;
}

.help.block::before {
    content: "❓";
    color: #666;
}

.help {
    color: #666;
}

p.help:not(.block) {
    margin: 1em;
}

.help ul li {







|



|






>


















>
|
|








|



















|



|







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
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
.block table {
    margin: 1rem 0;
}

.block table th, .block table td {
    vertical-align: top;
    padding: .2rem .4rem;
    border: 1px solid var(--gBorderColor);
}

.alert.block, .error.block, .confirm.block, .help.block {
    border: 1px solid var(--gLightBorderColor);
    padding: .5em;
    margin: .5em 0;
    border-radius: .3em;
    padding-left: 3em;
    position: relative;
    clear: both;
    color: #000;
}

.alert.block {
    border-color: #cc0;
    background-color: #ffc;
}

.error.block {
    border-color: #c00;
    background-color: #fcc;
}

.confirm.block {
    border-color: #0c0;
    background-color: #cfc;
}

.help.block {
    color: rgb(var(--gTextColor));
    border-color: var(--gLightBorderColor);
    background-color: var(--gLightBackgroundColor);
}

.confirm.block::before, .alert.block::before, .error.block::before, .help.block::before {
    font-family: "gicon";
    left: .5em;
    top: .2em;
    position: absolute;
    font-size: 1.5em;
    text-shadow: 2px 2px 5px var(--gLightBorderColor);
}

.confirm.block::before {
    content: "☑";
    color: green;
}

.alert.block::before {
    content: "⚠";
    color: yellow;
}

.error.block::before {
    content: "⚠";
    color: red;
}

.help.block::before {
    content: "❓";
    color: var(--gBorderColor);
}

.help {
    color: var(--gBorderColor);
}

p.help:not(.block) {
    margin: 1em;
}

.help ul li {
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
    margin-left: 1.5em;
    list-style: disc;
}

.ruler {
    margin: .5em;
    text-align: center;
    color: #333;
    overflow: hidden;
}

.ruler:before, .ruler:after {
    background-color: #000;
    content: "";
    display: inline-block;
    height: 1px;
    position: relative;
    vertical-align: middle;
    width: 50%;
}







<




|







117
118
119
120
121
122
123

124
125
126
127
128
129
130
131
132
133
134
135
    margin-left: 1.5em;
    list-style: disc;
}

.ruler {
    margin: .5em;
    text-align: center;

    overflow: hidden;
}

.ruler:before, .ruler:after {
    background-color: var(--gLightBorderColor);
    content: "";
    display: inline-block;
    height: 1px;
    position: relative;
    vertical-align: middle;
    width: 50%;
}
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
.num a, a.num {
    text-decoration: none;
    border-radius: .5rem;
    display: inline-block;
    text-align: center;
    padding: 0 .3rem;
    background: rgba(var(--gMainColor), 0.7);
    color: #fff;
    white-space: pre;
}

.permissions b {
    border: 2px solid #999;
    border-radius: 1em;
    color: #000;







|







147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
.num a, a.num {
    text-decoration: none;
    border-radius: .5rem;
    display: inline-block;
    text-align: center;
    padding: 0 .3rem;
    background: rgba(var(--gMainColor), 0.7);
    color: rgb(var(--gBgColor));
    white-space: pre;
}

.permissions b {
    border: 2px solid #999;
    border-radius: 1em;
    color: #000;
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
dl.list dt {
    font-size: 1.2em;
    font-weight: bold;
    margin-top: .8em;
}

dl.list dd.desc {
    color: #666;
}

dl.describe {
    margin-bottom: 1rem;
    display: grid;
    grid-template: auto / 15rem 1fr;
}

dl.describe > dt {
    grid-column: 1;
    margin: .2rem .5rem;
    text-align: right;
    color: #666;
    align-self: start;
}

dl.describe > dd {
    grid-column: 2;
    margin: .2rem .5rem;
    align-self: center;
}

dl.describe ul {
    margin-left: 1.5em;
    list-style-type: disc;
}

dl.cotisation {
    background: rgb(255, 174, 80);
    background: rgba(217, 134, 40, 0.2);
    background: rgba(var(--gSecondColor), 0.2);
    padding: .5em;
    border-radius: .5em;
    margin: 1em;
}

dl.cotisation dt {







|












|















<
<







282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317


318
319
320
321
322
323
324
dl.list dt {
    font-size: 1.2em;
    font-weight: bold;
    margin-top: .8em;
}

dl.list dd.desc {
    color: var(--gLightBorderColor);
}

dl.describe {
    margin-bottom: 1rem;
    display: grid;
    grid-template: auto / 15rem 1fr;
}

dl.describe > dt {
    grid-column: 1;
    margin: .2rem .5rem;
    text-align: right;
    color: var(--gBorderColor);
    align-self: start;
}

dl.describe > dd {
    grid-column: 2;
    margin: .2rem .5rem;
    align-self: center;
}

dl.describe ul {
    margin-left: 1.5em;
    list-style-type: disc;
}

dl.cotisation {


    background: rgba(var(--gSecondColor), 0.2);
    padding: .5em;
    border-radius: .5em;
    margin: 1em;
}

dl.cotisation dt {
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
    float: right;
    margin: .5em;
    background: rgba(var(--gSecondColor), 0.2);
    border-radius: .5em;
    border: 2px solid rgba(var(--gSecondColor), 0.5);
    padding: .5em;
    z-index: 200;
    color: #666;
}

aside.describe dl.describe {
    display: block;
}

aside.describe dl.describe dt {
    text-align: left;
    font-weight: bold;
    color: #000;
}

.hidden {
    display: none;
}

img.qrcode {







|









|







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
    float: right;
    margin: .5em;
    background: rgba(var(--gSecondColor), 0.2);
    border-radius: .5em;
    border: 2px solid rgba(var(--gSecondColor), 0.5);
    padding: .5em;
    z-index: 200;
    color: var(--gBorderColor);
}

aside.describe dl.describe {
    display: block;
}

aside.describe dl.describe dt {
    text-align: left;
    font-weight: bold;
    color: rgb(var(--gTextColor));
}

.hidden {
    display: none;
}

img.qrcode {
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
    content: "↓";
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    /* From .icn-btn */
    cursor: pointer;
    color: #003;
    border: 1px solid rgba(var(--gMainColor), 0.5);
    background-color: rgba(var(--gSecondColor), 0.1);
    user-select: none;
    display: inline-block;
    font-size: inherit;
    border-radius: .2em;
    padding: .2em .4em;
    margin: auto .5em;
    height: 1em;
    white-space: pre;
    transition: color .3s, background-color .3s;
    font-family: "gicon", sans-serif;
    text-shadow: 1px 1px 1px #999;
    font-size: 1.2em;
}

details[open] summary::after {
    content: "↑";
}








<












|







387
388
389
390
391
392
393

394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
    content: "↓";
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    /* From .icn-btn */
    cursor: pointer;

    border: 1px solid rgba(var(--gMainColor), 0.5);
    background-color: rgba(var(--gSecondColor), 0.1);
    user-select: none;
    display: inline-block;
    font-size: inherit;
    border-radius: .2em;
    padding: .2em .4em;
    margin: auto .5em;
    height: 1em;
    white-space: pre;
    transition: color .3s, background-color .3s;
    font-family: "gicon", sans-serif;
    text-shadow: 1px 1px 1px var(--gLightBorderColor);
    font-size: 1.2em;
}

details[open] summary::after {
    content: "↑";
}

438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
    text-align: center;
    padding: .5rem;
    margin: .5rem;
}

.files-list aside small {
    display: block;
    color: #666;
}

nav.breadcrumbs {
    margin: .5em 0;
    color: #999;
}

nav.breadcrumbs a {
    color: #333;
}

nav.breadcrumbs ul, nav.breadcrumbs li {
    list-style-type: none;
    display: inline;
}








|




|



|







436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
    text-align: center;
    padding: .5rem;
    margin: .5rem;
}

.files-list aside small {
    display: block;
    color: var(--gBorderColor);
}

nav.breadcrumbs {
    margin: .5em 0;
    color: var(--gLightBorderColor);
}

nav.breadcrumbs a {
    color: var(--gBorderColor);
}

nav.breadcrumbs ul, nav.breadcrumbs li {
    list-style-type: none;
    display: inline;
}

471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
    float: right;
}

aside.quota {
    background: rgba(var(--gMainColor), 0.1);
    border-radius: .5rem;
    padding: .2rem .5rem;
    color: #000;
    margin: .5em 0;
}

aside.quota i {
    font-style: normal;
}








|







469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
    float: right;
}

aside.quota {
    background: rgba(var(--gMainColor), 0.1);
    border-radius: .5rem;
    padding: .2rem .5rem;
    color: rgb(var(--gTextColor));
    margin: .5em 0;
}

aside.quota i {
    font-style: normal;
}

517
518
519
520
521
522
523
524
525
526
}

.search-results h3 {
    margin: .3em 0;
}

.search-results h3 a {
    color: darkblue;
    font-weight: normal;
}







<


515
516
517
518
519
520
521

522
523
}

.search-results h3 {
    margin: .3em 0;
}

.search-results h3 a {

    font-weight: normal;
}

Modified src/www/admin/static/styles/03-forms.css from [48a1aece0a] to [d01f0ddad6].

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
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
115
116
117
118
/* Forms */
fieldset {
    border: 1px solid #ccc;
    padding: 0.8em 1em 0 1em;
    margin-bottom: 1em;
    padding: 0.5em;
}

fieldset legend {
    padding: 0 0.5em;
    font-weight: bold;
    color: #000;
}

/* Override selector in 06-tables.css */
table tr.clickable:hover, table tr.clickable:nth-child(even):hover {
    cursor: pointer;
    color: #633;
    background: #ffc;
}





table tr.focused {
    color: #633;
    background: #ffc !important;
    box-shadow: 0 0 5px .2rem #990;
}

dl dt label {
    font-weight: bold;
}

fieldset dl dt b {
    color: #900;
    font-size: 0.7em;
    font-weight: normal;
    vertical-align: super;
}

fieldset dl dt i {
    color: #999;
    font-size: 0.7em;
    font-weight: normal;
    vertical-align: super;
}

fieldset dl dd.tip {
    color: #666;
}

fieldset dl dd {
    padding: 0.2em 0.5em 0.2em 1em;
}

fieldset dl dd ol, fieldset dl dd ul {
    margin-left: 1.5em;
}

fieldset dl dl {
    margin: .5em 0 .5em 1.2em;
}

label:hover {
    cursor: pointer;
    border-bottom: 1px dotted #900;
}

input[type=checkbox] + label:hover {
    border: none;
}

/* We can't use :not([type=checkbox]):not([type=radio]) here as it is too specific
and then it's a mess to override the selector after... */
input[type=text], input[type=number], input[type=color],
input[type=date], input[type=datetime-local], input[type=datetime], input[type=time], input[type=week],
input[type=email], input[type=file], input[type=url], input[type=month],
input[type=password], input[type=range], input[type=search], input[type=tel],
textarea, select, .input-list, .file-selector {
    padding: .4rem .6rem;
    font-family: inherit;
    min-width: 20em;
    max-width: 100%;
    border: 1px solid rgb(var(--gMainColor));
    font-size: inherit;
    background: #fff;
    color: #000;
    border-radius: .25rem;
    transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out;
}





input:not(:placeholder-shown):focus:invalid {
    border-color: #f33;
}

input.time {
    text-align: center;
    padding: .2em 0;
}

/* Fake checkbox and radio buttons */
input[type=checkbox], input[type=radio] {
    position: absolute;
    opacity: 0;
}

input[type=checkbox] + label::before, input[type=radio] + label::before {
    display: inline-block;
    width: 1em;
    height: 1em;
    text-align: center;
    transition: color .2s, box-shadow .2s ease-in-out;
    text-shadow: 1px 1px 3px #ccc;
    cursor: pointer;
    font-family: gicon;
    font-size: 1.2rem;
    font-weight: normal;
    color: rgb(var(--gMainColor));
    margin-right: .5em;
    border-radius: .25rem;


|








|








>
>
>
>












|






|





<
<
<
<














<



















|
|




>
>
>
>

|



















|







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
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
115
116
117
118
119
120
121
/* Forms */
fieldset {
    border: 1px solid var(--gLightBorderColor);
    padding: 0.8em 1em 0 1em;
    margin-bottom: 1em;
    padding: 0.5em;
}

fieldset legend {
    padding: 0 0.5em;
    font-weight: bold;
    color: rgb(var(--gTextColor));
}

/* Override selector in 06-tables.css */
table tr.clickable:hover, table tr.clickable:nth-child(even):hover {
    cursor: pointer;
    color: #633;
    background: #ffc;
}

table tr.clickable:hover button, table tr.focused button {
    color: rgb(var(--gHoverLinkColor));
}

table tr.focused {
    color: #633;
    background: #ffc !important;
    box-shadow: 0 0 5px .2rem #990;
}

dl dt label {
    font-weight: bold;
}

fieldset dl dt b {
    color: rgb(var(--gHoverLinkColor));
    font-size: 0.7em;
    font-weight: normal;
    vertical-align: super;
}

fieldset dl dt i {
    color: var(--gLightBorderColor);
    font-size: 0.7em;
    font-weight: normal;
    vertical-align: super;
}





fieldset dl dd {
    padding: 0.2em 0.5em 0.2em 1em;
}

fieldset dl dd ol, fieldset dl dd ul {
    margin-left: 1.5em;
}

fieldset dl dl {
    margin: .5em 0 .5em 1.2em;
}

label:hover {
    cursor: pointer;

}

input[type=checkbox] + label:hover {
    border: none;
}

/* We can't use :not([type=checkbox]):not([type=radio]) here as it is too specific
and then it's a mess to override the selector after... */
input[type=text], input[type=number], input[type=color],
input[type=date], input[type=datetime-local], input[type=datetime], input[type=time], input[type=week],
input[type=email], input[type=file], input[type=url], input[type=month],
input[type=password], input[type=range], input[type=search], input[type=tel],
textarea, select, .input-list, .file-selector {
    padding: .4rem .6rem;
    font-family: inherit;
    min-width: 20em;
    max-width: 100%;
    border: 1px solid rgb(var(--gMainColor));
    font-size: inherit;
    background: rgb(var(--gBgColor));
    color: rgb(var(--gTextColor));
    border-radius: .25rem;
    transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out;
}

textarea.full-width {
    width: calc(100% - 1rem);
}

input:not(:placeholder-shown):focus:invalid {
    border-color: rgb(var(--gHoverLinkColor));
}

input.time {
    text-align: center;
    padding: .2em 0;
}

/* Fake checkbox and radio buttons */
input[type=checkbox], input[type=radio] {
    position: absolute;
    opacity: 0;
}

input[type=checkbox] + label::before, input[type=radio] + label::before {
    display: inline-block;
    width: 1em;
    height: 1em;
    text-align: center;
    transition: color .2s, box-shadow .2s ease-in-out;
    text-shadow: 1px 1px 3px var(--gLightBorderColor);
    cursor: pointer;
    font-family: gicon;
    font-size: 1.2rem;
    font-weight: normal;
    color: rgb(var(--gMainColor));
    margin-right: .5em;
    border-radius: .25rem;
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
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










191
























192










193
194
195
196
197
198
199
200
201
202
203


204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
}

input:hover + label::before {
    color: rgb(var(--gSecondColor));
}

input:checked + label::before {
    text-shadow: 1px 1px 5px #ff9;
}

#queryBuilder input[type=checkbox] {
    position: unset;
    opacity: unset;
}

input:focus, button:focus, select:focus, textarea:focus, input[type=radio]:focus + label::before, input[type=checkbox]:focus + label::before {
    box-shadow: 0 0 5px .2rem rgb(var(--gSecondColor));
    outline: 0;
}

/* buttons */

input[type=submit], input[type=button], button, input[type=file] {
    border-radius: 1em;
    border: none;
    box-shadow: 0px 0px 5px 0 #ccc;
    cursor: pointer;
    border: 2px solid rgba(var(--gMainColor), 0.5);
    background-color: rgba(var(--gSecondColor), 0.1);
    display: inline-block;
    font-size: inherit;
    border-radius: .2em;
    padding: .2em .4em;
    margin: .2em .5em;
    text-decoration: none;
    transition: color .3s, background-color .3s;
    color: #000;
}

a.icn-btn {
    cursor: pointer;
    color: #003;
    border: 1px solid rgba(var(--gMainColor), 0.5);
    background-color: rgba(var(--gSecondColor), 0.1);
    user-select: none;
    display: inline-block;
    font-size: inherit;
    border-radius: .2em;
    padding: .2em .4em;
    margin: .2em .5em;
    white-space: pre;
    transition: color .3s, background-color .3s;
    text-decoration: underline;
}











.submit .main {
























    color: #000;










    font-size: 1.2em;
    border-radius: 1em;
    padding: .5em 1em;
}

.submit .main[data-icon]:before {
    display: none;
}

p.submit .main[data-icon]:after {
    padding: 0 0 0 .5rem;


    color: rgba(var(--gSecondColor));
    font-size: 1.5rem;
    line-height: .2em;
}

.submit .minor {
    font-size: .9em;
}

input[type=submit]:hover, input[type=button]:hover, button:hover, a.icn-btn:hover, input[type=file]:hover,
.radio-btn:hover div, a.num:hover, .num a:hover {
    background-color: rgba(var(--gSecondColor), 0.2);
    color: darkred !important;
    border-color: rgb(var(--gSecondColor));
}

input[type=submit]:active, input[type=button]:active, button:active, input[type=file]:active {
    box-shadow: 0 0 10px .1rem rgb(var(--gSecondColor));
}

input[type=color] {
    cursor: pointer;
}

input.resetButton {
    margin-left: 1em;
}

input[readonly], input.disabled, input[disabled], textarea[disabled], select[disabled] {
    cursor: not-allowed;
    color: #666;
    background-color: #eee;
    border-color: #999;
}

input[disabled]:hover, input[readonly]:hover {
    background-color: unset;
    color: unset;
    border-color: unset;
}

input[disabled] + label {
    color: #666;
}

input[disabled] + label::before {
    color: #999;
    cursor: not-allowed;
}

select, input[size], input[type=color], button, input[type=button], input[type=submit], input[type=number] {
    min-width: 0;
}








|








|








|


|







|




|

|











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





|



|
|
>
>












|

















|
|
|









|



|







139
140
141
142
143
144
145
146
147
148
149
150
151
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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
}

input:hover + label::before {
    color: rgb(var(--gSecondColor));
}

input:checked + label::before {
    text-shadow: 1px 1px 5px rgba(var(--gSecondColor), 0.5);
}

#queryBuilder input[type=checkbox] {
    position: unset;
    opacity: unset;
}

input:focus, button:focus, select:focus, textarea:focus, input[type=radio]:focus + label::before, input[type=checkbox]:focus + label::before {
    box-shadow: 0 0 5px .2rem rgba(var(--gMainColor), 0.5);
    outline: 0;
}

/* buttons */

input[type=submit], input[type=button], button, input[type=file] {
    border-radius: 1em;
    border: none;
    box-shadow: 0px 0px 5px 0 var(--gLightBorderColor);
    cursor: pointer;
    border: 2px solid rgba(var(--gMainColor), 0.5);
    background: rgba(var(--gSecondColor), 0.2);
    display: inline-block;
    font-size: inherit;
    border-radius: .2em;
    padding: .2em .4em;
    margin: .2em .5em;
    text-decoration: none;
    transition: color .3s, background-color .3s;
    color: rgb(var(--gTextColor));
}

a.icn-btn {
    cursor: pointer;
    color: rgb(var(--gTextColor));
    border: 1px solid rgba(var(--gMainColor), 0.5);
    background: rgba(var(--gSecondColor), 0.1);
    user-select: none;
    display: inline-block;
    font-size: inherit;
    border-radius: .2em;
    padding: .2em .4em;
    margin: .2em .5em;
    white-space: pre;
    transition: color .3s, background-color .3s;
    text-decoration: underline;
}

[data-icon]:before, .main[data-icon]:after {
    display: inline-block;
    font-family: "gicon", sans-serif;
    text-shadow: 1px 1px 1px var(--gLightBorderColor);
    padding-right: .5em;
    font-size: 1.2em;
    line-height: .8em;
    vertical-align: middle;
    content: attr(data-icon);
}

[data-icon]:empty:before {
    padding: 0;
}

.icn, .icnl {
    font-family: "gicon", sans-serif;
    font-style: normal;
    font-weight: normal;
    speak: none;
    font-variant: normal;
    text-transform: none;
    position: relative;
}

.actions .icn, .icn.action {
    text-decoration: none;
    border-radius: 1em;
    display: inline-block;
    text-align: center;
    font-size: 1.2em;
    line-height: .8em;
    vertical-align: middle;
    padding: .2em;
    font-family: "gicon", sans-serif;
    color: rgb(var(--gMainColor));
    text-shadow: 1px 1px 1px var(--gBorderColor);
    border: none;
    cursor: pointer;
    position: relative;
    z-index: 200;
}


button.main, .icn-btn.main {
    color: rgb(var(--gTextColor));
    font-size: 1.2em;
    border-radius: 1em;
    padding: .5em 1em;
}

button.main[data-icon]:before, .icn-btn.main:before {
    display: none;
}

button.main[data-icon]:after, .icn-btn[data-icon]:after {
    padding: 0;
    padding-left: .5em;
    padding-right: 0;
    color: rgba(var(--gSecondColor));
    font-size: 1.5rem;
    line-height: .2em;
}

.submit .minor {
    font-size: .9em;
}

input[type=submit]:hover, input[type=button]:hover, button:hover, a.icn-btn:hover, input[type=file]:hover,
.radio-btn:hover div, a.num:hover, .num a:hover {
    background-color: rgba(var(--gSecondColor), 0.2);
    color: rgb(var(--gHoverLinkColor)) !important;
    border-color: rgb(var(--gSecondColor));
}

input[type=submit]:active, input[type=button]:active, button:active, input[type=file]:active {
    box-shadow: 0 0 10px .1rem rgb(var(--gSecondColor));
}

input[type=color] {
    cursor: pointer;
}

input.resetButton {
    margin-left: 1em;
}

input[readonly], input.disabled, input[disabled], textarea[disabled], select[disabled] {
    cursor: not-allowed;
    color: var(--gBorderColor);
    background-color: var(--gLightBackgroundColor);
    border-color: var(--gLightBorderColor);
}

input[disabled]:hover, input[readonly]:hover {
    background-color: unset;
    color: unset;
    border-color: unset;
}

input[disabled] + label {
    color: var(--gBorderColor);
}

input[disabled] + label::before {
    color: var(--gBorderColor);
    cursor: not-allowed;
}

select, input[size], input[type=color], button, input[type=button], input[type=submit], input[type=number] {
    min-width: 0;
}

283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
    display: table-cell;
    border: 1px solid rgba(var(--gSecondColor), 0.5);
    background-color: rgba(var(--gSecondColor), 0.1);
    font-size: inherit;
    border-radius: .2em;
    padding: .2em .4em;
    transition: color .3s, background-color .3s;
    color: #333;
}

form .radio-btn h3 {
    text-decoration: underline;
}

form .radio-btn input {
    margin: 1em;
}

form .radio-btn .help {
    margin: .8em 0 0 0;
    font-size: .8em;
}

form .radio-btn input:checked + label div {
    background-color: rgba(var(--gSecondColor), 0.3);
}

/* Custom list input */







|











|
|







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
    display: table-cell;
    border: 1px solid rgba(var(--gSecondColor), 0.5);
    background-color: rgba(var(--gSecondColor), 0.1);
    font-size: inherit;
    border-radius: .2em;
    padding: .2em .4em;
    transition: color .3s, background-color .3s;
    color: rgb(var(--gTextColor));
}

form .radio-btn h3 {
    text-decoration: underline;
}

form .radio-btn input {
    margin: 1em;
}

form .radio-btn .help {
    margin: 0;
    font-size: .9em;
}

form .radio-btn input:checked + label div {
    background-color: rgba(var(--gSecondColor), 0.3);
}

/* Custom list input */
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
input.money {
    text-align: right;
}

input.money + b {
    padding: .2rem .6rem;
    line-height: 1.5rem;
    color: #999;
}

p.submit {
    margin: 1em;
}


form .checkUncheck {
    float: left;
}

form span.password_check {
    margin-left: 1em;
    padding: .1em .3em;
    border-radius: .5em;

}

form span.password_check.fail { background-color: #f99; }
form span.password_check.weak { background-color: #ff9; }
form span.password_check.medium { background-color: #ccf; }
form span.password_check.ok { background-color: #cfc; }








|















>







387
388
389
390
391
392
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
input.money {
    text-align: right;
}

input.money + b {
    padding: .2rem .6rem;
    line-height: 1.5rem;
    color: var(--gBorderColor);
}

p.submit {
    margin: 1em;
}


form .checkUncheck {
    float: left;
}

form span.password_check {
    margin-left: 1em;
    padding: .1em .3em;
    border-radius: .5em;
    color: #000;
}

form span.password_check.fail { background-color: #f99; }
form span.password_check.weak { background-color: #ff9; }
form span.password_check.medium { background-color: #ccf; }
form span.password_check.ok { background-color: #cfc; }

449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
    border-radius: .5em;
}

.datepicker input {
    font-family: gicon;
}

[data-icon]:before, .main[data-icon]:after {
    display: inline-block;
    font-family: "gicon", sans-serif;
    text-shadow: 1px 1px 1px #ccc;
    padding-right: .5em;
    font-size: 1.2em;
    line-height: .8em;
    vertical-align: middle;
    content: attr(data-icon);
}

[data-icon]:empty:before {
    padding: 0;
}

.icn, .icnl {
    font-family: "gicon", sans-serif;
    font-style: normal;
    font-weight: normal;
    speak: none;
    font-variant: normal;
    text-transform: none;
    position: relative;
}

.actions .icn, .icn.action {
    text-decoration: none;
    border-radius: 1em;
    display: inline-block;
    text-align: center;
    font-size: 1.2em;
    line-height: .8em;
    vertical-align: middle;
    padding: .2em;
    font-family: "gicon", sans-serif;
    color: #9c4f15;
    color: rgb(var(--gMainColor));
    text-shadow: 1px 1px 1px #999;
    border: none;
    cursor: pointer;
    position: relative;
    z-index: 200;
}

fieldset.memberMessage {
    max-width: 30em;
}

fieldset.memberMessage #f_sujet, fieldset.memberMessage #f_message, fieldset.memberMessage select, #queryBuilderForm textarea {
    width: calc(100% - 2em);
}







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







499
500
501
502
503
504
505












































506
507
508
509
510
511
512
    border-radius: .5em;
}

.datepicker input {
    font-family: gicon;
}













































fieldset.memberMessage {
    max-width: 30em;
}

fieldset.memberMessage #f_sujet, fieldset.memberMessage #f_message, fieldset.memberMessage select, #queryBuilderForm textarea {
    width: calc(100% - 2em);
}
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
631
632
    max-height: 5em;
}

.file-selector table.list .num {
    text-align: right;
}







.file-selector.uploading {

    opacity: 0.3;

}

.file-selector.uploading::after {
    display: inline-block;
    content: " ";
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    margin: auto;
    width: 50px;
    height: 50px;
    border: 5px solid #000;
    border-radius: 50%;
    border-top-color: #999;
    animation: spin 1s ease-in-out infinite;





















}

@keyframes spin { to { transform: rotate(360deg); } }

@media screen and (max-width: 1279px) {
    #queryBuilder table tr {
        display: flex;







>
>
>
>
>
>
|
>
|
>


|










|

|

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







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
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
    max-height: 5em;
}

.file-selector table.list .num {
    text-align: right;
}

/**
 * Progress spinner
 */
.progressing {
    position: relative;
}

fieldset.progressing > dl, fieldset.progressing > p {
    opacity: 0.5;
    filter: blur(3px);
}

.progressing::after {
    display: inline-block;
    content: " ";
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    margin: auto;
    width: 50px;
    height: 50px;
    border: 5px solid #666;
    border-radius: 50%;
    border-top-color: #ccc;
    animation: spin 1s ease-in-out infinite;
    filter: none;
}

.progress-status {
    display: none;
}

.progressing .progress-status {
    display: inline-block;
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    margin: auto;
    padding-top: 80px;
    text-align: center;
    width: 100%;
    height: 1em;
    filter: none !important;
    color: #000;
}

@keyframes spin { to { transform: rotate(360deg); } }

@media screen and (max-width: 1279px) {
    #queryBuilder table tr {
        display: flex;

Modified src/www/admin/static/styles/04-dialogs.css from [0eac2544ce] to [e8d4e068e3].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
body.transparent {
    background: none;
}

html.dialog body {
    background: transparent;
    overflow: auto;
}

html.dialog {
    height: auto;
    background: white;
}

html.dialog main {
    background: #fff;
    padding: .5em;
    margin: 0;
}

/** Dialogs pop-ins */
#dialog {
    width: 100%;











|



|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
body.transparent {
    background: none;
}

html.dialog body {
    background: transparent;
    overflow: auto;
}

html.dialog {
    height: auto;
    background: rgb(var(--gBgColor));
}

html.dialog main {
    background: rgb(var(--gBgColor));
    padding: .5em;
    margin: 0;
}

/** Dialogs pop-ins */
#dialog {
    width: 100%;
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
    opacity: 1;
}

#dialog > button.closeBtn {
    background: unset;
    border: unset;
    box-shadow: unset;
    color: #fff;
    font-size: 1.3em;
    display: block;
    width: 90%;
}

#dialog > button.closeBtn:hover {
    color: #999 !important;
}

.loader {
    width: 100%;
    min-height: 32px;
    display: block;
    position: relative;
}

.loader.install {
    margin-top: -40px;
}

.loader b {
    text-shadow: 2px 2px 5px #999;
    background: rgb(255, 255, 255);
    background: rgba(255, 255, 255, 0.5);
    border-radius: .5em;
    font-size: 16px;
    line-height: 16px;
    height: 16px;
    z-index: 9999;
    position: absolute;







|






|















<







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
    opacity: 1;
}

#dialog > button.closeBtn {
    background: unset;
    border: unset;
    box-shadow: unset;
    color: #999;
    font-size: 1.3em;
    display: block;
    width: 90%;
}

#dialog > button.closeBtn:hover {
    color: #fff !important;
}

.loader {
    width: 100%;
    min-height: 32px;
    display: block;
    position: relative;
}

.loader.install {
    margin-top: -40px;
}

.loader b {
    text-shadow: 2px 2px 5px #999;

    background: rgba(255, 255, 255, 0.5);
    border-radius: .5em;
    font-size: 16px;
    line-height: 16px;
    height: 16px;
    z-index: 9999;
    position: absolute;

Modified src/www/admin/static/styles/05-navigation.css from [d3ea311406] to [1424a8a273].

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
}

nav.tabs li a {
    display: inline-block;
    background: rgba(var(--gSecondColor), .5);
    border-radius: .5em .5em 0 0;
    padding: .1em .5em;
    color: #000;
    text-decoration: none;
    transition: background-color .2s, color .2s;
}

nav.tabs .current a {
    background: rgb(var(--gMainColor));
    color: rgb(var(--gBgColor));
}

nav.tabs li a:hover {
    color: rgb(var(--gBgColor));
    background-color: rgb(var(--gMainColor));
    text-decoration: underline;
    border-bottom: none;
}

nav.tabs aside {
    float: right;
    max-width: 50%;
    text-align: right

}







|



















|
>

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
}

nav.tabs li a {
    display: inline-block;
    background: rgba(var(--gSecondColor), .5);
    border-radius: .5em .5em 0 0;
    padding: .1em .5em;
    color: rgb(var(--gTextColor));
    text-decoration: none;
    transition: background-color .2s, color .2s;
}

nav.tabs .current a {
    background: rgb(var(--gMainColor));
    color: rgb(var(--gBgColor));
}

nav.tabs li a:hover {
    color: rgb(var(--gBgColor));
    background-color: rgb(var(--gMainColor));
    text-decoration: underline;
    border-bottom: none;
}

nav.tabs aside {
    float: right;
    max-width: 50%;
    text-align: right;
    clear: right;
}

Modified src/www/admin/static/styles/06-tables.css from [30c2ec2452] to [1bbe7e128c].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
table.list {
    margin-bottom: 1em;
    width: 100%;
}

table.list caption {
    text-align: center;
    font-size: 1.2em;
}

table.list tbody td.desc {
    font-size: .9em;
    color: #666;
}

table.list.auto {
    width: auto;
}

table.list table {












|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
table.list {
    margin-bottom: 1em;
    width: 100%;
}

table.list caption {
    text-align: center;
    font-size: 1.2em;
}

table.list tbody td.desc {
    font-size: .9em;
    color: var(--gBorderColor);
}

table.list.auto {
    width: auto;
}

table.list table {
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
    transition: background .2s
}

table.list tr:nth-child(even), table.multi tbody:nth-child(even) {
    background: rgba(var(--gSecondColor), 0.2);
}

table.list tr.disabled {
    color: #666;
}

table.multi tr {
    background: inherit !important;
}

table.list tr.checked {
    color: #633;
    background: #ffc;







<
<
<
<







43
44
45
46
47
48
49




50
51
52
53
54
55
56
    transition: background .2s
}

table.list tr:nth-child(even), table.multi tbody:nth-child(even) {
    background: rgba(var(--gSecondColor), 0.2);
}





table.multi tr {
    background: inherit !important;
}

table.list tr.checked {
    color: #633;
    background: #ffc;
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
}

table.list .check {
    width: 1%;
}

table.search th {
    background: rgb(217, 134, 40);
    background: rgba(217, 134, 40, 0.5);
    background: rgba(var(--gSecondColor), 0.5);
}

table.list .disabled {

    background: #eee;
    color: #999;
}

.userOrder .cur {
    background: rgba(var(--gSecondColor), 1.0);
    color: rgb(var(--gBgColor));
}

.userOrder a {
    text-decoration: none;
    display: block;
    color: inherit;
    padding: .2em;
    border-radius: .5em;
}

.userOrder a:hover {
    background: rgba(255, 255, 255, 0.5);
}

table.list .userOrder td, table.list .userOrder th {
    padding: .2em;
}

table.list .userOrder .check {







<
<




>
|
<
















|







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
}

table.list .check {
    width: 1%;
}

table.search th {


    background: rgba(var(--gSecondColor), 0.5);
}

table.list .disabled {
    color: var(--gBorderColor);
    background: var(--gLightBorderColor);

}

.userOrder .cur {
    background: rgba(var(--gSecondColor), 1.0);
    color: rgb(var(--gBgColor));
}

.userOrder a {
    text-decoration: none;
    display: block;
    color: inherit;
    padding: .2em;
    border-radius: .5em;
}

.userOrder a:hover {
    background: rgba(var(--gBgColor), 0.5);
}

table.list .userOrder td, table.list .userOrder th {
    padding: .2em;
}

table.list .userOrder .check {
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
    width: 1em;
    text-align: center;
    vertical-align: middle;
    font-weight: normal;
}

thead .cur .icn {
    color: #fff;
}

table.list .actions {
    text-align: right;
}

table.list .separator {
    border-left: 2px dashed #999;
}

table.list .icon {
    width: 1.5em;
    color: rgba(0, 0, 0, 0.3);
}

table.list .folder .icon {
    color: rgba(0, 0, 0, 0.5);
}







|












|



|

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
    width: 1em;
    text-align: center;
    vertical-align: middle;
    font-weight: normal;
}

thead .cur .icn {
    color: rgb(var(--gBgColor));
}

table.list .actions {
    text-align: right;
}

table.list .separator {
    border-left: 2px dashed #999;
}

table.list .icon {
    width: 1.5em;
    color: rgba(var(--gTextColor), 0.3);
}

table.list .folder .icon {
    color: rgba(var(--gTextColor), 0.5);
}

Modified src/www/admin/static/styles/10-accounting.css from [1b7cf590bb] to [432930b788].

22
23
24
25
26
27
28

29
30
31
32
33
34
35
36
}

.transaction-lines input[type=text] {
    min-width: 0 !important;
}

nav.acc-year {

    background: white;
    text-align: center;
    border-radius: .5rem;
    border: .2rem solid rgba(var(--gMainColor), 0.5);
    display: flex;
    align-items: center;
    margin-bottom: .5rem;
}







>
|







22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
}

.transaction-lines input[type=text] {
    min-width: 0 !important;
}

nav.acc-year {
    color: rgb(var(--gTextColor));
    background: rgb(var(--gBgColor));
    text-align: center;
    border-radius: .5rem;
    border: .2rem solid rgba(var(--gMainColor), 0.5);
    display: flex;
    align-items: center;
    margin-bottom: .5rem;
}
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
    color: rgb(var(--gMainColor));
}

.year-header {
    text-align: center;
    margin-bottom: .8em;
    padding-bottom: .5em;
    border-bottom: 1pt solid #999;
}

.year-header .print-btn button {
    font-size: 1.3rem;
}

.year-header form {







|







70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
    color: rgb(var(--gMainColor));
}

.year-header {
    text-align: center;
    margin-bottom: .8em;
    padding-bottom: .5em;
    border-bottom: 1pt solid var(--gBorderColor);
}

.year-header .print-btn button {
    font-size: 1.3rem;
}

.year-header form {

Modified src/www/admin/static/styles/config.css from [33fa4a39f3] to [cd8a35a686].






1
2
3
4
5
6
7





.error .trace {
	border: 1px solid #ccc;
	margin: 1em;
}

.error .trace h4 {
	background: #ccc;
>
>
>
>
>







1
2
3
4
5
6
7
8
9
10
11
12
.error {
    background: #fff;
    color: #000;
}

.error .trace {
	border: 1px solid #ccc;
	margin: 1em;
}

.error .trace h4 {
	background: #ccc;

Modified src/www/admin/static/styles/wiki.css from [5b5f5a2486] to [8d582848fc].

20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    .web-edit {
        display: block;
    }
}

#encryptPasswordDisplay {
    cursor: help;
    background: #ddd;
}

fieldset.wikiEncrypt, fieldset.wikiMain, .web-edit p.submit {
    grid-column: 2;
}

.web-edit dd.help {







|







20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    .web-edit {
        display: block;
    }
}

#encryptPasswordDisplay {
    cursor: help;
    background: var(--gLightBackgroundColor);
}

fieldset.wikiEncrypt, fieldset.wikiMain, .web-edit p.submit {
    grid-column: 2;
}

.web-edit dd.help {
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

.wikiFiles {
    text-align: center;
}

.wikiFooter, .wikiFiles {
    font-size: 0.8em;
    color: #666;
    border-top: 0.1em solid #ccc;
    clear: both;
}

.wikiMain samp {
    background: #eee;
    padding: 0.2em 0.3em;
}

.wikiChildren {
    margin: 1em 0 1em 1em;
    border: .1em solid #999;
    border-radius: .5em;
    padding: 1em;
    background: rgba(255, 255, 255, 0.5);
    float: right;
    clear: right;
    width: 25%;
}

.wikiChildren ul {
    color: #ccc;
    list-style-type: square;
    margin-left: 1em;
}

form#f_upload fieldset {
    position: relative;
}

.diff {
    margin: 1em;
}







|
|



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







47
48
49
50
51
52
53
54
55
56
57
58






















59
60
61
62
63
64
65

.wikiFiles {
    text-align: center;
}

.wikiFooter, .wikiFiles {
    font-size: 0.8em;
    color: var(--gBorderColor);
    border-top: 0.1em solid var(--gLightBorderColor);
    clear: both;
}























form#f_upload fieldset {
    position: relative;
}

.diff {
    margin: 1em;
}

Modified src/www/admin/web/_syntax_markdown.html from [cb696910b0] to [602dc80a26].

15
16
17
18
19
20
21

22
23
24
25
26
27
28
  h5  { font-size: 0.9em; }
  h6  { font-size: 0.8em; }
  article, aside, figure, footer, header, hgroup, menu, nav, section { display: block; }

  body {
    font-family: "Trebuchet MS", Arial, Helvetica, Sans-serif;
    padding: .8em;

  }
  pre, samp {
    display: block;
    background: #ccc;
    color: #000;
    border-radius: .5em;
    padding: .4em;







>







15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
  h5  { font-size: 0.9em; }
  h6  { font-size: 0.8em; }
  article, aside, figure, footer, header, hgroup, menu, nav, section { display: block; }

  body {
    font-family: "Trebuchet MS", Arial, Helvetica, Sans-serif;
    padding: .8em;
    background: #eee;
  }
  pre, samp {
    display: block;
    background: #ccc;
    color: #000;
    border-radius: .5em;
    padding: .4em;

Modified src/www/admin/web/_syntax_skriv.html from [a383e52cdf] to [8451d4b45e].

15
16
17
18
19
20
21

22
23
24
25
26
27
28
	h5  { font-size: 0.9em; }
	h6  { font-size: 0.8em; }
	article, aside, figure, footer, header, hgroup, menu, nav, section { display: block; }

	body {
		font-family: "Trebuchet MS", Arial, Helvetica, Sans-serif;
		padding: .8em;

	}
	pre, samp {
		display: block;
		background: #ccc;
		color: #000;
		border-radius: .5em;
		padding: .4em;







>







15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
	h5  { font-size: 0.9em; }
	h6  { font-size: 0.8em; }
	article, aside, figure, footer, header, hgroup, menu, nav, section { display: block; }

	body {
		font-family: "Trebuchet MS", Arial, Helvetica, Sans-serif;
		padding: .8em;
		background: #eee;
	}
	pre, samp {
		display: block;
		background: #ccc;
		color: #000;
		border-radius: .5em;
		padding: .4em;

Modified src/www/admin/web/page.php from [7684223213] to [fa91cc041f].

1
2
3
4
5
6
7
8




9
10
11
12
13
14
15
<?php

namespace Garradin;

use Garradin\Web\Web;
use Garradin\Entities\Web\Page;

require_once __DIR__ . '/_inc.php';





$page = Web::get(qg('p'));

if (!$page) {
	throw new UserException('Page inconnue');
}









>
>
>
>







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

namespace Garradin;

use Garradin\Web\Web;
use Garradin\Entities\Web\Page;

require_once __DIR__ . '/_inc.php';

if (!qg('p')) {
	throw new UserException('Page inconnue');
}

$page = Web::get(qg('p'));

if (!$page) {
	throw new UserException('Page inconnue');
}