Overview
Comment:Rename 'url' to 'author_url' in module/plugin.ini, add "open" button in extensions list, better handle errors in plugin.ini
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | dev
Files: files | file ages | folders
SHA3-256: f95cf4f0aa53f8f7ec93138a3a6fcd1a1f02ab35381292efd8d21428092003cd
User & Date: bohwaz on 2023-02-17 12:38:47
Other Links: branch diff | manifest | tags
Context
2023-02-17
21:21
Fix missing use constant statement for emails check-in: d8c24cbf4a user: bohwaz tags: dev
12:38
Rename 'url' to 'author_url' in module/plugin.ini, add "open" button in extensions list, better handle errors in plugin.ini check-in: f95cf4f0aa user: bohwaz tags: dev
12:37
Update default colors in CSS, use admin_color1 and 2 even in install, if set check-in: 2e065b30bb user: bohwaz tags: dev
Changes

Modified src/include/lib/Garradin/Entities/Module.php from [d85fe33668] to [2994320732].

16
17
18
19
20
21
22

23
24
25
26
27
28
29
{
	const ROOT = File::CONTEXT_SKELETON . '/modules';
	const DIST_ROOT = ROOT . '/skel-dist/modules';
	const META_FILE = 'module.ini';
	const ICON_FILE = 'icon.svg';
	const README_FILE = 'README.md';
	const CONFIG_FILE = 'config.html';


	// Snippets, don't forget to create alias constant in UserTemplate\Modules class
	const SNIPPET_TRANSACTION = 'snippets/transaction_details.html';
	const SNIPPET_USER = 'snippets/user_details.html';
	const SNIPPET_HOME_BUTTON = 'snippets/home_button.html';

	const SNIPPETS = [







>







16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
	const ROOT = File::CONTEXT_SKELETON . '/modules';
	const DIST_ROOT = ROOT . '/skel-dist/modules';
	const META_FILE = 'module.ini';
	const ICON_FILE = 'icon.svg';
	const README_FILE = 'README.md';
	const CONFIG_FILE = 'config.html';
	const INDEX_FILE = 'index.html';

	// Snippets, don't forget to create alias constant in UserTemplate\Modules class
	const SNIPPET_TRANSACTION = 'snippets/transaction_details.html';
	const SNIPPET_USER = 'snippets/user_details.html';
	const SNIPPET_HOME_BUTTON = 'snippets/home_button.html';

	const SNIPPETS = [
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
	 * Directory name
	 */
	protected string $name;

	protected string $label;
	protected ?string $description;
	protected ?string $author;
	protected ?string $url;
	protected ?string $restrict_section;
	protected ?int $restrict_level;
	protected bool $home_button;
	protected bool $menu;
	protected ?\stdClass $config;
	protected bool $enabled;








|







41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
	 * Directory name
	 */
	protected string $name;

	protected string $label;
	protected ?string $description;
	protected ?string $author;
	protected ?string $author_url;
	protected ?string $restrict_section;
	protected ?int $restrict_level;
	protected bool $home_button;
	protected bool $menu;
	protected ?\stdClass $config;
	protected bool $enabled;

84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
		if (!isset($ini->name)) {
			return false;
		}

		$this->set('label', $ini->name);
		$this->set('description', $ini->description ?? null);
		$this->set('author', $ini->author ?? null);
		$this->set('url', $ini->url ?? null);
		$this->set('home_button', !empty($ini->home_button));
		$this->set('menu', !empty($ini->menu));
		$this->set('restrict_section', $ini->restrict_section ?? null);
		$this->set('restrict_level', isset($ini->restrict_section, $ini->restrict_level, Session::ACCESS_WORDS[$ini->restrict_level]) ? Session::ACCESS_WORDS[$ini->restrict_level] : null);

		return true;
	}







|







85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
		if (!isset($ini->name)) {
			return false;
		}

		$this->set('label', $ini->name);
		$this->set('description', $ini->description ?? null);
		$this->set('author', $ini->author ?? null);
		$this->set('author_url', $ini->author_url ?? null);
		$this->set('home_button', !empty($ini->home_button));
		$this->set('menu', !empty($ini->menu));
		$this->set('restrict_section', $ini->restrict_section ?? null);
		$this->set('restrict_level', isset($ini->restrict_section, $ini->restrict_level, Session::ACCESS_WORDS[$ini->restrict_level]) ? Session::ACCESS_WORDS[$ini->restrict_level] : null);

		return true;
	}

Modified src/include/lib/Garradin/Entities/Plugin.php from [e14930c4dd] to [0b3948c904].

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
	protected string $name;

	protected string $label;
	protected string $version;

	protected ?string $description;
	protected ?string $author;
	protected ?string $url;

	protected bool $home_button;
	protected bool $menu;
	protected ?string $restrict_section;
	protected ?int $restrict_level;

	protected ?\stdClass $config;
	protected bool $enabled;



	public function hasCode(): bool
	{
		return file_exists($this->path());
	}

	public function selfCheck(): void
	{
		$this->assert(preg_match('/^' . Plugins::NAME_REGEXP . '$/', $this->name), 'Nom unique d\'extension invalide: ' . $this->name);
		$this->assert(trim($this->label) !== '', 'Le libellé ne peut rester vide');
		$this->assert(trim($this->version) !== '', 'La version ne peut rester vide');

		if ($this->hasCode() || $this->enabled) {
			$this->assert(!$this->menu || $this->hasFile(self::INDEX_FILE), 'Le fichier admin/index.php n\'existe pas alors que la directive "menu" est activée.');
			$this->assert(!$this->home_button || $this->hasFile(self::INDEX_FILE), 'Le fichier admin/index.php n\'existe pas alors que la directive "home_button" est activée.');
			$this->assert(!$this->home_button || $this->hasFile(self::ICON_FILE), 'Le fichier admin/icon.svg n\'existe pas alors que la directive "home_button" est activée.');
		}
	}











	/**
	 * Fills information from plugin.ini file
	 */
	public function updateFromINI(): bool
	{
		$ini = parse_ini_file($this->path(self::META_FILE), false, \INI_SCANNER_TYPED);







|








>
>









|
|







>
>
>
>
>
>
>
>
>
>







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
	protected string $name;

	protected string $label;
	protected string $version;

	protected ?string $description;
	protected ?string $author;
	protected ?string $author_url;

	protected bool $home_button;
	protected bool $menu;
	protected ?string $restrict_section;
	protected ?int $restrict_level;

	protected ?\stdClass $config;
	protected bool $enabled;

	protected ?string $_broken_message = null;

	public function hasCode(): bool
	{
		return file_exists($this->path());
	}

	public function selfCheck(): void
	{
		$this->assert(preg_match('/^' . Plugins::NAME_REGEXP . '$/', $this->name), 'Nom unique d\'extension invalide: ' . $this->name);
		$this->assert(isset($this->label) && trim($this->label) !== '', sprintf('%s : le nom de l\'extension ("name") ne peut rester vide', $this->name));
		$this->assert(isset($this->label) && trim($this->version) !== '', sprintf('%s : la version ne peut rester vide', $this->name));

		if ($this->hasCode() || $this->enabled) {
			$this->assert(!$this->menu || $this->hasFile(self::INDEX_FILE), 'Le fichier admin/index.php n\'existe pas alors que la directive "menu" est activée.');
			$this->assert(!$this->home_button || $this->hasFile(self::INDEX_FILE), 'Le fichier admin/index.php n\'existe pas alors que la directive "home_button" est activée.');
			$this->assert(!$this->home_button || $this->hasFile(self::ICON_FILE), 'Le fichier admin/icon.svg n\'existe pas alors que la directive "home_button" est activée.');
		}
	}

	public function setBrokenMessage(string $str)
	{
		$this->_broken_message = $str;
	}

	public function getBrokenMessage(): ?string
	{
		return $this->_broken_message;
	}

	/**
	 * Fills information from plugin.ini file
	 */
	public function updateFromINI(): bool
	{
		$ini = parse_ini_file($this->path(self::META_FILE), false, \INI_SCANNER_TYPED);
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127

		$this->assert(empty($ini->min_version) || version_compare(\Garradin\garradin_version(), $ini->min_version, '>='), sprintf('L\'extension "%s" nécessite Paheko version %s ou supérieure.', $this->name, $ini->min_version));

		$this->set('label', $ini->name);
		$this->set('version', $ini->version);
		$this->set('description', $ini->description ?? null);
		$this->set('author', $ini->author ?? null);
		$this->set('url', $ini->url ?? null);
		$this->set('home_button', !empty($ini->home_button));
		$this->set('menu', !empty($ini->menu));
		$this->set('restrict_section', $ini->restrict_section ?? null);
		$this->set('restrict_level', isset($ini->restrict_section, $ini->restrict_level, Session::ACCESS_WORDS[$ini->restrict_level]) ? Session::ACCESS_WORDS[$ini->restrict_level] : null);

		return true;
	}







|







125
126
127
128
129
130
131
132
133
134
135
136
137
138
139

		$this->assert(empty($ini->min_version) || version_compare(\Garradin\garradin_version(), $ini->min_version, '>='), sprintf('L\'extension "%s" nécessite Paheko version %s ou supérieure.', $this->name, $ini->min_version));

		$this->set('label', $ini->name);
		$this->set('version', $ini->version);
		$this->set('description', $ini->description ?? null);
		$this->set('author', $ini->author ?? null);
		$this->set('author_url', $ini->author_url ?? null);
		$this->set('home_button', !empty($ini->home_button));
		$this->set('menu', !empty($ini->menu));
		$this->set('restrict_section', $ini->restrict_section ?? null);
		$this->set('restrict_level', isset($ini->restrict_section, $ini->restrict_level, Session::ACCESS_WORDS[$ini->restrict_level]) ? Session::ACCESS_WORDS[$ini->restrict_level] : null);

		return true;
	}
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

	public function url(string $file = '', array $params = null)
	{
		if (null !== $params) {
			$params = '?' . http_build_query($params);
		}









		return sprintf('%sp/%s/%s%s', WWW_URL, $this->name, $file, $params);
	}

	public function getConfig(string $key = null)
	{
		if (is_null($key)) {
			return $this->config;
		}

		if (property_exists($this->config, $key)) {
			return $this->config->$key;
		}

		return null;
	}

	public function setConfigProperty(string $key, $value = null)
	{




		if (is_null($value)) {
			unset($this->config->$key);
		}
		else {
			$this->config->$key = $value;
		}








>
>
>
>
>
>
>
>
|

















>
>
>
>







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

	public function url(string $file = '', array $params = null)
	{
		if (null !== $params) {
			$params = '?' . http_build_query($params);
		}

		if (substr($file, 0, 6) == 'admin/') {
			$url = ADMIN_URL;
			$file = substr($file, 6);
		}
		else {
			$url = WWW_URL;
		}

		return sprintf('%sp/%s/%s%s', $url, $this->name, $file, $params);
	}

	public function getConfig(string $key = null)
	{
		if (is_null($key)) {
			return $this->config;
		}

		if (property_exists($this->config, $key)) {
			return $this->config->$key;
		}

		return null;
	}

	public function setConfigProperty(string $key, $value = null)
	{
		if (null === $this->config) {
			$this->config = new \stdClass;
		}

		if (is_null($value)) {
			unset($this->config->$key);
		}
		else {
			$this->config->$key = $value;
		}

344
345
346
347
348
349
350
351





352
353
354
355
356
357
358
		}
	}

	public function route(string $uri): void
	{
		$uri = ltrim($uri, '/');

		if (0 !== strpos($uri, 'admin/')) {





			$uri = 'public/' . $uri;
		}

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








|
>
>
>
>
>







368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
		}
	}

	public function route(string $uri): void
	{
		$uri = ltrim($uri, '/');

		if (0 === strpos($uri, 'admin/')) {
			if (!Session::getInstance()->isLogged()) {
				Utils::redirect('!login.php');
			}
		}
		else {
			$uri = 'public/' . $uri;
		}

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

Modified src/include/lib/Garradin/Install.php from [0a113d106d] to [83fa8d0d78].

9
10
11
12
13
14
15

16
17
18
19
20
21
22
use Garradin\Entities\Users\User;
use Garradin\Entities\Files\File;
use Garradin\Entities\Search;
use Garradin\Users\DynamicFields;
use Garradin\Users\Session;
use Garradin\Files\Files;
use Garradin\UserTemplate\Modules;


use KD2\HTTP;

/**
 * Pour procéder à l'installation de l'instance Garradin
 * Utile pour automatiser l'installation sans passer par la page d'installation
 */







>







9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use Garradin\Entities\Users\User;
use Garradin\Entities\Files\File;
use Garradin\Entities\Search;
use Garradin\Users\DynamicFields;
use Garradin\Users\Session;
use Garradin\Files\Files;
use Garradin\UserTemplate\Modules;
use Garradin\Plugins;

use KD2\HTTP;

/**
 * Pour procéder à l'installation de l'instance Garradin
 * Utile pour automatiser l'installation sans passer par la page d'installation
 */
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
		]);
		$search->created = new \DateTime;
		$search->save();

		$config->save();

		// Install welcome plugin if available
		$has_welcome_plugin = Plugin::getPath('welcome');

		if ($has_welcome_plugin) {
			Plugin::install('welcome');
		}

		foreach ($plugins as $plugin) {
			Plugin::install($plugin);
		}

		Modules::refresh();

		foreach ($modules as $module) {
			$m = Modules::get($module);
			$m->set('enabled', true);







|


|



|







352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
		]);
		$search->created = new \DateTime;
		$search->save();

		$config->save();

		// Install welcome plugin if available
		$has_welcome_plugin = Plugins::exists('welcome');

		if ($has_welcome_plugin) {
			Plugins::install('welcome');
		}

		foreach ($plugins as $plugin) {
			Plugins::install($plugin);
		}

		Modules::refresh();

		foreach ($modules as $module) {
			$m = Modules::get($module);
			$m->set('enabled', true);

Modified src/include/lib/Garradin/Plugins.php from [2cbb616829] to [1823da1b99].

44
45
46
47
48
49
50





51
52
53
54
55
56
57
		if (file_exists(PLUGINS_ROOT . '/' . $name . '.tar.gz')) {
			return 'phar://' . PLUGINS_ROOT . '/' . $name . '.tar.gz';
		}
		else {
			return PLUGINS_ROOT . '/' . $name;
		}
	}






	/**
	 * Déclenche le signal donné auprès des plugins enregistrés
	 * @param  string $signal Nom du signal
	 * @param  array  $params Paramètres du callback (array ou null)
	 * @return NULL 		  NULL si aucun plugin n'a été appelé,
	 * TRUE si un plugin a été appelé et a arrêté l'exécution,







>
>
>
>
>







44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
		if (file_exists(PLUGINS_ROOT . '/' . $name . '.tar.gz')) {
			return 'phar://' . PLUGINS_ROOT . '/' . $name . '.tar.gz';
		}
		else {
			return PLUGINS_ROOT . '/' . $name;
		}
	}

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

	/**
	 * Déclenche le signal donné auprès des plugins enregistrés
	 * @param  string $signal Nom du signal
	 * @param  array  $params Paramètres du callback (array ou null)
	 * @return NULL 		  NULL si aucun plugin n'a été appelé,
	 * TRUE si un plugin a été appelé et a arrêté l'exécution,
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

		if (null === $params) {
			$params = [];
		}

		foreach ($list as $row)
		{
			$path = self::getPath($row->plugin);

			// Ne pas appeler les plugins dont le code n'existe pas/plus,
			if (!$path)


			{



				continue;
			}

			$params['plugin_root'] = $path;

			$return = call_user_func_array('Garradin\\Plugin\\' . $row->callback, [&$params, &$callback_return]);

			if (true === $return) {
				return true;
			}
		}

		return false;
	}

	static public function listModulesAndPlugins(bool $installable = false): array
	{
		$list = [];

		if ($installable) {
			foreach (EM::getInstance(Module::class)->iterate('SELECT * FROM @TABLE WHERE enabled = 0;') as $m) {
				$list[$m->name] = ['module' => $m];
			}

			foreach (self::listInstallable() as $p) {
				$list[$p->name] = ['plugin'   => $p];
			}

			foreach (self::listInstalled() as $p) {
				if ($p->enabled) {
					continue;
				}








|


|
>
>
|
>
>
>



|

|


















|
|







92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141

		if (null === $params) {
			$params = [];
		}

		foreach ($list as $row)
		{
			$path = self::exists($row->plugin);

			// Ne pas appeler les plugins dont le code n'existe pas/plus,
			if (!$path) {
				continue;
			}

			$callback = 'Garradin\\Plugin\\' . $row->callback;

			if (!is_callable($callback)) {
				continue;
			}

			$params['plugin_root'] = self::getPath($row->plugin);

			$return = call_user_func_array($callback, [&$params, &$callback_return]);

			if (true === $return) {
				return true;
			}
		}

		return false;
	}

	static public function listModulesAndPlugins(bool $installable = false): array
	{
		$list = [];

		if ($installable) {
			foreach (EM::getInstance(Module::class)->iterate('SELECT * FROM @TABLE WHERE enabled = 0;') as $m) {
				$list[$m->name] = ['module' => $m];
			}

			foreach (self::listInstallable() as $name => $p) {
				$list[$name] = ['plugin'   => $p];
			}

			foreach (self::listInstalled() as $p) {
				if ($p->enabled) {
					continue;
				}

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
		foreach ($list as &$item) {
			$c = isset($item['plugin']) ? $item['plugin'] : $item['module'];
			$item['icon_url'] = $c->icon_url();
			$item['name'] = $c->name;
			$item['label'] = $c->label;
			$item['description'] = $c->description;
			$item['author'] = $c->author;
			$item['url'] = $c->url;
			$item['config_url'] = $c->hasConfig() ? $c->url($c::CONFIG_FILE) : null;
			$item['readme_url'] = $c->hasFile($c::README_FILE) ? $c->url($c::README_FILE) : null;
			$item['enabled'] = $c->enabled;
			$item['installed'] = isset($item['plugin']) ? $c->exists() : true;
			$item['broken'] = isset($item['plugin']) ? !$c->hasCode() : false;

			$item['restrict_section'] = $c->restrict_section;
			$item['restrict_level'] = $c->restrict_level;






		}

		unset($item);

		usort($list, fn ($a, $b) => strnatcasecmp($a['label'], $b['label']));

		return $list;
	}

	static public function listModulesAndPluginsMenu(Session $session): array
	{
		$list = [];







|





>


>
>
>
>
>
>




|







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
		foreach ($list as &$item) {
			$c = isset($item['plugin']) ? $item['plugin'] : $item['module'];
			$item['icon_url'] = $c->icon_url();
			$item['name'] = $c->name;
			$item['label'] = $c->label;
			$item['description'] = $c->description;
			$item['author'] = $c->author;
			$item['author_url'] = $c->author_url;
			$item['config_url'] = $c->hasConfig() ? $c->url($c::CONFIG_FILE) : null;
			$item['readme_url'] = $c->hasFile($c::README_FILE) ? $c->url($c::README_FILE) : null;
			$item['enabled'] = $c->enabled;
			$item['installed'] = isset($item['plugin']) ? $c->exists() : true;
			$item['broken'] = isset($item['plugin']) ? !$c->hasCode() : false;
			$item['broken_message'] = isset($item['plugin']) ? $c->getBrokenMessage() : false;
			$item['restrict_section'] = $c->restrict_section;
			$item['restrict_level'] = $c->restrict_level;

			$item['url'] = null;

			if ($c->hasFile($c::INDEX_FILE)) {
				$item['url'] = $c->url($c::INDEX_FILE);
			}
		}

		unset($item);

		usort($list, fn ($a, $b) => strnatcasecmp($a['label'] ?? $a['name'], $b['label'] ?? $b['name']));

		return $list;
	}

	static public function listModulesAndPluginsMenu(Session $session): array
	{
		$list = [];
259
260
261
262
263
264
265
266
267
268


269




270
271
272
273
274
275
276
	{
		return EM::getInstance(Plugin::class)->all('SELECT * FROM @TABLE ORDER BY label COLLATE NOCASE ASC;');
	}

	/**
	 * Liste les plugins téléchargés mais non installés
	 */
	static public function listInstallable(): array
	{
		$list = [];


		$exists = DB::getInstance()->getAssoc('SELECT name, name FROM plugins;');





		foreach (glob(PLUGINS_ROOT . '/*') as $file)
		{
			if (substr($file, 0, 1) == '.') {
				continue;
			}








|


>
>
|
>
>
>
>







276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
	{
		return EM::getInstance(Plugin::class)->all('SELECT * FROM @TABLE ORDER BY label COLLATE NOCASE ASC;');
	}

	/**
	 * Liste les plugins téléchargés mais non installés
	 */
	static public function listInstallable(bool $check_exists = true): array
	{
		$list = [];

		if ($check_exists) {
			$exists = DB::getInstance()->getAssoc('SELECT name, name FROM plugins;');
		}
		else {
			$exists = [];
		}

		foreach (glob(PLUGINS_ROOT . '/*') as $file)
		{
			if (substr($file, 0, 1) == '.') {
				continue;
			}

289
290
291
292
293
294
295
296
297
298
299
300
301
302



303
304
305
306
307
308
309
310
311
			// Ignore existing plugins
			if (in_array($name, $exists)) {
				continue;
			}

			$list[$file] = null;

			try {
				$p = new Plugin;
				$p->name = $name;
				$p->updateFromINI();
				$p->selfCheck();
				$list[$name] = $p;
			}



			catch (ValidationException $e) {
				$list[$name] = $file . ': ' . $e->getMessage();
			}
		}

		ksort($list);

		return $list;
	}







<
|
|
|
<
|
|
>
>
>

|







312
313
314
315
316
317
318

319
320
321

322
323
324
325
326
327
328
329
330
331
332
333
334
335
			// Ignore existing plugins
			if (in_array($name, $exists)) {
				continue;
			}

			$list[$file] = null;


			$p = new Plugin;
			$p->name = $name;
			$p->updateFromINI();

			$list[$name] = $p;

			try {
				$p->selfCheck();
			}
			catch (ValidationException $e) {
				$p->setBrokenMessage($e->getMessage());
			}
		}

		ksort($list);

		return $list;
	}

