driver, ...] */ protected $actions = []; /** @var \Illuminate\Contracts\Session\Session */ protected $session; /** @var string */ protected $sessionKey = 'captcha'; /** @var \Application */ protected $app; /** * Create a new manager instance. * @param \Illuminate\Contracts\Container\Container $container * @return void */ public function __construct(Container $container) { parent::__construct($container); $this->app = $container; $this->session = $container['session.store']; } /** * Captcha routes * @param bool $register * @return array */ public function routes($register = false) { $routes = $this->app->filter('captcha.routes', [ 'captcha-image' => [ 'uri' => '/captcha/image/{action}', 'action' => function ($action) { if (! $this->enabled($action)) { return ''; } $captcha = $this->get($action); if ($captcha instanceof ImageCaptcha) { return $captcha->image(); } if ($captcha) { return $captcha->render(); } return ''; }, ], ]); if ($register) { $this->app->router()->addMany($routes); } return $routes; } /** * Set action captcha * @param string $action * @param string|null $driver * @return static */ public function set(string $action, ?string $driver) { $this->actions[$action] = $driver; return $this; } /** * Get action captcha * @param string $action * @return Captcha|null */ public function get(string $action) { try { $captcha = $this->driver($this->actions[$action] ?? $this->getDefaultDriver()); if ($captcha instanceof Captcha) { return $captcha->action($action); } } catch (\InvalidArgumentException $e) { # Unknown captcha driver return null; } return null; } /** * Get a driver instance * @param string|null $driver * @return mixed */ public function driver($driver = null) { /** * 0 - disabled * 1 - default */ if (is_numeric($driver)) { $driver = null; } return parent::driver($driver); } /** * Is action captcha enabled * @param string $action * @param bool|null $default * @return bool */ public function enabled(string $action, $default = null) { $enabled = Config::get('captcha.enabled.' . $action, $default ?? 1, TYPE_STR); if (! empty($enabled) && ! is_numeric($enabled)) { $this->set($action, $enabled); } return ! empty($enabled); } /** * Render captcha * @param string $action * @param string|array|\Closure|null $render * @param array $settings * @return Captcha|false */ public function view(string $action, $render = self::PLACEHOLDER, array $settings = []) { $captcha = $this->get($action); if (! $captcha || ! $captcha->enabled()) { return false; } $class = get_class($captcha); # settings $settings = $settings[$class] ?? reset($settings) ?? null; if ($settings) { $captcha->setSettings($settings); } # images (Math) if ($captcha instanceof ImageCaptcha) { return $captcha; } # others if (is_array($render)) { $render = $render[$class] ?? reset($render) ?? null; } if ($render instanceof Closure) { $render = $render($captcha); } if (is_string($render)) { if (mb_strpos($render, self::PLACEHOLDER) !== false) { echo strtr($render, [ self::PLACEHOLDER => $captcha->render(), ]); } else { echo $render; } } else { echo $captcha->render(); } return false; } /** * Is captcha valid * @param string $action * @param array $opts [ * string inputKey - value input key * \bff\http\Request request - current request * string value - value to validate (get from input if not set) * string|array|bool message - error message * bool expire - expire after validation * ] * @return bool */ public function valid(string $action, array $opts = []) { # skip disabled action captcha validation if (! $this->enabled($action)) { return true; } $captcha = $this->get($action); if (! $captcha) { return true; } # options $request = $opts['request'] ?? $this->app->request(); $inputKey = $opts['inputKey'] ?? 'captcha'; $message = $opts['message'] ?? _t('', 'The result from the image is incorrect'); $expire = $opts['expire'] ?? true; $value = $opts['value'] ?? $request->post($inputKey, TYPE_STR); # validate if (! $captcha->valid($value, $expire)) { # set error if ($this->app->errors()->no() && $message !== false) { if (is_array($message)) { $message = $message[get_class($captcha)] ?? reset($message); } $this->app->errors()->set($message, $inputKey); } return false; } return true; } /** * Store captcha value * @param string $action * @param string $value * @param int|mixed $expire */ public function storeInSession(string $action, string $value, $expire = null) { $session = $this->session->get($this->sessionKey, []); $session[$action] = [ 'value' => $value, 'expire' => $this->expireTime($expire), ]; $this->session->put($this->sessionKey, $session); } /** * Validate captcha value * @param string $action * @param mixed $value * @param bool $expire * @return bool */ public function validInSession(string $action, $value, bool $expire = true) { $session = $this->session->get($this->sessionKey, []); $valid = ( !empty($value) && !empty($session[$action]) && (intval($session[$action]['expire'] ?? 0) > time()) && ($value === ($session[$action]['value'] ?? '')) ); if ($expire) { $this->expireInSession($action); } return $valid; } /** * Expire captcha value in session * @param string $action */ public function expireInSession(string $action) { $session = $this->session->get($this->sessionKey, []); if (isset($session[$action])) { unset($session[$action]); $this->session->put($this->sessionKey, $session); } } /** * Unify expire datetime * @param mixed $expire * @return int */ public function expireTime($expire) { if ($expire instanceof DateTimeInterface) { $expire = $expire->format('U'); } elseif (is_string($expire)) { $expire = strtotime($expire); } if (! is_numeric($expire)) { $expire = strtotime($this->app->config('captcha.expire', '+20 minutes')); } return intval($expire); } /** * Create an instance of the Math Captcha. * @return \bff\captcha\Math */ public function createMathDriver() { return new Math(); } /** * Get the default captcha name. * @return string */ public function getDefaultDriver() { $default = 'math'; $config = $this->app->config('captcha.default', $default); if ($config != $default && ! isset($this->customCreators[$config])) { $config = $default; } return $config; } /** * Select default driver list options * @param array $options * @return array */ public function defaultDriverOptions(array $options = []) { $drivers = array_merge(['math'], array_keys($this->customCreators)); foreach ($drivers as $key) { /** @var Captcha $driver */ if ($driver = $this->driver($key)) { $options[$key] = [ 'id' => $key, 'title' => $driver->getTitle(), ]; } } return $options; } /** * Select action driver list options * @return array */ public function actionDriverOptions() { $options = $this->defaultDriverOptions([ # disabled 0 => ['id' => '0', 'value' => '0', 'title' => _t('', 'Disabled')], # default 1 => ['id' => '1', 'value' => '1', 'title' => ''], ]); $default = $this->getDefaultDriver(); $options[1]['title'] = _t('', 'Default ([title])', [ 'title' => $options[$default]['title'] ?? _t('', 'Default'), ]); return $options; } }