Copied!
<?php

namespace PHPFUI;

/**
 * A simple math problem captcha for low traffic sites. Often Google ReCAPTCHA has issues and lets spammers through (and they want to sell you Google ReCAPTCHA hacking service to deliver your message to millions of sites for not much money). This is not a sophisticated captcha involving a hard to solve image, but a simple addition to Google ReCAPTCHA for automated bots that are not wise to two captchas on a page.
 *
 * Works the same way as ReCAPTCHA, just a different contructor.  You really only need to pass the Page.
 *
 * Add kwn/number-to-words to your project to support words in addition to numbers to solve the CAPTCHA
 */
class MathCaptcha extends \PHPFUI\MultiColumn implements \PHPFUI\Interfaces\Captcha
	{
	/** @var array<string, string> */
	private array $operators = ['plus' => '+', 'minus' => '-', 'times' => '*'];

	public function __construct(private \PHPFUI\Interfaces\Page $page, private int $limit = 10, private string $fieldName = 'mathAnswer')
		{
		parent::__construct();
		}

	public function __toString() : string
		{
		$this->addClass('clearfix');

		$container = new \PHPFUI\Container();
		$answers = [];
		$answer = -999;

		for ($i = 0; $i < 10; ++$i)
			{
			$operator = \random_int(0, 2);
			$type = \random_int(0, 1);
			$first = \random_int(1, $this->limit);
			$second = \random_int(1, $this->limit);

			if ($first < $second)
				{
				$temp = $first;
				$first = $second;
				$second = $temp;
				}

			\reset($this->operators);

			for ($j = 0; $j < $operator; ++$j)
				{
				\next($this->operators);
				}

			switch (\current($this->operators))
				{
				case '+':
					$answer = $first + $second;

					break;

				case '-':
					$answer = $first - $second;

					break;

				case '*':
					$answer = $first * $second;

					break;
				}

			$op = $type ? \current($this->operators) : \key($this->operators);

			$bold = ['strong', 'b', ];
			$message = new \PHPFUI\HTML5Element($bold[\random_int(0, \count($bold) - 1)]);

			if (\class_exists(\NumberToWords\NumberToWords::class))
				{
				$numberToWords = new \NumberToWords\NumberToWords();
				$numberTransformer = $numberToWords->getNumberTransformer('en');

				if (\random_int(0, 1))
					{
					$first = $numberTransformer->toWords($first);
					}

				if (\random_int(0, 1))
					{
					$second = $numberTransformer->toWords($second);
					}
				}
			$message->add("Please Solve: {$first} {$op} {$second} = ");
			$message->addClass('float-right hide');
			$answers[$message->getId()] = $answer;
			$container->add((string)$message);
			}
		$this->add($container);

		$oneToShow = \random_int(0, 9);

		for ($i = 0; $i < $oneToShow; ++$i)
			{
			\next($answers);
			}
		$this->page->addJavaScript('$("#' . \key($answers) . '").toggleClass("hide")');
		\PHPFUI\Session::setFlash($this->fieldName, \current($answers));
		$answerInput = new \PHPFUI\Input\Number($this->fieldName);
		$this->add($answerInput);
		$this->add('&nbsp;');

		return parent::__toString();
		}

	public function isValid() : bool
		{
		if (\PHPFUI\Session::checkCSRF() && isset($_POST[$this->fieldName]))
			{

			return \strlen($_POST[$this->fieldName]) && $_POST[$this->fieldName] == \PHPFUI\Session::getFlash($this->fieldName);
			}

		return false;
		}
	}
© 2026 Bruce Wells
Search Namespaces \ Classes
Configuration