1 <?php
  2 
  3 /**
  4  * @author gharlan
  5  *
  6  * @package redaxo\core\login
  7  */
  8 class rex_password_policy
  9 {
 10     private $options;
 11 
 12     public function __construct(array $options)
 13     {
 14         $this->options = $options;
 15     }
 16 
 17     /**
 18      * @param string   $password
 19      * @param null|int $id
 20      *
 21      * @return bool|string `true` on success, otherwise an error message
 22      *
 23      * @throws rex_exception
 24      */
 25     public function check($password, $id = null)
 26     {
 27         if ($this->isValid($password)) {
 28             return true;
 29         }
 30 
 31         return rex_i18n::msg('password_invalid', $this->getRule());
 32     }
 33 
 34     protected function getRule()
 35     {
 36         $parts = [];
 37 
 38         foreach ($this->options as $key => $options) {
 39             if (isset($options['min'], $options['max'])) {
 40                 $constraint = rex_i18n::msg('password_rule_between', $options['min'], $options['max']);
 41             } elseif (isset($options['max'])) {
 42                 $constraint = rex_i18n::msg('password_rule_max', $options['max']);
 43             } else {
 44                 $constraint = rex_i18n::msg('password_rule_min', $options['min']);
 45             }
 46 
 47             $parts[] = rex_i18n::msg('password_rule_'.$key, $constraint);
 48         }
 49 
 50         return implode('; ', $parts);
 51     }
 52 
 53     protected function isValid($password)
 54     {
 55         foreach ($this->options as $key => $options) {
 56             switch ($key) {
 57                 case 'length':
 58                     $count = mb_strlen($password);
 59                     break;
 60                 case 'letter':
 61                     $count = preg_match_all('/[a-zA-Z]/', $password);
 62                     break;
 63                 case 'uppercase':
 64                     $count = preg_match_all('/[A-Z]/', $password);
 65                     break;
 66                 case 'lowercase':
 67                     $count = preg_match_all('/[a-z]/', $password);
 68                     break;
 69                 case 'digit':
 70                     $count = preg_match_all('/[0-9]/', $password);
 71                     break;
 72                 case 'symbol':
 73                     $count = preg_match_all('/[^a-zA-Z0-9]/', $password);
 74                     break;
 75 
 76                 default:
 77                     throw new rex_exception(sprintf('Unknown password_policy key "%s".', $key));
 78             }
 79 
 80             if (!$this->matchesCount($count, $options)) {
 81                 return false;
 82             }
 83         }
 84 
 85         return true;
 86     }
 87 
 88     protected function matchesCount($count, array $options)
 89     {
 90         if (isset($options['min']) && $count < $options['min']) {
 91             return false;
 92         }
 93 
 94         if (isset($options['max']) && $count > $options['max']) {
 95             return false;
 96         }
 97 
 98         return true;
 99     }
100 }
101