Overview
Comment:Merge changes from trunk
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | dev
Files: files | file ages | folders
SHA3-256: 49124e29bef85e197f5c9124f61d02158c3fb5bce381ece3b3f38a2a8a621885
User & Date: bohwaz on 2021-02-26 01:09:01
Other Links: branch diff | manifest | tags
Context
2021-02-26
18:26
Improve UI of document preview and dialog frames check-in: 2d601784e0 user: bohwaz tags: dev
01:09
Merge changes from trunk check-in: 49124e29be user: bohwaz tags: dev
01:07
Alert when deleting directory check-in: 8512043f1a user: bohwaz tags: dev
2021-02-25
20:54
Add "first connection" to label for lost password check-in: a1a07cf6fa user: bohwaz tags: trunk, stable
Changes

Modified doc/index.md from [af8aa07b41] to [d17dd5a726].

103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
	r.json().then((list) => {
		let last;
		let selected;

		list.forEach((file) => {
			var v = file.name.match(/^garradin-(.*)\.tar\.bz2/);

			if (!v) {
				return;
			}

			if (!last || isNewerVersion(last, v[1])) {
				last = v[1];
				selected = file;
			}







|







103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
	r.json().then((list) => {
		let last;
		let selected;

		list.forEach((file) => {
			var v = file.name.match(/^garradin-(.*)\.tar\.bz2/);

			if (!v || v[1].match(/-(alpha|rc|beta)/)) {
				return;
			}

			if (!last || isNewerVersion(last, v[1])) {
				last = v[1];
				selected = file;
			}

Modified src/include/lib/Garradin/CSV_Custom.php from [f34f839792] to [fc76dfa5e6].

136
137
138
139
140
141
142




143
144
145
146
147
148
149
		}

		foreach ($this->mandatory_columns as $key) {
			if (!in_array($key, $translation, true)) {
				throw new UserException(sprintf('La colonne "%s" est obligatoire mais n\'a pas été sélectionnée ou n\'existe pas.', $this->columns[$key]));
			}
		}





		$this->translation = $translation;

		$this->session->set($this->key . '_translation', $this->translation);
	}

	public function clear(): void







>
>
>
>







136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
		}

		foreach ($this->mandatory_columns as $key) {
			if (!in_array($key, $translation, true)) {
				throw new UserException(sprintf('La colonne "%s" est obligatoire mais n\'a pas été sélectionnée ou n\'existe pas.', $this->columns[$key]));
			}
		}

		if (!count($translation)) {
			throw new UserException('Aucune colonne n\'a été sélectionnée');
		}

		$this->translation = $translation;

		$this->session->set($this->key . '_translation', $this->translation);
	}

	public function clear(): void

Modified src/include/lib/Garradin/DynamicList.php from [35e6a8d407] to [21ed3dc2ae].

22
23
24
25
26
27
28





29
30
31
32
33
34
35
	public function __construct(array $columns, string $tables, string $conditions = '1')
	{
		$this->columns = $columns;
		$this->tables = $tables;
		$this->conditions = $conditions;
		$this->order = key($columns);
	}






	public function __get($key)
	{
		return $this->$key;
	}

	public function setTitle(string $title) {







>
>
>
>
>







22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
	public function __construct(array $columns, string $tables, string $conditions = '1')
	{
		$this->columns = $columns;
		$this->tables = $tables;
		$this->conditions = $conditions;
		$this->order = key($columns);
	}

	public function __isset($key)
	{
		return property_exists($this, $key);
	}

	public function __get($key)
	{
		return $this->$key;
	}

	public function setTitle(string $title) {

Modified src/include/lib/Garradin/Membres.php from [3607d944ce] to [4de8b8cf6d].

341
342
343
344
345
346
347




348
349
350
351
352
353
354

        if (!count($recipients)) {
        	throw new UserException('Aucun destinataire de la liste ne possède d\'adresse email.');
        }

        foreach ($recipients as $recipient)
        {




            Utils::sendEmail(Utils::EMAIL_CONTEXT_BULK, $recipient->email, $subject, $message, $recipient->id);
        }

        if ($send_copy)
        {
            Utils::sendEmail(Utils::EMAIL_CONTEXT_BULK, $config->get('email_asso'), $subject, $message);
        }







>
>
>
>







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

        if (!count($recipients)) {
        	throw new UserException('Aucun destinataire de la liste ne possède d\'adresse email.');
        }

        foreach ($recipients as $recipient)
        {
            if (!isset($recipient->email, $recipient->id)) {
                throw new UserException('Il manque l\'identifiant ou l\'email dans le résultat');
            }

            Utils::sendEmail(Utils::EMAIL_CONTEXT_BULK, $recipient->email, $subject, $message, $recipient->id);
        }

        if ($send_copy)
        {
            Utils::sendEmail(Utils::EMAIL_CONTEXT_BULK, $config->get('email_asso'), $subject, $message);
        }

Modified src/include/lib/Garradin/Recherche.php from [d2da83f543] to [25cb8d4a11].

77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
...
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
				}
			}
		}

		return $query;
	}

	public function duplicate(int $id)
	{
		DB::getInstance()->preparedQuery('INSERT INTO recherches (id_membre, intitule, cible, type, contenu)
			SELECT id_membre, \'Copie de : \' || intitule, cible, type, contenu FROM recherches WHERE id = ?;', [$id]);
	}

	public function edit($id, $data)
	{
		$allowed = ['intitule', 'id_membre', 'type', 'cible', 'contenu'];

		// Supprimer les champs qui ne sont pas ceux de la BDD
		$data = array_intersect_key($data, array_flip($allowed));

................................................................................
		if (!in_array($target, self::TARGETS, true))
		{
			throw new \InvalidArgumentException('Cible inconnue : ' . $target);
		}

		if (null !== $force_select)
		{
			$query = preg_replace('/^\s*SELECT.*FROM\s+/Ui', 'SELECT ' . implode(', ', $force_select) . ' FROM ', $query);
		}

		if (!$no_limit && !preg_match('/LIMIT\s+\d+/i', $query))
		{
			$query = preg_replace('/;?\s*$/', '', $query);
			$query .= ' LIMIT 100';
		}







<
<
<
<
<
<







 







|







77
78
79
80
81
82
83






84
85
86
87
88
89
90
...
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
				}
			}
		}

		return $query;
	}







	public function edit($id, $data)
	{
		$allowed = ['intitule', 'id_membre', 'type', 'cible', 'contenu'];

		// Supprimer les champs qui ne sont pas ceux de la BDD
		$data = array_intersect_key($data, array_flip($allowed));

................................................................................
		if (!in_array($target, self::TARGETS, true))
		{
			throw new \InvalidArgumentException('Cible inconnue : ' . $target);
		}

		if (null !== $force_select)
		{
			$query = preg_replace('/^\s*SELECT.*FROM\s+/Uis', 'SELECT ' . implode(', ', $force_select) . ' FROM ', $query);
		}

		if (!$no_limit && !preg_match('/LIMIT\s+\d+/i', $query))
		{
			$query = preg_replace('/;?\s*$/', '', $query);
			$query .= ' LIMIT 100';
		}

Modified src/include/lib/Garradin/Sauvegarde.php from [6297c98cf3] to [63babef5f6].

63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
			$version = DB::parseVersion($db->querySingle('PRAGMA user_version;'));

			if (null === $version) {
				// for versions prior to 1.1.0
				$version = $db->querySingle('SELECT valeur FROM config WHERE cle = \'version\';');
			}

			// Delete SHM and WAL files
			if ($db->querySingle('PRAGMA journal_mode;') == 'wal') {
				$db = new \SQLite3(DATA_ROOT . '/' . $file, \SQLITE3_OPEN_READWRITE);
				$db->exec('PRAGMA journal_mode = DELETE;');
			}

			$db->close();

			$out[$file] = (object) [
				'filename'    => $file,
				'date'        => filemtime(DATA_ROOT . '/' . $file),
				'name'        => $name != $file ? $name : null,
				'version'     => $version,







<
<
<
<
<
<







63
64
65
66
67
68
69






70
71
72
73
74
75
76
			$version = DB::parseVersion($db->querySingle('PRAGMA user_version;'));

			if (null === $version) {
				// for versions prior to 1.1.0
				$version = $db->querySingle('SELECT valeur FROM config WHERE cle = \'version\';');
			}







			$db->close();

			$out[$file] = (object) [
				'filename'    => $file,
				'date'        => filemtime(DATA_ROOT . '/' . $file),
				'name'        => $name != $file ? $name : null,
				'version'     => $version,

Modified src/include/lib/Garradin/Template.php from [c91ab7d8ed] to [259b32cdc3].

561
562
563
564
565
566
567
568


569
570
571
572
573
574
575
			case 'tel':
				return '<a href="tel:' . rawurlencode($v) . '">' . htmlspecialchars($v) . '</a>';
			case 'url':
				return '<a href="' . htmlspecialchars($v) . '">' . htmlspecialchars($v) . '</a>';
			case 'country':
				return Utils::getCountryName($v);
			case 'date':
				return Utils::date_fr($v);


			case 'multiple':
				// Useful for search results, if a value is not a number
				if (!is_numeric($v)) {
					return htmlspecialchars($v);
				}

				$out = [];







|
>
>







561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
			case 'tel':
				return '<a href="tel:' . rawurlencode($v) . '">' . htmlspecialchars($v) . '</a>';
			case 'url':
				return '<a href="' . htmlspecialchars($v) . '">' . htmlspecialchars($v) . '</a>';
			case 'country':
				return Utils::getCountryName($v);
			case 'date':
				return Utils::date_fr($v, 'd/m/Y');
			case 'datetime':
				return Utils::date_fr($v, 'd/m/Y à H:i');
			case 'multiple':
				// Useful for search results, if a value is not a number
				if (!is_numeric($v)) {
					return htmlspecialchars($v);
				}

				$out = [];

Modified src/templates/admin/_head.tpl from [6d07a42fd7] to [fc1d3a04f1].

62
63
64
65
66
67
68

69

70

71
72
73

74
75
76
77
78
79
80
                </ul>
            {/if}
        </li>
        {if $session->canAccess($session::SECTION_USERS, $session::ACCESS_READ)}
            <li class="member list{if $current == 'membres'} current{elseif $current_parent == 'membres'} current_parent{/if}"><a href="{$admin_url}membres/"><b class="icn">👪</b><i> Membres</i></a>
            {if $session->canAccess($session::SECTION_USERS, $session::ACCESS_WRITE)}
            <ul>

                <li class="member new{if $current == 'membres/ajouter'} current{/if}"><a href="{$admin_url}membres/ajouter.php">Ajouter</a></li>

                <li class="{if $current == 'membres/services'} current{/if}"><a href="{$admin_url}services/">Activités &amp; cotisations</a></li>

                <li class="member message{if $current == 'membres/message'} current{/if}"><a href="{$admin_url}membres/message_collectif.php">Message collectif</a></li>
            </ul>
            {/if}

            </li>
        {/if}
        {if $session->canAccess($session::SECTION_ACCOUNTING, $session::ACCESS_READ)}
            <li class="{if $current == 'acc'} current{elseif $current_parent == 'acc'} current_parent{/if}"><a href="{$admin_url}acc/"><b>€</b><i> Comptabilité</i></a>
            <ul>
            {if $session->canAccess($session::SECTION_ACCOUNTING, $session::ACCESS_WRITE)}
                <li class="{if $current == 'acc/new'} current{/if}"><a href="{$admin_url}acc/transactions/new.php">Saisie</a></li>







>

>

>

<

>







62
63
64
65
66
67
68
69
70
71
72
73
74

75
76
77
78
79
80
81
82
83
                </ul>
            {/if}
        </li>
        {if $session->canAccess($session::SECTION_USERS, $session::ACCESS_READ)}
            <li class="member list{if $current == 'membres'} current{elseif $current_parent == 'membres'} current_parent{/if}"><a href="{$admin_url}membres/"><b class="icn">👪</b><i> Membres</i></a>
            {if $session->canAccess($session::SECTION_USERS, $session::ACCESS_WRITE)}
            <ul>
            {if $session->canAccess('membres', Membres::DROIT_ECRITURE)}
                <li class="member new{if $current == 'membres/ajouter'} current{/if}"><a href="{$admin_url}membres/ajouter.php">Ajouter</a></li>
            {/if}
                <li class="{if $current == 'membres/services'} current{/if}"><a href="{$admin_url}services/">Activités &amp; cotisations</a></li>
            {if $session->canAccess('membres', Membres::DROIT_ECRITURE)}
                <li class="member message{if $current == 'membres/message'} current{/if}"><a href="{$admin_url}membres/message_collectif.php">Message collectif</a></li>

            {/if}
            </ul>
            </li>
        {/if}
        {if $session->canAccess($session::SECTION_ACCOUNTING, $session::ACCESS_READ)}
            <li class="{if $current == 'acc'} current{elseif $current_parent == 'acc'} current_parent{/if}"><a href="{$admin_url}acc/"><b>€</b><i> Comptabilité</i></a>
            <ul>
            {if $session->canAccess($session::SECTION_ACCOUNTING, $session::ACCESS_WRITE)}
                <li class="{if $current == 'acc/new'} current{/if}"><a href="{$admin_url}acc/transactions/new.php">Saisie</a></li>

Modified src/templates/admin/login.tpl from [5f99a8a020] to [011baee06b].

50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

    <p class="submit">
        {csrf_field key="login"}
        {button type="submit" name="login" label="Se connecter" shape="right" class="main"}
    </p>

    <p class="help">
        <a href="{$admin_url}password.php">Pas de mot de passe ou mot de passe perdu ?</a>
    </p>

</form>

{literal}
<script type="text/javascript">
if (window.navigator.userAgent.match(/MSIE|Trident\/|Edge\//)) {







|







50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

    <p class="submit">
        {csrf_field key="login"}
        {button type="submit" name="login" label="Se connecter" shape="right" class="main"}
    </p>

    <p class="help">
        <a href="{$admin_url}password.php">Première connexion ou mot de passe perdu ?</a>
    </p>

</form>

{literal}
<script type="text/javascript">
if (window.navigator.userAgent.match(/MSIE|Trident\/|Edge\//)) {

Modified src/templates/admin/password.tpl from [c3884ccc30] to [6998bc730a].

1
2
3
4
5
6
7
8
..
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
{include file="admin/_head.tpl" title="Mot de passe oublié ou pas de mot de passe ?"}


{if !empty($sent)}
    <p class="block confirm">
        Un e-mail vous a été envoyé, cliquez sur le lien dans cet e-mail
        pour modifier votre mot de passe.
    </p>
................................................................................
    {form_errors}

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

        <fieldset>
            <legend>Recevoir un e-mail avec un nouveau mot de passe</legend>
            <p class="help">
                Inscrivez ici votre {$champ.title}.
                Nous vous enverrons un message vous indiquant un lien permettant de recevoir un
                nouveau mot de passe.
            </p>
            <dl>
                <dt><label for="f_id">{$champ.title}</label></dt>
                <dd><input type="text" name="id" id="f_id" value="{form_field name=id}" /></dd>
            </dl>
        </fieldset>

|







 







|
|
<







1
2
3
4
5
6
7
8
..
15
16
17
18
19
20
21
22
23

24
25
26
27
28
29
30
{include file="admin/_head.tpl" title="Mot de passe oublié ou première connexion ?"}


{if !empty($sent)}
    <p class="block confirm">
        Un e-mail vous a été envoyé, cliquez sur le lien dans cet e-mail
        pour modifier votre mot de passe.
    </p>
................................................................................
    {form_errors}

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

        <fieldset>
            <legend>Recevoir un e-mail avec un nouveau mot de passe</legend>
            <p class="help">
                Inscrivez ici votre identifiant.
                Nous vous enverrons un e-mail avec un lien vous permettant de créer ou changer le mot de passe.

            </p>
            <dl>
                <dt><label for="f_id">{$champ.title}</label></dt>
                <dd><input type="text" name="id" id="f_id" value="{form_field name=id}" /></dd>
            </dl>
        </fieldset>

Modified src/templates/common/search/advanced.tpl from [71b8bf21e4] to [e8576fd3d6].

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
..
45
46
47
48
49
50
51

52
53
54
55
56
57
58
?>

{form_errors}

<form method="post" action="{$action_url}" id="queryBuilderForm">
	<fieldset>
	{if $sql_query && !$sql_disabled}
		<legend>Schéma des tables SQL</legend>
		<pre class="sql_schema">{foreach from=$schema item="table"}{$table}<br />{/foreach}</pre>
		<dl>
			{input type="textarea" name="sql_query" cols="100" rows="7" required=1 label="Requête SQL" help="Si aucune limite n'est précisée, une limite de 100 résultats sera appliquée." default=$sql_query}
			{if $session->canAccess($session::SECTION_CONFIG, $session::ACCESS_ADMIN)}
				{input type="checkbox" name="unprotected" value=1 label="Autoriser l'accès à toutes les tables de la base de données" default=$is_unprotected}
				<dd class="help">Attention : en cochant cette case vous autorisez la requête à lire toutes les données de toutes les tables de la base de données&nbsp;!</dd>
			{/if}









		</dl>
		<p class="submit">
			{button type="submit" name="run" label="Exécuter" shape="search" class="main"}
			<input type="hidden" name="id" value="{$search.id}" />
			{if $search.id}
				{button name="save" value=1 type="submit" label="Enregistrer : %s"|args:$search.intitule|truncate:40:"…":true shape="upload"}

			{else}
				{button name="save" value=1 type="submit" label="Enregistrer cette recherche" shape="upload"}
			{/if}



		</p>
	{elseif !$sql_query}
		<legend>Rechercher</legend>
		<div class="queryBuilder" id="queryBuilder"></div>
		<p class="actions">
			<label>Trier par
				<select name="order">
................................................................................
		</p>
		<p class="submit">
			{button name="search" value=1 type="submit" label="Chercher" shape="search" id="send" class="main"}
			<input type="hidden" name="q" id="jsonQuery" />
			<input type="hidden" name="id" value="{$search.id}" />
			{if $search.id}
				{button name="save" value=1 type="submit" label="Enregistrer : %s"|args:$search.intitule|truncate:40:"…":true shape="upload"}

			{else}
				{button name="save" value=1 type="submit" label="Enregistrer cette recherche" shape="upload"}
			{/if}
			{if $is_admin}
				{button name="to_sql" value=1 type="submit" label="Recherche SQL" shape="edit"}
			{/if}
		</p>







|
<

|




>
>
>
>
>
>
>
>
>






>



>
>
>







 







>







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
..
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
?>

{form_errors}

<form method="post" action="{$action_url}" id="queryBuilderForm">
	<fieldset>
	{if $sql_query && !$sql_disabled}
		<legend>Recherche SQL</legend>

		<dl>
			{input type="textarea" name="sql_query" cols="100" rows="10" required=1 label="Requête SQL" help="Si aucune limite n'est précisée, une limite de 100 résultats sera appliquée." default=$sql_query}
			{if $session->canAccess($session::SECTION_CONFIG, $session::ACCESS_ADMIN)}
				{input type="checkbox" name="unprotected" value=1 label="Autoriser l'accès à toutes les tables de la base de données" default=$is_unprotected}
				<dd class="help">Attention : en cochant cette case vous autorisez la requête à lire toutes les données de toutes les tables de la base de données&nbsp;!</dd>
			{/if}

			{if !ENABLE_TECH_DETAILS && !$session->canAccess('config', Membres::DROIT_ADMIN)}
			<dd class="help">
				<details>
					<summary class="block help">Schéma SQL des tables</summary>
					<pre class="block help">{foreach from=$schema item="table"}{$table}<br />{/foreach}</pre>
				</details>
			</dd>
			{/if}
		</dl>
		<p class="submit">
			{button type="submit" name="run" label="Exécuter" shape="search" class="main"}
			<input type="hidden" name="id" value="{$search.id}" />
			{if $search.id}
				{button name="save" value=1 type="submit" label="Enregistrer : %s"|args:$search.intitule|truncate:40:"…":true shape="upload"}
				{button name="save_new" value=1 type="submit" label="Enregistrer nouvelle recherche" shape="plus"}
			{else}
				{button name="save" value=1 type="submit" label="Enregistrer cette recherche" shape="upload"}
			{/if}
			{if ENABLE_TECH_DETAILS && $session->canAccess('config', Membres::DROIT_ADMIN)}
				{linkbutton href="!config/advanced/sql.php" target="_blank" shape="menu" label="Voir le schéma SQL"}
			{/if}
		</p>
	{elseif !$sql_query}
		<legend>Rechercher</legend>
		<div class="queryBuilder" id="queryBuilder"></div>
		<p class="actions">
			<label>Trier par
				<select name="order">
................................................................................
		</p>
		<p class="submit">
			{button name="search" value=1 type="submit" label="Chercher" shape="search" id="send" class="main"}
			<input type="hidden" name="q" id="jsonQuery" />
			<input type="hidden" name="id" value="{$search.id}" />
			{if $search.id}
				{button name="save" value=1 type="submit" label="Enregistrer : %s"|args:$search.intitule|truncate:40:"…":true shape="upload"}
				{button name="save_new" value=1 type="submit" label="Enregistrer nouvelle recherche" shape="plus"}
			{else}
				{button name="save" value=1 type="submit" label="Enregistrer cette recherche" shape="upload"}
			{/if}
			{if $is_admin}
				{button name="to_sql" value=1 type="submit" label="Recherche SQL" shape="edit"}
			{/if}
		</p>

Modified src/templates/common/search/saved_searches.tpl from [6d4f5623b8] to [c5426cd9d9].

67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
			<tr>
				<th><a href="{$search_url}?id={$recherche.id}">{$recherche.intitule}</a></th>
				<td>{if $recherche.type == Recherche::TYPE_JSON}Avancée{else}SQL{/if}</td>
				<td>{if !$recherche.id_membre}Publique{else}Privée{/if}</td>
				<td class="actions">
					{linkbutton href="%s?id=%d"|args:$search_url,$recherche.id shape="search" label="Rechercher"}
					{if $recherche.id_membre || $session->canAccess($access_section, $session::ACCESS_ADMIN)}
						{linkbutton href="?duplicate=%d"|args:$recherche.id shape="export" label="Dupliquer"}
						{linkbutton href="?edit=%d"|args:$recherche.id shape="edit" label="Modifier"}
						{linkbutton href="?delete=%d"|args:$recherche.id shape="delete" label="Supprimer"}
					{/if}
				</td>
			</tr>
			{/foreach}
		</tbody>
	</table>
{/if}

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







<











67
68
69
70
71
72
73

74
75
76
77
78
79
80
81
82
83
84
			<tr>
				<th><a href="{$search_url}?id={$recherche.id}">{$recherche.intitule}</a></th>
				<td>{if $recherche.type == Recherche::TYPE_JSON}Avancée{else}SQL{/if}</td>
				<td>{if !$recherche.id_membre}Publique{else}Privée{/if}</td>
				<td class="actions">
					{linkbutton href="%s?id=%d"|args:$search_url,$recherche.id shape="search" label="Rechercher"}
					{if $recherche.id_membre || $session->canAccess($access_section, $session::ACCESS_ADMIN)}

						{linkbutton href="?edit=%d"|args:$recherche.id shape="edit" label="Modifier"}
						{linkbutton href="?delete=%d"|args:$recherche.id shape="delete" label="Supprimer"}
					{/if}
				</td>
			</tr>
			{/foreach}
		</tbody>
	</table>
{/if}

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

Modified src/templates/services/_nav.tpl from [aa92921191] to [daf9b6b9bf].

1
2
3

4

5
6
7
8
9
10
11
..
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<nav class="tabs">
	<ul>
		<li{if $current == 'index'} class="current"{/if}><a href="{$admin_url}services/">Activités et cotisations</a></li>

		<li{if $current == 'save'} class="current"{/if}><a href="{$admin_url}services/save.php">Inscrire à une activité</a></li>

		{if $session->canAccess($session::SECTION_USERS, $session::ACCESS_ADMIN)}
			<li{if $current == 'reminders'} class="current"{/if}><a href="{$admin_url}services/reminders/">Gestion des rappels automatiques</a></li>
		{/if}
	</ul>

	{if isset($current_service)}
	<ul class="sub">
................................................................................
				{$current_service.duration} jours
			{elseif $current_service.start_date}
				du {$current_service.start_date|date_short} au {$current_service.end_date|date_short}
			{else}
				ponctuelle
			{/if}
		</li>
		<li{if $service_page == 'index'} class="current"{/if}><a href="{$admin_url}services/fees/?id={$current_service.id}"><strong>Gestion des tarifs</strong></a></li>
		<li{if $service_page == 'paid'} class="current"{/if}><a href="{$admin_url}services/details.php?id={$current_service.id}">À jour et payés</a></li>
		<li{if $service_page == 'expired'} class="current"{/if}><a href="{$admin_url}services/details.php?id={$current_service.id}&amp;type=expired">Inscription expirée</a></li>
		<li{if $service_page == 'unpaid'} class="current"{/if}><a href="{$admin_url}services/details.php?id={$current_service.id}&amp;type=unpaid">En attente de règlement</a></li>
	</ul>
	{/if}

	{if isset($current_fee)}



>
|
>







 







|







1
2
3
4
5
6
7
8
9
10
11
12
13
..
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<nav class="tabs">
	<ul>
		<li{if $current == 'index'} class="current"{/if}><a href="{$admin_url}services/">Activités et cotisations</a></li>
		{if $session->canAccess('membres', Membres::DROIT_ECRITURE)}
			<li{if $current == 'save'} class="current"{/if}><a href="{$admin_url}services/save.php">Inscrire à une activité</a></li>
		{/if}
		{if $session->canAccess($session::SECTION_USERS, $session::ACCESS_ADMIN)}
			<li{if $current == 'reminders'} class="current"{/if}><a href="{$admin_url}services/reminders/">Gestion des rappels automatiques</a></li>
		{/if}
	</ul>

	{if isset($current_service)}
	<ul class="sub">
................................................................................
				{$current_service.duration} jours
			{elseif $current_service.start_date}
				du {$current_service.start_date|date_short} au {$current_service.end_date|date_short}
			{else}
				ponctuelle
			{/if}
		</li>
		<li{if $service_page == 'index'} class="current"{/if}><a href="{$admin_url}services/fees/?id={$current_service.id}"><strong>Tarifs</strong></a></li>
		<li{if $service_page == 'paid'} class="current"{/if}><a href="{$admin_url}services/details.php?id={$current_service.id}">À jour et payés</a></li>
		<li{if $service_page == 'expired'} class="current"{/if}><a href="{$admin_url}services/details.php?id={$current_service.id}&amp;type=expired">Inscription expirée</a></li>
		<li{if $service_page == 'unpaid'} class="current"{/if}><a href="{$admin_url}services/details.php?id={$current_service.id}&amp;type=unpaid">En attente de règlement</a></li>
	</ul>
	{/if}

	{if isset($current_fee)}

Modified src/templates/services/save.tpl from [f442aa1d24] to [9c93e683cc].

25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
..
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
...
127
128
129
130
131
132
133

134
135
136
137
138
139
140
...
144
145
146
147
148
149
150

151


152
153
154
155
156
157
158
...
166
167
168
169
170
171
172
173

174
















175
176
177
178
179
		<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-expiry=$service.expiry_date|date_short label=null}
				<label for="f_id_service_{$service.id}">
					<div>
						<h3>{$service.label}</h3>
						<p>
							{if $service.duration}
								{$service.duration} jours
							{elseif $service.start_date}
................................................................................
	</fieldset>

	<fieldset class="accounting">
		<legend>{input type="checkbox" name="create_payment" value=1 default=1 label="Enregistrer en comptabilité"}</legend>

		<dl>
			{input type="money" name="amount" label="Montant réglé par le membre" fake_required=1 help="En cas de règlement en plusieurs fois il sera possible d'ajouter des règlements via la page de suivi des activités de ce membre."}
			{input type="list" target="acc/charts/accounts/selector.php?targets=%s"|args:$account_targets name="account" label="Compte de règlement" required=1}
			{input type="text" name="reference" label="Numéro de pièce comptable" help="Numéro de facture, de note de frais, etc."}
			{input type="text" name="payment_reference" label="Référence de paiement" help="Numéro de chèque, numéro de transaction CB, etc."}
		</dl>
{/if}
	</fieldset>

	<p class="submit">
................................................................................
	$('[data-service]').forEach((e) => {
		e.style.display = ('s' + elm.value == e.getAttribute('data-service')) ? 'block' : 'none';
	});

	let expiry = $('#f_expiry_date');

	if (!first_load || !expiry.value) {

		expiry.value = elm.dataset.expiry;
	}

	var first = document.querySelector('[data-service="s' + elm.value + '"] input[name=id_fee]');

	if (first) {
		first.checked = true;
................................................................................

function selectFee(elm, first_load) {
	var amount = parseInt(elm.getAttribute('data-user-amount'), 10);

	// Toggle accounting part of the form
	var accounting = elm.getAttribute('data-account') ? true : false;
	g.toggle('.accounting', accounting);

	$('#f_amount').required = accounting;



	// Fill the amount paid by the user
	if (amount && !first_load) {
		$('#f_amount').value = g.formatMoney(amount);
	}
}

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

var selected = document.querySelector('input[name="id_service"]:checked') || document.querySelector('input[name="id_service"]');
selected.checked = true;

g.toggle('.accounting', false);
selectService(selected, true);

$('#f_create_payment_1').onchange = (e) => {

	g.toggle('.accounting dl', $('#f_create_payment_1').checked);
















};
</script>
{/literal}

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







|







 







|







 







>







 







>
|
>
>







 







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





25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
..
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
...
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
...
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
...
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
		<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}
				<label for="f_id_service_{$service.id}">
					<div>
						<h3>{$service.label}</h3>
						<p>
							{if $service.duration}
								{$service.duration} jours
							{elseif $service.start_date}
................................................................................
	</fieldset>

	<fieldset class="accounting">
		<legend>{input type="checkbox" name="create_payment" value=1 default=1 label="Enregistrer en comptabilité"}</legend>

		<dl>
			{input type="money" name="amount" label="Montant réglé par le membre" fake_required=1 help="En cas de règlement en plusieurs fois il sera possible d'ajouter des règlements via la page de suivi des activités de ce membre."}
			{input type="list" target="acc/charts/accounts/selector.php?targets=%s"|args:$account_targets name="account" label="Compte de règlement" fake_required=1}
			{input type="text" name="reference" label="Numéro de pièce comptable" help="Numéro de facture, de note de frais, etc."}
			{input type="text" name="payment_reference" label="Référence de paiement" help="Numéro de chèque, numéro de transaction CB, etc."}
		</dl>
{/if}
	</fieldset>

	<p class="submit">
................................................................................
	$('[data-service]').forEach((e) => {
		e.style.display = ('s' + elm.value == e.getAttribute('data-service')) ? 'block' : 'none';
	});

	let expiry = $('#f_expiry_date');

	if (!first_load || !expiry.value) {
		// Set the expiry date
		expiry.value = elm.dataset.expiry;
	}

	var first = document.querySelector('[data-service="s' + elm.value + '"] input[name=id_fee]');

	if (first) {
		first.checked = true;
................................................................................

function selectFee(elm, first_load) {
	var amount = parseInt(elm.getAttribute('data-user-amount'), 10);

	// Toggle accounting part of the form
	var accounting = elm.getAttribute('data-account') ? true : false;
	g.toggle('.accounting', accounting);

	if (accounting) {
		$('#f_create_payment_1').checked = true;
	}

	// Fill the amount paid by the user
	if (amount && !first_load) {
		$('#f_amount').value = g.formatMoney(amount);
	}
}

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

var selected = document.querySelector('input[name="id_service"]:checked') || document.querySelector('input[name="id_service"]');
selected.checked = true;

g.toggle('.accounting', false);
selectService(selected, true);

let checkbox = $('#f_create_payment_1');
checkbox.onchange = (e) => {
	g.toggle('.accounting dl', checkbox.checked);
	//$('#f_amount').required = checkbox.checked;
};

// Automatically increase expiry date when date is changed
let date_input = $('#f_date');
let expiry_input = $('#f_expiry_date');

date_input.onchange = (e) => {
	if (!selected.dataset.duration) {
		return;
	}

	let d = date_input.value.split('/').reverse();
	d = new Date(d[0], d[1]-1, d[2], 12);
	d.setDate(d.getDate() + parseInt(selected.dataset.duration, 10));
	expiry_input.value = d.toISOString().split('T')[0].split('-').reverse().join('/');
};
</script>
{/literal}

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

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

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
}

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

$recherche = new Recherche;
$mode = null;

if (qg('edit') || qg('delete') || qg('duplicate'))
{
	$r = $recherche->get(qg('edit') ?: (qg('delete') ?: qg('duplicate')));

	if (!$r)
	{
		throw new UserException('Recherche non trouvée');
	}

	if ($r->id_membre !== null && $r->id_membre != $user->id)
	{
		throw new UserException('Recherche privée appartenant à un autre membre.');
	}

	if (qg('duplicate')) {
		$recherche->duplicate($r->id);
		Utils::redirect(Utils::getSelfURI(false));
	}

	$tpl->assign('recherche', $r);

	$mode = qg('edit') ? 'edit' : 'delete';
}

if ($mode == 'edit' && f('save') && $form->check('edit_recherche_' . $r->id))
{







|

|











<
<
<
<
<







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
}

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

$recherche = new Recherche;
$mode = null;

if (qg('edit') || qg('delete'))
{
	$r = $recherche->get(qg('edit') ?: qg('delete'));

	if (!$r)
	{
		throw new UserException('Recherche non trouvée');
	}

	if ($r->id_membre !== null && $r->id_membre != $user->id)
	{
		throw new UserException('Recherche privée appartenant à un autre membre.');
	}






	$tpl->assign('recherche', $r);

	$mode = qg('edit') ? 'edit' : 'delete';
}

if ($mode == 'edit' && f('save') && $form->check('edit_recherche_' . $r->id))
{

Modified src/www/admin/common/search.php from [fa0d136bb7] to [f310a1310c].

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

if (null !== $result)
{
	if (count($result) == 1 && $text_query !== '' && $target === 'membres') {
		Utils::redirect(ADMIN_URL . 'membres/fiche.php?id=' . (int)$result[0]->_user_id);
	}

	if (f('save') && !$form->hasErrors())
	{
		if (!$sql_query) {
			$type = Recherche::TYPE_JSON;
		}
		elseif ($is_unprotected) {
			$type = Recherche::TYPE_SQL_UNPROTECTED;
		}
		else {
			$type = Recherche::TYPE_SQL;
		}

		if ($id) {
			$recherche->edit($id, [
				'type'    => $type,
				'contenu' => $sql_query ?: $query,
			]);
		}
		else
		{
			$label = $sql_query ? 'Recherche SQL du ' : 'Recherche avancée du ';
			$label .= date('d/m/Y à H:i:s');
			$id = $recherche->add($label, $user->id, $type, $target, $sql_query ?: $query);
		}

		$url = $target == 'compta' ? '/admin/acc/saved_searches.php?id=' : '/admin/membres/recherches.php?id=';
		Utils::redirect($url . $id);
	}

	$tpl->assign('result_header', $recherche->getResultHeader($target, $result));
}
elseif ($target === 'membres')
{







|











|












|







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

if (null !== $result)
{
	if (count($result) == 1 && $text_query !== '' && $target === 'membres') {
		Utils::redirect(ADMIN_URL . 'membres/fiche.php?id=' . (int)$result[0]->_user_id);
	}

	if ((f('save_new') || f('save')) && !$form->hasErrors())
	{
		if (!$sql_query) {
			$type = Recherche::TYPE_JSON;
		}
		elseif ($is_unprotected) {
			$type = Recherche::TYPE_SQL_UNPROTECTED;
		}
		else {
			$type = Recherche::TYPE_SQL;
		}

		if ($id && !f('save_new')) {
			$recherche->edit($id, [
				'type'    => $type,
				'contenu' => $sql_query ?: $query,
			]);
		}
		else
		{
			$label = $sql_query ? 'Recherche SQL du ' : 'Recherche avancée du ';
			$label .= date('d/m/Y à H:i:s');
			$id = $recherche->add($label, $user->id, $type, $target, $sql_query ?: $query);
		}

		$url = $target == 'compta' ? '!acc/saved_searches.php?edit=' : '!membres/recherches.php?edit=';
		Utils::redirect($url . $id);
	}

	$tpl->assign('result_header', $recherche->getResultHeader($target, $result));
}
elseif ($target === 'membres')
{

Modified src/www/admin/membres/message_collectif.php from [449f926a89] to [040e680ac0].

20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
        if ($match[1] == 'categorie')
        {
            $recipients = $membres->listAllByCategory($match[2], true);
        }
        else
        {
            try {
                $recipients = $recherche->search($match[2], ['id', 'email'], true);
            }
            catch (UserException $e) {
                $form->addError($e->getMessage());
            }
        }

        if (isset($recipients) && !count($recipients))







|







20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
        if ($match[1] == 'categorie')
        {
            $recipients = $membres->listAllByCategory($match[2], true);
        }
        else
        {
            try {
                $recipients = $recherche->search($match[2], ['membres.id', 'membres.email'], true);
            }
            catch (UserException $e) {
                $form->addError($e->getMessage());
            }
        }

        if (isset($recipients) && !count($recipients))

Modified src/www/admin/password.php from [51fae7e937] to [8dbf35c476].

14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
    }
    else
    {
        if (f('change') && $form->check('changePassword'))
        {
            try {
                $session->recoverPasswordChange(qg('c'), f('passe'), f('passe_confirmed'));
                Utils::redirect('/admin/login.php?changed');
            }
            catch (UserException $e) {
                $form->addError($e->getMessage());
            }
        }

        $tpl->assign('passphrase', Utils::suggestPassword());







|







14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
    }
    else
    {
        if (f('change') && $form->check('changePassword'))
        {
            try {
                $session->recoverPasswordChange(qg('c'), f('passe'), f('passe_confirmed'));
                Utils::redirect('!login.php?changed');
            }
            catch (UserException $e) {
                $form->addError($e->getMessage());
            }
        }

        $tpl->assign('passphrase', Utils::suggestPassword());

Modified src/www/admin/static/handheld.css from [82c8dcdda9] to [796936d9ab].

144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
}

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

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

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







|







144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
}

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

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

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

Modified src/www/admin/static/styles/02-common.css from [fb80f6862b] to [fed0918522].

337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
...
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422

aside.describe dl.describe dt {
    text-align: left;
    font-weight: bold;
    color: #000;
}

pre.sql_schema {
    float: right;
    color: #666;
    font-size: .9em;
    width: 30%;
    overflow: auto;
}

.hidden {
    display: none;
}

img.qrcode {
    float: right;
    padding: .5em;
................................................................................
    padding-right: 4em !important;
}

details summary.block::after {
    right: 0;
    left: inherit;
}

.files-list {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
}

.files-list aside.file {







<
<
<
<
<
<
<
<







 







<







337
338
339
340
341
342
343








344
345
346
347
348
349
350
...
400
401
402
403
404
405
406

407
408
409
410
411
412
413

aside.describe dl.describe dt {
    text-align: left;
    font-weight: bold;
    color: #000;
}









.hidden {
    display: none;
}

img.qrcode {
    float: right;
    padding: .5em;
................................................................................
    padding-right: 4em !important;
}

details summary.block::after {
    right: 0;
    left: inherit;
}

.files-list {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
}

.files-list aside.file {

Modified src/www/admin/static/styles/03-forms.css from [f5a97ac502] to [211fa0b520].

494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
    z-index: 200;
}

fieldset.memberMessage {
    max-width: 30em;
}

fieldset.memberMessage #f_sujet, fieldset.memberMessage #f_message, fieldset.memberMessage select {
    width: calc(100% - 2em);
}


#queryBuilder .column select, #queryBuilderForm .actions select {
    max-width: 15em;
}







|







494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
    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);
}


#queryBuilder .column select, #queryBuilderForm .actions select {
    max-width: 15em;
}