Modified src/include/lib/Garradin/UserTemplate/CommonFunctions.php from [dfd204480b] to [cc6c1c6c13].

341
342
343
344
345
346
347




348
349
350
351
352
353
354
	}

	static public function link(array $params): string
	{
		$href = $params['href'];
		$label = $params['label'];
		$prefix = $params['prefix'] ?? '';





		// href can be prefixed with '!' to make the URL relative to ADMIN_URL
		if (substr($href, 0, 1) == '!') {
			$href = ADMIN_URL . substr($params['href'], 1);
		}

		// propagate _dialog param if we are in an iframe







>
>
>
>







341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
	}

	static public function link(array $params): string
	{
		$href = $params['href'];
		$label = $params['label'];
		$prefix = $params['prefix'] ?? '';

		if (!$href || !$label) {
			return '';
		}

		// href can be prefixed with '!' to make the URL relative to ADMIN_URL
		if (substr($href, 0, 1) == '!') {
			$href = ADMIN_URL . substr($params['href'], 1);
		}

		// propagate _dialog param if we are in an iframe

Modified src/include/lib/Garradin/UserTemplate/UserTemplate.php from [3f0c556abf] to [26f1142bcc].

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
			'logged_user'  => $is_logged ? $session->getUser() : null,
			'dialog'       => isset($_GET['_dialog']) ? ($_GET['_dialog'] ?: true) : false,
		];

		return self::$root_variables;
	}

	public function __construct(string $path)
	{
		$this->_tpl_path = $path;

		if ($file = Files::get(File::CONTEXT_SKELETON . '/' . $path)) {
			if ($file->type != $file::TYPE_FILE) {
				throw new \LogicException('Cannot construct a UserTemplate with a directory');
			}

			$this->file = $file;
			$this->modified = $file->modified->getTimestamp();
		}
		else {
			$this->path = self::DIST_ROOT . $path;

			if (!($this->modified = @filemtime($this->path))) {
				throw new \InvalidArgumentException('File not found: ' . $this->path);
			}
		}








|



|







|







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
			'logged_user'  => $is_logged ? $session->getUser() : null,
			'dialog'       => isset($_GET['_dialog']) ? ($_GET['_dialog'] ?: true) : false,
		];

		return self::$root_variables;
	}

	public function __construct(?string $path)
	{
		$this->_tpl_path = $path;

		if ($path && $file = Files::get(File::CONTEXT_SKELETON . '/' . $path)) {
			if ($file->type != $file::TYPE_FILE) {
				throw new \LogicException('Cannot construct a UserTemplate with a directory');
			}

			$this->file = $file;
			$this->modified = $file->modified->getTimestamp();
		}
		elseif ($path) {
			$this->path = self::DIST_ROOT . $path;

			if (!($this->modified = @filemtime($this->path))) {
				throw new \InvalidArgumentException('File not found: ' . $this->path);
			}
		}

Modified src/include/migrations/1.3/1.3.0.sql from [99fcb50e59] to [d138e6e6bc].

45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

DROP TABLE services_reminders_sent_old;
DROP TABLE acc_transactions_users_old;
DROP TABLE acc_transactions_old;
DROP TABLE services_users_old;

-- Remove old plugin as it cannot be uninstalled as it no longer exists
DELETE FROM plugins_old WHERE nom = 'ouvertures';
DELETE FROM plugins_signaux_old WHERE plugin = 'ouvertures';

-- Rename plugins table columns to English
INSERT INTO plugins (name, label, description, author, url, version, config, enabled, menu, restrict_level, restrict_section)
	SELECT id, nom, description, auteur, url, version, config, 1, menu,
	CASE WHEN menu_condition IS NOT NULL THEN 2 ELSE NULL END,
	CASE WHEN menu_condition IS NOT NULL THEN 'users' ELSE NULL END
	FROM plugins_old;
INSERT INTO plugins_signals SELECT * FROM plugins_signaux_old;

DROP TABLE plugins_signaux_old;







|



|







45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

DROP TABLE services_reminders_sent_old;
DROP TABLE acc_transactions_users_old;
DROP TABLE acc_transactions_old;
DROP TABLE services_users_old;

-- Remove old plugin as it cannot be uninstalled as it no longer exists
DELETE FROM plugins_old WHERE id = 'ouvertures';
DELETE FROM plugins_signaux_old WHERE plugin = 'ouvertures';

-- Rename plugins table columns to English
INSERT INTO plugins (name, label, description, author, author_url, version, config, enabled, menu, restrict_level, restrict_section)
	SELECT id, nom, description, auteur, url, version, config, 1, menu,
	CASE WHEN menu_condition IS NOT NULL THEN 2 ELSE NULL END,
	CASE WHEN menu_condition IS NOT NULL THEN 'users' ELSE NULL END
	FROM plugins_old;
INSERT INTO plugins_signals SELECT * FROM plugins_signaux_old;

DROP TABLE plugins_signaux_old;

Modified src/include/migrations/1.3/schema.sql from [c3e1dca3f5] to [a912bda722].

30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
CREATE TABLE IF NOT EXISTS plugins
(
    id INTEGER NOT NULL PRIMARY KEY,
    name TEXT NOT NULL,
    label TEXT NOT NULL,
    description TEXT NULL,
    author TEXT NULL,
    url TEXT NULL,
    version TEXT NOT NULL,
    menu INT NOT NULL DEFAULT 0,
    home_button INT NOT NULL DEFAULT 0,
    restrict_section TEXT NULL,
    restrict_level INT NULL,
    config TEXT NULL,
    enabled INTEGER NOT NULL DEFAULT 0







|







30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
CREATE TABLE IF NOT EXISTS plugins
(
    id INTEGER NOT NULL PRIMARY KEY,
    name TEXT NOT NULL,
    label TEXT NOT NULL,
    description TEXT NULL,
    author TEXT NULL,
    author_url TEXT NULL,
    version TEXT NOT NULL,
    menu INT NOT NULL DEFAULT 0,
    home_button INT NOT NULL DEFAULT 0,
    restrict_section TEXT NULL,
    restrict_level INT NULL,
    config TEXT NULL,
    enabled INTEGER NOT NULL DEFAULT 0
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
-- List of modules
(
    id INTEGER NOT NULL PRIMARY KEY,
    name TEXT NOT NULL,
    label TEXT NOT NULL,
    description TEXT NULL,
    author TEXT NULL,
    url TEXT NULL,
    menu INT NOT NULL DEFAULT 0,
    home_button INT NOT NULL DEFAULT 0,
    restrict_section TEXT NULL,
    restrict_level INT NULL,
    config TEXT NULL,
    enabled INTEGER NOT NULL DEFAULT 0
);







|







59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
-- List of modules
(
    id INTEGER NOT NULL PRIMARY KEY,
    name TEXT NOT NULL,
    label TEXT NOT NULL,
    description TEXT NULL,
    author TEXT NULL,
    author_url TEXT NULL,
    menu INT NOT NULL DEFAULT 0,
    home_button INT NOT NULL DEFAULT 0,
    restrict_section TEXT NULL,
    restrict_level INT NULL,
    config TEXT NULL,
    enabled INTEGER NOT NULL DEFAULT 0
);

Modified src/templates/config/ext/index.tpl from [0f165e3e3f] to [c2160f4936].

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
		<thead>
			<td></td>
			<td>Extension</td>
			<td>Accès restreint</td>
			<td></td>
			<td></td>
			<td></td>

		</thead>
		<tbody>
			{foreach from=$list item="item"}
			<tr {if $_GET.focus == $item.name}class="highlight"{/if}>







				<td class="icon">
					{if $item.icon_url}
					<svg>
						<use xlink:href='{$item.icon_url}#img' href="{$item.icon_url}#img"></use>

					</svg>

					{/if}
				</td>
				<td>
					<h3>{$item.label}
						{if $item.module && $item.module->canDelete()}
							<strong class="tag">Modifiée</strong>
						{elseif $item.module}
							<span class="tag">Modifiable</span>
						{/if}
					</h3>
					<small>{$item.description|escape|nl2br}</small><br />
					<small class="help">

						Par {link label=$item.author href=$item.url target="_blank"}

						{if $item.plugin && $item.plugin.version}— Version {$item.plugin.version}{/if}
						{if $item.readme_url}
							— {link href=$item.readme_url label="Documentation" target="_dialog"}
						{/if}
					</small>
				</td>
			{if $item.broken}
				<td colspan="4">
					<strong class="error">Extension cassée</strong><br />
					{if ENABLE_TECH_DETAILS}
						<strong>Le code source de l'extension est absent du répertoire <tt>…/data/plugins/</tt></strong>
					{else}
						<strong>Cette extension n'est pas installée sur ce serveur.</strong><br />
					{/if}
					<br />
					<small>Il n'est pas possible de la supprimer non plus, le code source est nécessaire pour pouvoir la supprimer.</small>
				</td>
			{else}
				<td>
					{if $item.restrict_section}
						<span class="permissions">{display_permissions section=$item.restrict_section level=$item.restrict_level}</span>





					{/if}
				</td>
				<td class="actions">
					{if $item.module && $item.enabled}
						{if $item.module->hasLocal() && $item.module->hasDist()}
							{linkbutton label="Remettre à zéro" href="delete.php?module=%s"|args:$item.name shape="reset" target="_dialog"}
						{/if}
						{*FIXME{linkbutton label="Modifier" href="edit.php?module=%s"|args:$item.name shape="edit" target="_dialog"}*}
					{elseif $item.module && !$item.enabled && $item.module->canDelete()}
						{linkbutton label="Supprimer" href="delete.php?module=%s"|args:$item.name shape="delete" target="_dialog"}
					{elseif $item.plugin && !$item.enabled && $item.installed}
						{linkbutton label="Supprimer" href="delete.php?plugin=%s"|args:$item.name shape="delete" target="_dialog"}
					{/if}
				</td>
				<td class="actions">
					{if $item.config_url && $item.enabled}
						{linkbutton label="Configurer" href=$item.config_url shape="settings" target="_dialog"}
					{/if}
				</td>
				<td class="actions">
					{if $item.module}
						{if $item.enabled}
							{button type="submit" label="Désactiver" shape="eye-off" name="disable_module" value=$item.name}
						{else}
							{button type="submit" label="Activer" shape="eye" name="enable" value=$item.name}
						{/if}
					{else}
						{if $item.enabled}
							{button type="submit" label="Désactiver" shape="eye-off" name="disable_plugin" value=$item.name}
						{else}
							{button type="submit" label="Activer" shape="eye" name="install" value=$item.name}
						{/if}
					{/if}
				</td>

			{/if}
			</tr>
			{/foreach}
		</tbody>
	</table>
	{csrf_field key=$csrf_key}
</form>

<p class="help">
	La mention <em class="tag">Modifiable</em> indique que cette extension est un module que vous pouvez modifier. {linkbutton shape="help" label="Documentation des modules" href=$url_help_modules target="_dialog"}
</p>

{include file="_foot.tpl"}







>




>
>
>
>
>
>
>


<
|
>
|
>



|








>
|
>






|
|
<
|
|
|
|
|
|
|
|
|
|
|
|
>
>
>
>
>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>













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
133
134
135
136
		<thead>
			<td></td>
			<td>Extension</td>
			<td>Accès restreint</td>
			<td></td>
			<td></td>
			<td></td>
			<td></td>
		</thead>
		<tbody>
			{foreach from=$list item="item"}
			<tr {if $_GET.focus == $item.name}class="highlight"{/if}>
			{if $item.broken_message}
				<td></td>
				<td colspan="6">
					<strong class="error">Extension cassée : {$item.name}</strong><br />
					{$item.broken_message}
				</td>
			{else}
				<td class="icon">
					{if $item.icon_url}

						<svg><use xlink:href='{$item.icon_url}#img' href="{$item.icon_url}#img"></use></svg>
						{if $item.url}
							</a>
						{/if}
					{/if}
				</td>
				<td>
					<h3>{if $item.label}{$item.label}{else}{$item.name}{/if}
						{if $item.module && $item.module->canDelete()}
							<strong class="tag">Modifiée</strong>
						{elseif $item.module}
							<span class="tag">Modifiable</span>
						{/if}
					</h3>
					<small>{$item.description|escape|nl2br}</small><br />
					<small class="help">
						{if $item.author}
							Par {link label=$item.author href=$item.url target="_blank"}
						{/if}
						{if $item.plugin && $item.plugin.version}— Version {$item.plugin.version}{/if}
						{if $item.readme_url}
							— {link href=$item.readme_url label="Documentation" target="_dialog"}
						{/if}
					</small>
				</td>
				{if $item.broken}
					<td colspan="5">

						{if ENABLE_TECH_DETAILS}
							<strong>Le code source de l'extension est absent du répertoire <tt>…/data/plugins/</tt></strong>
						{else}
							<strong>Cette extension n'est pas installée sur ce serveur.</strong><br />
						{/if}
						<br />
						<small>Il n'est pas possible de la supprimer non plus, le code source est nécessaire pour pouvoir la supprimer.</small>
					</td>
				{else}
					<td>
						{if $item.restrict_section}
							<span class="permissions">{display_permissions section=$item.restrict_section level=$item.restrict_level}</span>
						{/if}
					</td>
					<td>
						{if $item.enabled && $item.url}
							{linkbutton shape="right" label="Ouvrir" href=$item.url}
						{/if}
					</td>
					<td class="actions">
						{if $item.module && $item.enabled}
							{if $item.module->hasLocal() && $item.module->hasDist()}
								{linkbutton label="Remettre à zéro" href="delete.php?module=%s"|args:$item.name shape="reset" target="_dialog"}
							{/if}
							{*FIXME{linkbutton label="Modifier" href="edit.php?module=%s"|args:$item.name shape="edit" target="_dialog"}*}
						{elseif $item.module && !$item.enabled && $item.module->canDelete()}
							{linkbutton label="Supprimer" href="delete.php?module=%s"|args:$item.name shape="delete" target="_dialog"}
						{elseif $item.plugin && !$item.enabled && $item.installed}
							{linkbutton label="Supprimer" href="delete.php?plugin=%s"|args:$item.name shape="delete" target="_dialog"}
						{/if}
					</td>
					<td class="actions">
						{if $item.config_url && $item.enabled}
							{linkbutton label="Configurer" href=$item.config_url shape="settings" target="_dialog"}
						{/if}
					</td>
					<td class="actions">
						{if $item.module}
							{if $item.enabled}
								{button type="submit" label="Désactiver" shape="eye-off" name="disable_module" value=$item.name}
							{else}
								{button type="submit" label="Activer" shape="eye" name="enable" value=$item.name}
							{/if}
						{else}
							{if $item.enabled}
								{button type="submit" label="Désactiver" shape="eye-off" name="disable_plugin" value=$item.name}
							{else}
								{button type="submit" label="Activer" shape="eye" name="install" value=$item.name}
							{/if}
						{/if}
					</td>
				{/if}
			{/if}
			</tr>
			{/foreach}
		</tbody>
	</table>
	{csrf_field key=$csrf_key}
</form>

<p class="help">
	La mention <em class="tag">Modifiable</em> indique que cette extension est un module que vous pouvez modifier. {linkbutton shape="help" label="Documentation des modules" href=$url_help_modules target="_dialog"}
</p>

{include file="_foot.tpl"}