Overview
SHA1:7bfce6c5904ac711d29081136bf2559ab9c24c2e
Date: 2018-12-06 15:28:03
User: bohwaz
Comment:Merge de trunk vers dev
Timelines: family | ancestors | descendants | both | dev
Downloads: Tarball | ZIP archive
Other Links: files | file ages | folders | manifest
Tags And Properties
Context
2019-01-08
14:59
[ae518b948a] Merge trunk (user: bohwaz, tags: dev)
2018-12-06
15:28
[7bfce6c590] Merge de trunk vers dev (user: bohwaz, tags: dev)
2018-11-30
13:52
[ea902d98b2] Corrige mode plein écran éditeurs code / wiki (user: bohwaz, tags: trunk, stable)
2018-11-26
22:54
[6e79e54ca7] Merge avec trunk (user: bohwaz, tags: dev)
Changes

Modified src/include/lib/Garradin/Membres.php from [b2dcaceab5] to [747eeb941f].

     1      1   <?php
     2      2   
     3      3   namespace Garradin;
     4      4   
     5      5   use KD2\Security;
            6  +use KD2\SMTP;
     6      7   use Garradin\Membres\Session;
     7      8   
     8      9   class Membres
     9     10   {
    10     11       const DROIT_AUCUN = 0;
    11     12       const DROIT_ACCES = 1;
    12     13       const DROIT_ECRITURE = 2;
................................................................................
    93     94                           $data[$key] = 0;
    94     95                       }
    95     96                   }
    96     97                   elseif ($config->type == 'email')
    97     98                   {
    98     99                       $data[$key] = strtolower($data[$key]);
    99    100   
   100         -                    if (!SMTP::checkEmailIsValid($data[$key], false))
          101  +                    if (trim($data[$key]) !== '' && !SMTP::checkEmailIsValid($data[$key], false))
   101    102                       {
   102    103                           throw new UserException(sprintf('Adresse email invalide "%s" pour le champ "%s".', $data[$key], $config->title));
   103    104                       }
   104    105                   }
   105    106                   elseif ($config->type == 'select' && !in_array($data[$key], $config->options))
   106    107                   {
   107    108                       throw new UserException('Le champ "' . $config->title . '" ne correspond pas à un des choix proposés.');

Modified src/include/lib/Garradin/Membres/Session.php from [76f4ec5c54] to [6e72b311e7].

    18     18   
    19     19   class Session extends \KD2\UserSession
    20     20   {
    21     21   	// Personalisation de la config de UserSession
    22     22   	protected $cookie_name = 'gdin';
    23     23   	protected $remember_me_cookie_name = 'gdinp';
    24     24   	protected $remember_me_expiry = '+3 months';
           25  +
           26  +	const MINIMUM_PASSWORD_LENGTH = 8;
    25     27   
    26     28   	// Extension des méthodes de UserSession
    27     29   	public function __construct()
    28     30   	{
    29     31   		$url = parse_url(ADMIN_URL);
    30     32   
    31     33   		parent::__construct(DB::getInstance(), [
................................................................................
   154    156   			$secret = Security_OTP::getRandomSecret();
   155    157   		}
   156    158   
   157    159   		$out = [];
   158    160   		$out['secret'] = $secret;
   159    161   		$out['secret_display'] = implode(' ', str_split($secret, 4));
   160    162   		$out['url'] = Security_OTP::getOTPAuthURL(Config::getInstance()->get('nom_asso'), $secret);
   161         -	
          163  +
   162    164   		$qrcode = new QRCode($out['url']);
   163    165   		$out['qrcode'] = 'data:image/svg+xml;base64,' . base64_encode($qrcode->toSVG());
   164    166   
   165    167   		return $out;
   166    168   	}
   167    169   
   168         -	static public function recoverPasswordCheck($id)
          170  +	public function recoverPasswordSend($id)
   169    171   	{
   170    172   		$db = DB::getInstance();
   171    173   		$config = Config::getInstance();
   172    174   
   173    175   		$champ_id = $config->get('champ_identifiant');
   174    176   
   175    177   		$membre = $db->first('SELECT id, email, passe, clef_pgp FROM membres WHERE '.$champ_id.' = ? LIMIT 1;', trim($id));
................................................................................
   180    182   		}
   181    183   
   182    184   		// valide pour 1 heure minimum
   183    185   		$expire = ceil((time() - strtotime('2017-01-01')) / 3600) + 1;
   184    186   
   185    187   		$hash = hash_hmac('sha256', $membre->email . $membre->id . $membre->passe . $expire, SECRET_KEY, true);
   186    188   		$hash = substr(Security::base64_encode_url_safe($hash), 0, 16);
   187         -		
          189  +
   188    190   		$id = base_convert($membre->id, 10, 36);
   189    191   		$expire = base_convert($expire, 10, 36);
   190    192   
   191    193   		$query = sprintf('%s.%s.%s', $id, $expire, $hash);
   192    194   
   193    195   		$message = "Bonjour,\n\nVous avez oublié votre mot de passe ? Pas de panique !\n\n";
   194    196   		$message.= "Il vous suffit de cliquer sur le lien ci-dessous pour recevoir un nouveau mot de passe.\n\n";
   195    197   		$message.= ADMIN_URL . 'password.php?c=' . $query;
   196    198   		$message.= "\n\nSi vous n'avez pas demandé à recevoir ce message, ignorez-le, votre mot de passe restera inchangé.";
   197    199   
   198    200   		return Utils::sendEmail(Utils::EMAIL_CONTEXT_SYSTEM, $membre->email, 'Mot de passe perdu ?', $message, $membre->id, $membre->clef_pgp);
   199    201   	}
   200    202   
   201         -	static public function recoverPasswordConfirm($code)
          203  +	public function recoverPasswordCheck($code, &$membre = null)
   202    204   	{
   203    205   		if (substr_count($code, '.') !== 2)
   204    206   		{
   205    207   			return false;
   206    208   		}
   207    209   
   208    210   		list($id, $expire, $email_hash) = explode('.', $code);
................................................................................
   231    233   		$hash = substr(Security::base64_encode_url_safe($hash), 0, 16);
   232    234   
   233    235   		if (!hash_equals($hash, $email_hash))
   234    236   		{
   235    237   			return false;
   236    238   		}
   237    239   
   238         -		$password = Utils::suggestPassword();
          240  +		return true;
          241  +	}
   239    242   
   240         -		$message = "Bonjour,\n\nVous avez demandé un nouveau mot de passe pour votre compte.\n\n";
   241         -		$message.= "Votre adresse email : ".$membre->email."\n";
   242         -		$message.= "Votre nouveau mot de passe : ".$password."\n\n";
   243         -		$message.= "Si vous n'avez pas demandé à recevoir ce message, merci de nous le signaler.";
          243  +	public function recoverPasswordChange($code, $password, $password_confirm)
          244  +	{
          245  +		if (!$this->recoverPasswordCheck($code, $membre))
          246  +		{
          247  +			throw new UserException('Le code permettant de changer le mot de passe a expiré. Merci de bien vouloir recommencer la procédure.');
          248  +		}
          249  +
          250  +		$password = trim($password);
          251  +		$password_confirm = trim($password_confirm);
          252  +
          253  +		if (!hash_equals($password, $password_confirm))
          254  +		{
          255  +			throw new UserException('Le mot de passe et sa vérification ne sont pas identiques.');
          256  +		}
          257  +
          258  +		if (strlen($password) < self::MINIMUM_PASSWORD_LENGTH)
          259  +		{
          260  +			throw new UserException(sprintf('Le mot de passe doit faire au moins %d caractères.', self::MINIMUM_PASSWORD_LENGTH));
          261  +		}
   244    262   
   245    263   		$password = Membres::hashPassword($password);
   246    264   
   247         -		$db->update('membres', ['passe' => $password], 'id = :id', ['id' => (int)$id]);
          265  +		$message = "Bonjour,\n\nLe mot de passe de votre compte a bien été modifié.\n\n";
          266  +		$message.= "Votre adresse email : ".$membre->email."\n";
          267  +		$message.= "La demande émanait de l'adresse IP : ".Utils::getIP()."\n\n";
          268  +		$message.= "Si vous n'avez pas demandé à changer votre mot de passe, merci de nous le signaler.";
   248    269   
   249         -		return Utils::sendEmail(Utils::EMAIL_CONTEXT_SYSTEM, $membre->email, 'Nouveau mot de passe', $message, $membre->id, $membre->clef_pgp);
          270  +		DB::getInstance()->update('membres', ['passe' => $password], 'id = :id', ['id' => (int)$membre->id]);
          271  +
          272  +		return Utils::sendEmail(Utils::EMAIL_CONTEXT_SYSTEM, $membre->email, 'Mot de passe changé', $message, $membre->id, $membre->clef_pgp);
   250    273   	}
   251    274   
   252    275   	public function editUser($data)
   253    276   	{
   254    277   		(new Membres)->edit($this->user->id, $data, false);
   255    278   		$this->refresh(false);
   256    279   
................................................................................
   277    300   
   278    301   	public function getNewOTPSecret()
   279    302   	{
   280    303   		$out = [];
   281    304   		$out['secret'] = Security_OTP::getRandomSecret();
   282    305   		$out['secret_display'] = implode(' ', str_split($out['secret'], 4));
   283    306   		$out['url'] = Security_OTP::getOTPAuthURL(Config::getInstance()->get('nom_asso'), $out['secret']);
   284         -	
          307  +
   285    308   		$qrcode = new QRCode($out['url']);
   286    309   		$out['qrcode'] = 'data:image/svg+xml;base64,' . base64_encode($qrcode->toSVG());
   287    310   
   288    311   		return $out;
   289    312   	}
   290    313   
   291    314   	public function sendMessage($dest, $sujet, $message, $copie = false)
................................................................................
   315    338   			{
   316    339   				throw new \RuntimeException(sprintf('Le champ %s n\'est pas autorisé dans cette méthode.', $key));
   317    340   			}
   318    341   		}
   319    342   
   320    343   		if (isset($data['passe']) && trim($data['passe']) !== '')
   321    344   		{
   322         -			if (strlen($data['passe']) < 5)
          345  +			if (strlen($data['passe']) < self::MINIMUM_PASSWORD_LENGTH)
   323    346   			{
   324         -				throw new UserException('Le mot de passe doit faire au moins 5 caractères.');
          347  +				throw new UserException(sprintf('Le mot de passe doit faire au moins %d caractères.', self::MINIMUM_PASSWORD_LENGTH));
   325    348   			}
   326    349   
   327    350   			$data['passe'] = Membres::hashPassword(trim($data['passe']));
   328    351   		}
   329    352   		else
   330    353   		{
   331    354   			unset($data['passe']);

Modified src/include/lib/Garradin/Template.php from [808469b185] to [682f3b4c7a].

     1      1   <?php
     2      2   
     3      3   namespace Garradin;
     4      4   
     5      5   use KD2\Form;
            6  +use Garradin\Membres\Session;
     6      7   
     7      8   class Template extends \KD2\Smartyer
     8      9   {
     9     10   	static protected $_instance = null;
    10     11   
    11     12   	static public function getInstance()
    12     13   	{
................................................................................
    39     40   		$this->assign('version_hash', substr(sha1(VERSION . ROOT . SECRET_KEY), 0, 10));
    40     41   
    41     42   		$this->assign('www_url', WWW_URL);
    42     43   		$this->assign('self_url', Utils::getSelfUrl());
    43     44   		$this->assign('self_url_no_qs', Utils::getSelfUrl(false));
    44     45   
    45     46   		$this->assign('is_logged', false);
           47  +
           48  +		$this->assign('password_pattern', sprintf('.{%d,}', Session::MINIMUM_PASSWORD_LENGTH));
           49  +		$this->assign('password_length', Session::MINIMUM_PASSWORD_LENGTH);
    46     50   
    47     51   		$this->register_compile_function('continue', function ($pos, $block, $name, $raw_args) {
    48     52   			if ($block == 'continue')
    49     53   			{
    50     54   				return 'continue;';
    51     55   			}
    52     56   		});
................................................................................
   143    147   		{
   144    148   			return '';
   145    149   		}
   146    150   
   147    151   		return '<p class="error">' . $this->escape($params['message']) . '</p>';
   148    152   	}
   149    153   
   150         -	protected function formField(array $params)
          154  +	protected function formField(array $params, $escape = true)
   151    155   	{
   152    156   		if (!isset($params['name']))
   153    157   		{
   154    158   			throw new \BadFunctionCallException('name argument is mandatory');
   155    159   		}
   156    160   
   157    161   		$name = $params['name'];
................................................................................
   187    191   		{
   188    192   			if ($value == $params['selected'])
   189    193   				return ' selected="selected" ';
   190    194   
   191    195   			return '';
   192    196   		}
   193    197   
   194         -		return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
          198  +		return $escape ? htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8') : $value;
   195    199   	}
   196    200   
   197    201   	protected function formatPhoneNumber($n)
   198    202   	{
   199    203   		$n = preg_replace('![^\d\+]!', '', $n);
   200    204   
   201    205   		if (substr($n, 0, 1) == '+')
................................................................................
   310    314   		}
   311    315   		elseif ($type == 'date')
   312    316   		{
   313    317   			$params['pattern'] = '\d{4}-\d{2}-\d{2}';
   314    318   		}
   315    319   
   316    320   		$field = '';
   317         -		$value = $this->formField($params);
          321  +		$value = $this->formField($params, false);
   318    322   		$attributes = 'name="' . htmlspecialchars($params['name'], ENT_QUOTES, 'UTF-8') . '" ';
   319    323   		$attributes .= 'id="f_' . htmlspecialchars($params['name'], ENT_QUOTES, 'UTF-8') . '" ';
   320    324   
   321    325   		if ($params['name'] == 'numero' && $config->type == 'number' && !$value)
   322    326   		{
   323    327   			$value = DB::getInstance()->firstColumn('SELECT MAX(numero) + 1 FROM membres;');
   324    328   		}
................................................................................
   384    388   					. htmlspecialchars($params['name'], ENT_QUOTES, 'UTF-8') . '[' . (int)$k . ']" value="1" '
   385    389   					. (($value & $b) ? 'checked="checked"' : '') . ' ' . $attributes . '/> ' 
   386    390   					. htmlspecialchars($v, ENT_QUOTES, 'UTF-8') . '</label><br />';
   387    391   			}
   388    392   		}
   389    393   		elseif ($type == 'textarea')
   390    394   		{
   391         -			$field .= '<textarea ' . $attributes . 'cols="30" rows="5">' . $value . '</textarea>';
          395  +			$field .= '<textarea ' . $attributes . 'cols="30" rows="5">' . htmlspecialchars($value, ENT_QUOTES) . '</textarea>';
   392    396   		}
   393    397   		else
   394    398   		{
   395    399   			if ($type == 'checkbox')
   396    400   			{
   397    401   				if (!empty($value))
   398    402   				{
   399    403   					$attributes .= 'checked="checked" ';
   400    404   				}
   401    405   
   402    406   				$value = '1';
   403    407   			}
   404    408   
   405         -			$field .= '<input type="' . $type . '" ' . $attributes . ' value="' . $value . '" />';
          409  +			$field .= '<input type="' . $type . '" ' . $attributes . ' value="' . htmlspecialchars($value, ENT_QUOTES) . '" />';
   406    410   		}
   407    411   
   408    412   		$out = '
   409    413   		<dt>';
   410    414   
   411    415   		if ($type == 'checkbox')
   412    416   		{

Modified src/templates/admin/install.tpl from [164de6ecf1] to [2088579102].

    31     31               <dt><label for="f_nom_membre">Nom et prénom</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
    32     32               <dd><input type="text" name="nom_membre" id="f_nom_membre" required="required" value="{form_field name=nom_membre}" /></dd>
    33     33               <dt><label for="f_cat_membre">Catégorie du membre</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
    34     34               <dd class="tip">Par exemple : bureau, conseil d'administration, présidente, trésorier, etc.</dd>
    35     35               <dd><input type="text" name="cat_membre" id="f_cat_membre" required="required" value="{form_field name=cat_membre}" /></dd>
    36     36               <dt><label for="f_email_membre">Adresse E-Mail</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
    37     37               <dd><input type="email" name="email_membre" id="f_email_membre" required="required" value="{form_field name=email_membre}" /></dd>
    38         -            <dt><label for="f_passe_membre">Mot de passe</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
           38  +            <dt><label for="f_passe_membre">Mot de passe</label> (minimum {$password_length} caractères) <b title="(Champ obligatoire)">obligatoire</b></dt>
    39     39               <dd class="help">
    40     40                   Astuce : un mot de passe de quatre mots choisis au hasard dans le dictionnaire est plus sûr 
    41     41                   et plus simple à retenir qu'un mot de passe composé de 10 lettres et chiffres.
    42     42               </dd>
    43     43               <dd class="help">
    44     44                   Pas d'idée&nbsp;? Voici une suggestion choisie au hasard :
    45     45                   <input type="text" readonly="readonly" title="Cliquer pour utiliser cette suggestion comme mot de passe" id="pw_suggest" value="{$passphrase}" autocomplete="off" />
    46     46               </dd>
    47         -            <dd><input type="password" name="passe" id="f_passe_membre" value="{form_field name=passe}" pattern=".{ldelim}6,{rdelim}" required="required" /></dd>
           47  +            <dd><input type="password" name="passe" id="f_passe_membre" value="{form_field name=passe}" pattern="{$password_pattern}" required="required" /></dd>
    48     48               <dt><label for="f_repasse_membre">Encore le mot de passe</label> (vérification) <b title="(Champ obligatoire)">obligatoire</b></dt>
    49         -            <dd><input type="password" name="passe_confirmed" id="f_repasse_membre" value="{form_field name=passe_confirmed}" pattern=".{ldelim}6,{rdelim}" required="required" /></dd>
           49  +            <dd><input type="password" name="passe_confirmed" id="f_repasse_membre" value="{form_field name=passe_confirmed}" pattern="{$password_pattern}" required="required" /></dd>
    50     50           </dl>
    51     51       </fieldset>
    52     52   
    53     53       <p class="submit">
    54     54           {csrf_field key="install"}
    55     55           <input type="submit" id="f_submit" name="save" value="Terminer l'installation &rarr;" />
    56     56       </p>

Modified src/templates/admin/login.tpl from [ae198371c7] to [51528da26b].

     1      1   {include file="admin/_head.tpl" title="Connexion" js=1}
     2      2   
     3      3   {form_errors}
     4      4   {show_error if=$fail message="Connexion impossible. Vérifiez l'adresse e-mail et le mot de passe."}
            5  +
            6  +{if $changed}
            7  +    <p class="confirm">
            8  +        Votre mot de passe a bien été modifié.<br />
            9  +        Vous pouvez maintenant l'utiliser pour vous reconnecter.
           10  +    </p>
           11  +{/if}
     5     12   
     6     13   {if !$ssl_enabled && $prefer_ssl}
     7     14       <p class="alert">
     8     15           <strong>Message de sécurité</strong><br />
     9     16           Nous vous conseillons de vous connecter sur la version <a href="{$own_https_url}">chiffrée (HTTPS) de cette page</a>
    10     17           pour vous connecter.
    11     18       </p>

Modified src/templates/admin/membres/ajouter.tpl from [a19929b9a3] to [e23f736695].

    12     12               {/foreach}
    13     13           </dl>
    14     14       </fieldset>
    15     15   
    16     16       <fieldset>
    17     17           <legend>Connexion</legend>
    18     18           <dl>
    19         -            <dt><label for="f_passe">Mot de passe</label>{if $champs.passe.mandatory} <b title="(Champ obligatoire)">obligatoire</b>{/if}</dt>
           19  +            <dt><label for="f_passe">Mot de passe</label> (minimum {$password_length} caractères) {if $champs.passe.mandatory} <b title="(Champ obligatoire)">obligatoire</b>{/if}</dt>
    20     20               <dd class="help">
    21     21                   Astuce : un mot de passe de quatre mots choisis au hasard dans le dictionnaire est plus sûr 
    22     22                   et plus simple à retenir qu'un mot de passe composé de 10 lettres et chiffres.
    23     23               </dd>
    24     24               <dd class="help">
    25     25                   Pas d'idée&nbsp;? Voici une suggestion choisie au hasard :
    26     26                   <input type="text" readonly="readonly" title="Cliquer pour utiliser cette suggestion comme mot de passe" id="pw_suggest" value="{$passphrase}" autocomplete="off" />
    27     27               </dd>
    28         -            <dd><input type="password" name="passe" id="f_passe" value="{form_field name=passe}" pattern=".{ldelim}6,{rdelim}" /></dd>
           28  +            <dd><input type="password" name="passe" id="f_passe" value="{form_field name=passe}" pattern="{$password_pattern}" /></dd>
    29     29               <dt><label for="f_repasse">Encore le mot de passe</label> (vérification)</dt>
    30         -            <dd><input type="password" name="passe_confirmed" id="f_repasse" value="{form_field name=passe_confirmed}" pattern=".{ldelim}6,{rdelim}" /></dd>
           30  +            <dd><input type="password" name="passe_confirmed" id="f_repasse" value="{form_field name=passe_confirmed}" pattern="{$password_pattern}" /></dd>
    31     31           </dl>
    32     32       </fieldset>
    33     33   
    34     34       {if $session->canAccess('membres', Membres::DROIT_ADMIN)}
    35     35       <fieldset>
    36     36           <legend>Général</legend>
    37     37           <dl>

Modified src/templates/admin/membres/modifier.tpl from [fad59c5b3b] to [02f4db1009].

    26     26           <legend>{if $membre.passe}Changer le mot de passe{else}Choisir un mot de passe{/if}</legend>
    27     27           <dl>
    28     28           {if $membre.passe}
    29     29               <dd>Ce membre a déjà un mot de passe, mais vous pouvez le changer si besoin.</dd>
    30     30           {else}
    31     31               <dd>Ce membre n'a pas encore de mot de passe et ne peut donc se connecter.</dd>
    32     32           {/if}
    33         -            <dt><label for="f_passe">Nouveau mot de passe</label>{if $champs.passe.mandatory} <b title="(Champ obligatoire)">obligatoire</b>{/if}</dt>
           33  +            <dt><label for="f_passe">Nouveau mot de passe</label> (minimum {$password_length} caractères) {if $champs.passe.mandatory} <b title="(Champ obligatoire)">obligatoire</b>{/if}</dt>
    34     34               <dd class="help">
    35     35                   Astuce : un mot de passe de quatre mots choisis au hasard dans le dictionnaire est plus sûr 
    36     36                   et plus simple à retenir qu'un mot de passe composé de 10 lettres et chiffres.
    37     37               </dd>
    38     38               <dd class="help">
    39     39                   Pas d'idée&nbsp;? Voici une suggestion choisie au hasard :
    40     40                   <input type="text" readonly="readonly" title="Cliquer pour utiliser cette suggestion comme mot de passe" id="pw_suggest" value="{$passphrase}" autocomplete="off" />
    41     41               </dd>
    42         -            <dd><input type="password" name="passe" id="f_passe" value="{form_field name=passe}" pattern=".{ldelim}6,{rdelim}" /></dd>
           42  +            <dd><input type="password" name="passe" id="f_passe" value="{form_field name=passe}" pattern="{$password_pattern}" /></dd>
    43     43               <dt><label for="f_repasse">Encore le mot de passe</label> (vérification){if $champs.passe.mandatory} <b title="(Champ obligatoire)">obligatoire</b>{/if}</dt>
    44         -            <dd><input type="password" name="passe_confirmed" id="f_repasse" value="{form_field name=passe_confirmed}" pattern=".{ldelim}6,{rdelim}" /></dd>
           44  +            <dd><input type="password" name="passe_confirmed" id="f_repasse" value="{form_field name=passe_confirmed}" pattern="{$password_pattern}" /></dd>
    45     45           </dl>
    46     46       </fieldset>
    47     47   
    48     48       {if $session->canAccess('membres', Membres::DROIT_ADMIN) && $user.id != $membre.id}
    49     49       <fieldset>
    50     50           <legend>Général</legend>
    51     51           <dl>

Modified src/templates/admin/mes_infos_securite.tpl from [811d2ba17a] to [ce884953c2].

    68     68           <fieldset>
    69     69               <legend>Changer mon mot de passe</legend>
    70     70               {if $user.droit_membres < Membres::DROIT_ADMIN && (!empty($champs.passe.private) || empty($champs.passe.editable))}
    71     71                   <p class="help">Vous devez contacter un administrateur pour changer votre mot de passe.</p>
    72     72               {else}
    73     73                   <dl>
    74     74                       <dd>Vous avez déjà un mot de passe, ne remplissez les champs suivants que si vous souhaitez en changer.</dd>
    75         -                    <dt><label for="f_passe">Nouveau mot de passe</label></dt>
           75  +                    <dt><label for="f_passe">Nouveau mot de passe</label> (minimum {$password_length} caractères)</dt>
    76     76                       <dd class="help">
    77     77                           Astuce : un mot de passe de quatre mots choisis au hasard dans le dictionnaire est plus sûr 
    78     78                           et plus simple à retenir qu'un mot de passe composé de 10 lettres et chiffres.
    79     79                       </dd>
    80     80                       <dd class="help">
    81     81                           Pas d'idée&nbsp;? Voici une suggestion choisie au hasard :
    82     82                           <input type="text" readonly="readonly" title="Cliquer pour utiliser cette suggestion comme mot de passe" id="pw_suggest" value="{$passphrase}" autocomplete="off" />
    83     83                       </dd>
    84         -                    <dd><input type="password" name="passe" id="f_passe" value="{form_field name=passe}" pattern=".{ldelim}6,{rdelim}" /></dd>
           84  +                    <dd><input type="password" name="passe" id="f_passe" value="{form_field name=passe}" pattern="{$password_pattern}" /></dd>
    85     85                       <dt><label for="f_repasse">Encore le mot de passe</label> (vérification)</dt>
    86         -                    <dd><input type="password" name="passe_confirmed" id="f_passe_confirmed" value="{form_field name=passe_confirmed}" pattern=".{ldelim}6,{rdelim}" /></dd>
           86  +                    <dd><input type="password" name="passe_confirmed" id="f_passe_confirmed" value="{form_field name=passe_confirmed}" pattern="{$password_pattern}" /></dd>
    87     87                   </dl>
    88     88               {/if}
    89     89           </fieldset>
    90     90   
    91     91           <fieldset>
    92     92               <legend>Authentification à double facteur (2FA)</legend>
    93     93               <p class="help">Pour renforcer la sécurité de votre connexion en cas de vol de votre mot de passe, vous pouvez activer

Modified src/templates/admin/password.tpl from [e4ce7c137d] to [d9cc01423e].

     1      1   {include file="admin/_head.tpl" title="Mot de passe oublié ou pas de mot de passe ?"}
     2      2   
     3      3   
     4      4   {if !empty($sent)}
     5      5       <p class="confirm">
     6      6           Un e-mail vous a été envoyé, cliquez sur le lien dans cet e-mail
     7         -        pour recevoir un nouveau mot de passe.
            7  +        pour modifier votre mot de passe.
     8      8       </p>
     9      9       <p class="alert">
    10     10           Si le message n'apparaît pas dans les prochaines minutes, vérifiez le dossier Spam ou Indésirables.
    11     11       </p>
    12         -{elseif !empty($new_sent)}
    13         -    <p class="confirm">
    14         -        <strong>Un e-mail contenant votre nouveau mot de passe vous a été envoyé.</strong>
    15         -        Si le message n'apparaît pas dans les prochaines minutes, vérifiez le dossier Spam ou Indésirables.
    16         -    </p>
    17         -    <p><a href="{$admin_url}login.php">Connexion &rarr;</a></p>
           12  +
    18     13   {else}
    19     14   
    20     15       {form_errors}
    21     16   
    22     17       <form method="post" action="{$self_url_no_qs}">
    23     18   
    24     19           <fieldset>

Added src/templates/admin/password_change.tpl version [bfd4bc96b0].

            1  +{include file="admin/_head.tpl" title="Changement de mot de passe" js=1}
            2  +
            3  +
            4  +{form_errors}
            5  +
            6  +<form method="post" action="{$self_url}">
            7  +
            8  +	<fieldset>
            9  +		<legend>Modifier mon mot de passe</legend>
           10  +		<dl>
           11  +			<dt><label for="f_passe_membre">Mot de passe</label> (minimum {$password_length} caractères) <b title="(Champ obligatoire)">obligatoire</b></dt>
           12  +			<dd class="help">
           13  +				Astuce : un mot de passe de quatre mots choisis au hasard dans le dictionnaire est plus sûr 
           14  +				et plus simple à retenir qu'un mot de passe composé de 10 lettres et chiffres.
           15  +			</dd>
           16  +			<dd class="help">
           17  +				Pas d'idée&nbsp;? Voici une suggestion choisie au hasard :
           18  +				<input type="text" readonly="readonly" title="Cliquer pour utiliser cette suggestion comme mot de passe" id="pw_suggest" value="{$passphrase}" autocomplete="off" />
           19  +			</dd>
           20  +			<dd><input type="password" name="passe" id="f_passe_membre" value="{form_field name=passe}" pattern="{$password_pattern}" required="required" /></dd>
           21  +			<dt><label for="f_repasse_membre">Encore le mot de passe</label> (vérification) <b title="(Champ obligatoire)">obligatoire</b></dt>
           22  +			<dd><input type="password" name="passe_confirmed" id="f_repasse_membre" value="{form_field name=passe_confirmed}" pattern="{$password_pattern}" required="required" /></dd>
           23  +		</dl>
           24  +	</fieldset>
           25  +
           26  +	<p class="submit">
           27  +		{csrf_field key="changePassword"}
           28  +		<input type="submit" name="change" value="Modifier le mot de passe &rarr;" />
           29  +	</p>
           30  +
           31  +
           32  +	<script type="text/javascript">
           33  +	{literal}
           34  +	g.script('scripts/password.js').onload = function () {
           35  +		initPasswordField('pw_suggest', 'f_passe_membre', 'f_repasse_membre');
           36  +	};
           37  +	{/literal}
           38  +	</script>
           39  +</form>
           40  +
           41  +
           42  +{include file="admin/_foot.tpl"}

Modified src/www/.htaccess from [8c789141ef] to [da372037a9].

     1      1   Options -MultiViews -Indexes
            2  +DirectoryIndex disabled
     2      3   DirectoryIndex index.php
     3      4   
     4      5   # FallbackResource n'est dispo que depuis Apache 2.2.16, soit Debian Wheezy (2013)
            6  +# Mais bugue avant Apache 2.4.15, il faut donc bien désactiver le DirectoryIndex
            7  +# cf. https://bz.apache.org/bugzilla/show_bug.cgi?id=58292
            8  +# et https://serverfault.com/questions/559067/apache-hangs-for-five-seconds-with-fallbackresource-when-accessing
     5      9   <IfModule mod_version.c>
     6     10   	<IfVersion >= 2.2.16>
     7     11   		FallbackResource /_route.php
     8     12   	</IfVersion>
     9     13   </IfModule>
    10     14   
    11     15   ErrorDocument 404 /_route.php

Modified src/www/admin/login.php from [5bb3d0cb12] to [3ef470efd1].

    50     50   
    51     51   $tpl->assign('ssl_enabled', empty($_SERVER['HTTPS']) ? false : true);
    52     52   $tpl->assign('prefer_ssl', (bool)PREFER_HTTPS);
    53     53   $tpl->assign('own_https_url', str_replace('http://', 'https://', utils::getSelfURL()));
    54     54   
    55     55   $tpl->assign('champ', $champ);
    56     56   $tpl->assign('fail', $login === false);
           57  +$tpl->assign('changed', qg('changed') !== null);
    57     58   
    58     59   $tpl->display('admin/login.tpl');

Modified src/www/admin/password.php from [3ac4dae618] to [51fae7e937].

     4      4   
     5      5   const LOGIN_PROCESS = true;
     6      6   
     7      7   require_once __DIR__ . '/_inc.php';
     8      8   
     9      9   if (trim(qg('c')))
    10     10   {
    11         -    if ($session->recoverPasswordConfirm(qg('c')))
           11  +    if (!$session->recoverPasswordCheck(qg('c')))
    12     12       {
    13         -        Utils::redirect(ADMIN_URL . 'password.php?new_sent');
           13  +        $form->addError('Le lien que vous avez suivi est invalide ou a expiré.');
    14     14       }
           15  +    else
           16  +    {
           17  +        if (f('change') && $form->check('changePassword'))
           18  +        {
           19  +            try {
           20  +                $session->recoverPasswordChange(qg('c'), f('passe'), f('passe_confirmed'));
           21  +                Utils::redirect('/admin/login.php?changed');
           22  +            }
           23  +            catch (UserException $e) {
           24  +                $form->addError($e->getMessage());
           25  +            }
           26  +        }
    15     27   
    16         -    $form->addError('Le lien que vous avez suivi est invalide ou a expiré.');
           28  +        $tpl->assign('passphrase', Utils::suggestPassword());
           29  +        $tpl->display('admin/password_change.tpl');
           30  +        exit;
           31  +    }
    17     32   }
    18     33   elseif (f('recover'))
    19     34   {
    20     35       $form->check('recoverPassword', [
    21     36           'id' => 'required'
    22     37       ]);
    23     38   
    24     39       if (!$form->hasErrors())
    25     40       {
    26         -        if (trim(f('id')) && $session->recoverPasswordCheck(f('id')))
           41  +        if (trim(f('id')) && $session->recoverPasswordSend(f('id')))
    27     42           {
    28     43               Utils::redirect(ADMIN_URL . 'password.php?sent');
    29     44           }
    30     45   
    31     46           $form->addError('Ce membre n\'a pas d\'adresse email enregistrée ou n\'a pas le droit de se connecter.');
    32     47       }
    33     48   }
    34     49   
    35     50   if (!$form->hasErrors() && null !== qg('sent'))
    36     51   {
    37     52       $tpl->assign('sent', true);
    38     53   }
    39         -elseif (!$form->hasErrors() && null !== qg('new_sent'))
    40         -{
    41         -    $tpl->assign('new_sent', true);
    42         -}
    43         -
    44     54   
    45     55   $champs = $config->get('champs_membres');
    46     56   
    47     57   $champ = $champs->get($config->get('champ_identifiant'));
    48     58   
    49     59   $tpl->assign('champ', $champ);
    50     60   
    51     61   $tpl->display('admin/password.tpl');

Modified src/www/admin/static/admin.css from [2dd14f5041] to [3930ef3cf0].

   627    627       max-width: 15em;
   628    628   }
   629    629   
   630    630   #queryBuilder table td {
   631    631       vertical-align: top;
   632    632       padding: .1em .2em;
   633    633   }
          634  +
          635  +#queryBuilder table td.buttons {
          636  +    white-space: nowrap;
          637  +}
   634    638   
   635    639   #queryBuilder input[type=button], #queryBuilder .values input {
   636    640       margin: .1em;
   637    641   }
   638    642   
   639    643   #queryBuilderForm .actions label {
   640    644       margin: 0 .5em;
   641    645   }
          646  +
          647  +#queryBuilder table .values label {
          648  +    margin: 0 .3em;
          649  +}
   642    650   
   643    651   #queryBuilderForm input[type=number] {
   644    652       width: 4em;
   645    653   }
   646    654   
   647    655   .userOrder .cur {
   648    656       background: rgb(217, 134, 40);

Modified src/www/admin/static/scripts/query_builder.min.js from [6daac3a8d1] to [652d101dbf].

     1         -!function(){var e=function(e){this.columns=e};(window.SQLQueryBuilder=e).prototype.loadDefaultOperators=function(){for(var e in this.operators={"= ?":this.__("is equal to"),"!= ?":this.__("is not equal to"),"IN (??)":this.__("is equal to one of"),"NOT IN (??)":this.__("is not equal to one of"),"> ?":this.__("is greater than"),">= ?":this.__("is greater than or equal to"),"< ?":this.__("is less than"),"<= ?":this.__("is less than or equal to"),"BETWEEN ? AND ?":this.__("is between"),"NOT BETWEEN ? AND ?":this.__("is not between"),"IS NULL":this.__("is null"),"IS NOT NULL":this.__("is not null"),"LIKE ?%":this.__("begins with"),"NOT LIKE ?%":this.__("doesn't begin with"),"LIKE %?":this.__("ends with"),"NOT LIKE %?":this.__("doesn't end with"),"LIKE %?%":this.__("contains"),"NOT LIKE %?%":this.__("doesn't contain"),"&":this.__("matches one of"),"= 1":this.__("is true"),"= 0":this.__("is false")},this.types_operators={integer:["= ?","!= ?","IN (??)","NOT IN (??)","> ?",">= ?","< ?","<= ?","BETWEEN ? AND ?","NOT BETWEEN ? AND ?"],enum:["= ?","!= ?","IN (??)","NOT IN (??)"],boolean:["= 1","= 0"],text:["= ?","!= ?","IN (??)","NOT IN (??)","LIKE ?%","NOT LIKE ?%","LIKE %?","NOT LIKE %?","LIKE %?%","NOT LIKE %?%"],bitwise:["&"]},this.types_operators){var t={};for(var i in this.types_operators[e]){var o=this.types_operators[e][i];t[o]=this.operators[o]}this.types_operators[e]=t}this.types_operators.date=JSON.parse(JSON.stringify(this.types_operators.integer)),delete this.types_operators.date["<= ?"],delete this.types_operators.date[">= ?"],this.types_operators.date["< ?"]=this.__("before"),this.types_operators.date["> ?"]=this.__("after"),this.types_operators.datetime=this.types_operators.date},e.prototype.__=function(e){return e},e.prototype.init=function(e){this.parent=e;var t={"":"---"};for(column in this.columns)t[column]=this.columns[column].label;this.columnSelect=this.buildSelect(t)},e.prototype.addGroup=function(t,e){var i=document.createElement("fieldset"),o=document.createElement("legend"),n=this.buildSelect({AND:this.__("Matches ALL of the following conditions:"),OR:this.__("Matches ANY of the following conditions:"),ADD:this.__("Add a new set of conditions below this one"),DEL:this.__("Remove this set of conditions")});n.onfocus=function(){this.oldValue=this.value},n.value=e;var r=this;n.onchange=function(){if("DEL"==this.value){if(1==t.childNodes.length)return void(this.value=this.oldValue);t.removeChild(i)}else if("ADD"==this.value){var e=r.addGroup(t,"AND");r.addRow(e),this.value=this.oldValue}},o.appendChild(n),i.appendChild(o);var s=document.createElement("table");return i.appendChild(s),t.appendChild(i),i},e.prototype.addRow=function(e,t){var i=e.getElementsByTagName("table")[0],o=document.createElement("tr");(s=document.createElement("td")).className="buttons";var n,r=this;(n=this.buildInput("button","+")).onclick=function(){r.addRow(function(e,t){for(;(e=e.parentElement)&&!(e.matches||e.matchesSelector).call(e,t););return e}(this,"fieldset"),this.parentNode.parentNode)},s.appendChild(n),(n=this.buildInput("button","-")).onclick=function(){r.deleteRow(this.parentNode.parentNode)},s.appendChild(n),o.appendChild(s),(s=document.createElement("td")).className="column";var s,a=this.columnSelect.cloneNode(!0);return a.onchange=function(){return r.switchColumn(this)},s.appendChild(a),o.appendChild(s),(s=document.createElement("td")).className="operator",o.appendChild(s),(s=document.createElement("td")).className="values",o.appendChild(s),void 0===t?i.appendChild(o):i.insertBefore(o,t.nextSibling),o},e.prototype.deleteRow=function(e){e.parentNode.childNodes.length<=1||e.parentNode.removeChild(e)},e.prototype.switchColumn=function(e){var t=e.parentNode.parentNode;t.childNodes[2].innerHTML="",t.childNodes[3].innerHTML="",this.addOperator(t,this.columns[e.value])},e.prototype.addOperator=function(e,t){var i=this.types_operators[t.type],o={"":"---"};for(var n in t.null&&(i["IS NULL"]=this.operators["IS NULL"],i["IS NOT NULL"]=this.operators["IS NOT NULL"]),i)o[n]=i[n];var r=this.buildSelect(o),s=this;return r.onchange=function(){return s.switchOperator(this)},e.childNodes[2].appendChild(r),r},e.prototype.switchOperator=function(e,t){var i=e.parentNode.parentNode;i.childNodes[3].innerHTML="";var o=i.childNodes[3],n=i.childNodes[1].firstChild,r=e.value,s=this.columns[n.value];if(r){var a=1,l=!1,d=null,h=r.match(/\?/g);if(h){r.match(/\?\?/)?(a=t?t.length:3,l=!0):1<h.length&&(a=h.length);for(var p=0;p<a;p++)d=this.addMatchField(o,d,s,r),t&&(d.value=t[p]);if(l){(u=this.buildInput("button","-")).onclick=function(){this.parentNode.childNodes.length<=3||(this.parentNode.removeChild(this.previousSibling),this.parentNode.removeChild(this.previousSibling))},o.appendChild(u);var u=this.buildInput("button","+"),c=this;u.onclick=function(){c.addMatchField(o,this.previousSibling.previousSibling,s,r)},o.appendChild(u)}}}},e.prototype.addMatchField=function(e,t,i,o){if("enum"==i.type)var n=this.buildSelect(i.values);else if("bitwise"==i.type){n=document.createElement("span");for(var r in i.values){var s=this.buildInput("checkbox",r),a=document.createElement("label");a.appendChild(s),a.appendChild(document.createTextNode(" "+i.values[r])),n.appendChild(a.cloneNode(!0))}}else n=this.buildInput(i.type,"",i);return n=e.insertBefore(n,t?t.nextSibling:null),t&&e.insertBefore(document.createElement("br"),n),n},e.prototype.buildInput=function(e,t,i){var o=document.createElement("input");return o.type="integer"==e?"number":e,o.value=t,o},e.prototype.buildSelect=function(e){var t=document.createElement("select");for(var i in e){var o=document.createElement("option");o.value=i,o.innerHTML=e[i],t.appendChild(o)}return t},e.prototype.import=function(e){for(var t in e)if(0!=e[t].conditions.length){var i=this.addGroup(this.parent,e[t].operator);for(var o in e[t].conditions){var n=e[t].conditions[o],r=this.addRow(i);r.childNodes[1].firstChild.value=n.column;var s=this.addOperator(r,this.columns[n.column]);s.value=n.operator,this.switchOperator(s,n.values)}}},e.prototype.export=function(){var e=this.parent.querySelectorAll("table"),t=[];for(var i in e)if(e.hasOwnProperty(i)){for(var o=(i=e[i]).rows,n=[],r=0;r<o.length;r++){var s=o[r];if(s.getElementsByTagName("select")[0].value){var a=Array.prototype.slice.call(s.cells[3].querySelectorAll("input, select")).map(function(e){if("button"!=e.type)return e.value}),l={column:s.cells[1].firstChild.value,operator:s.cells[2].firstChild.value,values:a};l.operator&&(l.operator.match(/\?\?/)&&(l.values=l.values.slice(0,-2)),n.push(l))}}t.push({operator:i.parentNode.firstChild.firstChild.value,conditions:n})}return t}}();
            1  +!function(){var e=function(e){this.columns=e};(window.SQLQueryBuilder=e).prototype.loadDefaultOperators=function(){for(var e in this.operators={"= ?":this.__("is equal to"),"!= ?":this.__("is not equal to"),"IN (??)":this.__("is equal to one of"),"NOT IN (??)":this.__("is not equal to one of"),"> ?":this.__("is greater than"),">= ?":this.__("is greater than or equal to"),"< ?":this.__("is less than"),"<= ?":this.__("is less than or equal to"),"BETWEEN ? AND ?":this.__("is between"),"NOT BETWEEN ? AND ?":this.__("is not between"),"IS NULL":this.__("is null"),"IS NOT NULL":this.__("is not null"),"LIKE ?%":this.__("begins with"),"NOT LIKE ?%":this.__("doesn't begin with"),"LIKE %?":this.__("ends with"),"NOT LIKE %?":this.__("doesn't end with"),"LIKE %?%":this.__("contains"),"NOT LIKE %?%":this.__("doesn't contain"),"&":this.__("matches one of"),"= 1":this.__("is true"),"= 0":this.__("is false")},this.types_operators={integer:["= ?","!= ?","IN (??)","NOT IN (??)","> ?",">= ?","< ?","<= ?","BETWEEN ? AND ?","NOT BETWEEN ? AND ?"],enum:["= ?","!= ?","IN (??)","NOT IN (??)"],boolean:["= 1","= 0"],text:["= ?","!= ?","IN (??)","NOT IN (??)","LIKE ?%","NOT LIKE ?%","LIKE %?","NOT LIKE %?","LIKE %?%","NOT LIKE %?%"],bitwise:["&"]},this.types_operators){var t={};for(var i in this.types_operators[e]){var o=this.types_operators[e][i];t[o]=this.operators[o]}this.types_operators[e]=t}this.types_operators.date=JSON.parse(JSON.stringify(this.types_operators.integer)),delete this.types_operators.date["<= ?"],delete this.types_operators.date[">= ?"],this.types_operators.date["< ?"]=this.__("before"),this.types_operators.date["> ?"]=this.__("after"),this.types_operators.datetime=this.types_operators.date},e.prototype.__=function(e){return e},e.prototype.init=function(e){this.parent=e;var t={"":"---"};for(column in this.columns)t[column]=this.columns[column].label;this.columnSelect=this.buildSelect(t)},e.prototype.addGroup=function(t,e){var i=document.createElement("fieldset"),o=document.createElement("legend"),n=this.buildSelect({AND:this.__("Matches ALL of the following conditions:"),OR:this.__("Matches ANY of the following conditions:"),ADD:this.__("Add a new set of conditions below this one"),DEL:this.__("Remove this set of conditions")});n.onfocus=function(){this.oldValue=this.value},n.value=e;var r=this;n.onchange=function(){if("DEL"==this.value){if(1==t.childNodes.length)return void(this.value=this.oldValue);t.removeChild(i)}else if("ADD"==this.value){var e=r.addGroup(t,"AND");r.addRow(e),this.value=this.oldValue}},o.appendChild(n),i.appendChild(o);var s=document.createElement("table");return i.appendChild(s),t.appendChild(i),i},e.prototype.addRow=function(e,t){var i=e.getElementsByTagName("table")[0],o=document.createElement("tr");(s=document.createElement("td")).className="buttons";var n,r=this;(n=this.buildInput("button","+")).onclick=function(){r.addRow(function(e,t){for(;(e=e.parentElement)&&!(e.matches||e.matchesSelector).call(e,t););return e}(this,"fieldset"),this.parentNode.parentNode)},s.appendChild(n),(n=this.buildInput("button","-")).onclick=function(){r.deleteRow(this.parentNode.parentNode)},s.appendChild(n),o.appendChild(s),(s=document.createElement("td")).className="column";var s,a=this.columnSelect.cloneNode(!0);return a.onchange=function(){return r.switchColumn(this)},s.appendChild(a),o.appendChild(s),(s=document.createElement("td")).className="operator",o.appendChild(s),(s=document.createElement("td")).className="values",o.appendChild(s),void 0===t?i.appendChild(o):i.insertBefore(o,t.nextSibling),o},e.prototype.deleteRow=function(e){e.parentNode.childNodes.length<=1||e.parentNode.removeChild(e)},e.prototype.switchColumn=function(e){var t=e.parentNode.parentNode;if(t.childNodes[2].innerHTML="",t.childNodes[3].innerHTML="",e.value){var i=this.addOperator(t,this.columns[e.value]);i.value=i.children[1].value,this.switchOperator(i,null)}},e.prototype.addOperator=function(e,t){var i=this.types_operators[t.type],o={"":"---"};for(var n in t.null&&(i["IS NULL"]=this.operators["IS NULL"],i["IS NOT NULL"]=this.operators["IS NOT NULL"]),i)o[n]=i[n];var r=this.buildSelect(o),s=this;return r.onchange=function(){return s.switchOperator(this)},e.childNodes[2].appendChild(r),r},e.prototype.switchOperator=function(e,t){var i=e.parentNode.parentNode;i.childNodes[3].innerHTML="";var o=i.childNodes[3],n=i.childNodes[1].firstChild,r=e.value,s=this.columns[n.value];if(r){var a=1,l=!1,d=null,h=r.match(/\?/g);if(h&&r.match(/\?\?/))a=t?t.length:3,l=!0;else if(h&&1<=h.length)a=h.length;else{if("bitwise"!=s.type||"&"!=r)return;a=1}for(var u=0;u<a;u++)if(d=this.addMatchField(o,d,s,r),"bitwise"==s.type&&t)for(var p=0;p<s.values.length;p++)o.querySelectorAll("input")[p].checked=-1!=t.indexOf(p.toString());else t&&(d.value=t[u]);if(l){(c=this.buildInput("button","-")).onclick=function(){this.parentNode.childNodes.length<=3||(this.parentNode.removeChild(this.previousSibling),this.parentNode.removeChild(this.previousSibling))},o.appendChild(c);var c=this.buildInput("button","+"),v=this;c.onclick=function(){v.addMatchField(o,this.previousSibling.previousSibling,s,r)},o.appendChild(c)}}},e.prototype.addMatchField=function(e,t,i,o){if("enum"==i.type)var n=this.buildSelect(i.values);else if("bitwise"==i.type){n=document.createElement("span");for(var r in i.values){var s=this.buildInput("checkbox",r),a=document.createElement("label");a.appendChild(s),a.appendChild(document.createTextNode(" "+i.values[r])),n.appendChild(a.cloneNode(!0))}}else n=this.buildInput(i.type,"",i);return n=e.insertBefore(n,t?t.nextSibling:null),t&&e.insertBefore(document.createElement("br"),n),n},e.prototype.buildInput=function(e,t,i){var o=document.createElement("input");return o.type="integer"==e?"number":e,o.value=t,o},e.prototype.buildSelect=function(e){var t=document.createElement("select");for(var i in e){var o=document.createElement("option");o.value=i,o.innerHTML=e[i],t.appendChild(o)}return t},e.prototype.import=function(e){for(var t in e)if(0!=e[t].conditions.length){var i=this.addGroup(this.parent,e[t].operator);for(var o in e[t].conditions){var n=e[t].conditions[o],r=this.addRow(i);r.childNodes[1].firstChild.value=n.column;var s=this.addOperator(r,this.columns[n.column]);s.value=n.operator,this.switchOperator(s,n.values)}}},e.prototype.export=function(){var e=this.parent.querySelectorAll("table"),t=[];for(var i in e)if(e.hasOwnProperty(i)){for(var o=(i=e[i]).rows,n=[],r=0;r<o.length;r++){var s=o[r];if(s.getElementsByTagName("select")[0].value){var a=Array.prototype.slice.call(s.cells[3].querySelectorAll("input, select")).map(function(e){return"checkbox"==e.type?e.checked?e.value:null:"button"!=e.type?e.value:void 0});a=a.filter(function(e){return null!==e});var l={column:s.cells[1].firstChild.value,operator:s.cells[2].firstChild.value,values:a};l.operator&&(l.operator.match(/\?\?/)&&(l.values=l.values.slice(0,-2)),n.push(l))}}t.push({operator:i.parentNode.firstChild.firstChild.value,conditions:n})}return t}}();

Modified src/www/admin/static/scripts/skel_editor.css from [9d1b561b6e] to [f204b32def].

   130    130   	position: fixed;
   131    131   	top: 0;
   132    132   	left: 0;
   133    133   	right: 0;
   134    134   	bottom: 0;
   135    135   	width: 100%;
   136    136   	height: 100%;
          137  +	z-index: 100000;
   137    138   }

Modified src/www/admin/static/scripts/wiki_editor.css from [e0a0321c5b] to [d04c8ee367].

    99     99       left: 0;
   100    100       right: 0;
   101    101       bottom: 0;
   102    102       width: 98%;
   103    103       height: 98%;
   104    104       padding: 1%;
   105    105       border-radius: 0;
          106  +    z-index: 100000;
   106    107   }
   107    108   
   108    109   .textEditor.fullscreen textarea {
   109    110       height: 90%;
   110    111   }
   111    112   
   112    113   .textEditor.iframe textarea {