Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
68.06% |
49 / 72 |
|
42.86% |
3 / 7 |
CRAP | |
0.00% |
0 / 1 |
Captcha | |
68.06% |
49 / 72 |
|
42.86% |
3 / 7 |
74.93 | |
0.00% |
0 / 1 |
__construct | |
63.64% |
7 / 11 |
|
0.00% |
0 / 1 |
9.36 | |||
createNewToken | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
3 | |||
getToken | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setLabel | |
90.91% |
10 / 11 |
|
0.00% |
0 / 1 |
7.04 | |||
setValidator | |
45.83% |
11 / 24 |
|
0.00% |
0 / 1 |
25.89 | |||
generateEquation | |
61.54% |
8 / 13 |
|
0.00% |
0 / 1 |
8.05 | |||
evaluateEquation | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | /** |
3 | * Pop PHP Framework (http://www.popphp.org/) |
4 | * |
5 | * @link https://github.com/popphp/popphp-framework |
6 | * @author Nick Sagona, III <dev@nolainteractive.com> |
7 | * @copyright Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com) |
8 | * @license http://www.popphp.org/license New BSD License |
9 | */ |
10 | |
11 | /** |
12 | * @namespace |
13 | */ |
14 | namespace Pop\Form\Element\Input; |
15 | |
16 | /** |
17 | * Form CAPTCHA element class |
18 | * |
19 | * @category Pop |
20 | * @package Pop\Form |
21 | * @author Nick Sagona, III <dev@nolainteractive.com> |
22 | * @copyright Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com) |
23 | * @license http://www.popphp.org/license New BSD License |
24 | * @version 4.0.0 |
25 | */ |
26 | |
27 | class Captcha extends Text |
28 | { |
29 | |
30 | /** |
31 | * Current token data |
32 | * @var array |
33 | */ |
34 | protected array $token = []; |
35 | |
36 | /** |
37 | * Constructor |
38 | * |
39 | * Instantiate the captcha input form element |
40 | * |
41 | * @param string $name |
42 | * @param ?string $value |
43 | * @param ?string $captcha |
44 | * @param ?string $answer |
45 | * @param int $expire |
46 | * @param ?string $indent |
47 | */ |
48 | public function __construct( |
49 | string $name, ?string $value = null, ?string $captcha = null, ?string $answer = null, int $expire = 300, ?string $indent = null |
50 | ) |
51 | { |
52 | // Start a session. |
53 | if (session_id() == '') { |
54 | session_start(); |
55 | } |
56 | |
57 | // If token does not exist, create one |
58 | if (!isset($_SESSION['pop_captcha']) || (isset($_GET['captcha']) && ((int)$_GET['captcha'] == 1))) { |
59 | $this->createNewToken($captcha, $answer, $expire); |
60 | // Else, retrieve existing token |
61 | } else { |
62 | $this->token = unserialize($_SESSION['pop_captcha']); |
63 | |
64 | // Check to see if the token has expired |
65 | if ($this->token['expire'] > 0) { |
66 | if (($this->token['expire'] + $this->token['start']) < time()) { |
67 | $this->createNewToken($captcha, $value, $expire); |
68 | } |
69 | } |
70 | } |
71 | |
72 | parent::__construct($name, strtoupper((string)$value), $indent); |
73 | $this->setRequired(true); |
74 | $this->setValidator(); |
75 | } |
76 | |
77 | /** |
78 | * Set the token of the CAPTCHA form element |
79 | * |
80 | * @param ?string $captcha |
81 | * @param ?string $answer |
82 | * @param int $expire |
83 | * @return Captcha |
84 | */ |
85 | public function createNewToken(?string $captcha = null, ?string $answer = null, int $expire = 300): Captcha |
86 | { |
87 | if (($captcha === null) || ($answer === null)) { |
88 | $captcha = $this->generateEquation(); |
89 | $answer = $this->evaluateEquation($captcha); |
90 | } |
91 | |
92 | $this->token = [ |
93 | 'captcha' => $captcha, |
94 | 'answer' => $answer, |
95 | 'expire' => (int)$expire, |
96 | 'start' => time() |
97 | ]; |
98 | $_SESSION['pop_captcha'] = serialize($this->token); |
99 | return $this; |
100 | } |
101 | |
102 | /** |
103 | * Get token |
104 | * |
105 | * @return array |
106 | */ |
107 | public function getToken(): array |
108 | { |
109 | return $this->token; |
110 | } |
111 | |
112 | /** |
113 | * Set the label of the captcha form element |
114 | * |
115 | * @param string $label |
116 | * @return Captcha |
117 | */ |
118 | public function setLabel(string $label): Captcha |
119 | { |
120 | parent::setLabel($label); |
121 | |
122 | if (isset($this->token['captcha'])) { |
123 | if ((!str_contains($this->token['captcha'], '<img')) && |
124 | ((str_contains($this->token['captcha'], ' + ')) || |
125 | (str_contains($this->token['captcha'], ' - ')) || |
126 | (str_contains($this->token['captcha'], ' * ')) || |
127 | (str_contains($this->token['captcha'], ' / ')))) { |
128 | $this->label = $this->label . '(' . |
129 | str_replace([' * ', ' / '], [' × ', ' ÷ '], $this->token['captcha'] .')'); |
130 | } else { |
131 | $this->label = $this->label . $this->token['captcha']; |
132 | } |
133 | } |
134 | |
135 | return $this; |
136 | } |
137 | |
138 | /** |
139 | * Set the validator |
140 | * |
141 | * @throws Exception |
142 | * @return void |
143 | */ |
144 | protected function setValidator(): void |
145 | { |
146 | $this->validators = []; |
147 | |
148 | // Get query data |
149 | if (!isset($_SERVER['REQUEST_METHOD'])) { |
150 | throw new Exception('Error: The server request method is not set.'); |
151 | } |
152 | |
153 | $queryData = []; |
154 | switch ($_SERVER['REQUEST_METHOD']) { |
155 | case 'GET': |
156 | $queryData = $_GET; |
157 | break; |
158 | |
159 | case 'POST': |
160 | $queryData = $_POST; |
161 | break; |
162 | |
163 | default: |
164 | $input = fopen('php://input', 'r'); |
165 | $qData = null; |
166 | while ($data = fread($input, 1024)) { |
167 | $qData .= $data; |
168 | } |
169 | |
170 | parse_str($qData, $queryData); |
171 | } |
172 | |
173 | // If there is query data, set validator to check against the token value |
174 | if (count($queryData) > 0) { |
175 | if (isset($queryData[$this->name])) { |
176 | $this->addValidator(function($value){ |
177 | $token = $this->getToken(); |
178 | if (isset($token['answer']) && (strtoupper($token['answer']) == strtoupper($value))) { |
179 | return null; |
180 | } else { |
181 | return 'The answer is incorrect.'; |
182 | } |
183 | }); |
184 | } |
185 | } |
186 | } |
187 | |
188 | /** |
189 | * Randomly generate a simple, basic equation |
190 | * |
191 | * @return string |
192 | */ |
193 | protected function generateEquation(): string |
194 | { |
195 | $ops = [' + ', ' - ', ' * ', ' / ']; |
196 | $equation = null; |
197 | |
198 | $rand1 = rand(1, 10); |
199 | $rand2 = rand(1, 10); |
200 | $op = $ops[rand(0, 3)]; |
201 | |
202 | // If the operator is division, keep the equation very simple, with no remainder |
203 | if ($op == ' / ') { |
204 | $mod = ($rand2 > $rand1) ? $rand2 % $rand1 : $rand1 % $rand2; |
205 | while ($mod != 0) { |
206 | $rand1 = rand(1, 10); |
207 | $rand2 = rand(1, 10); |
208 | $mod = ($rand2 > $rand1) ? $rand2 % $rand1 : $rand1 % $rand2; |
209 | } |
210 | } |
211 | |
212 | $equation = ($rand2 > $rand1) ? $rand2 . $op . $rand1 : $rand1 . $op . $rand2; |
213 | |
214 | return $equation; |
215 | } |
216 | |
217 | /** |
218 | * Evaluate equation |
219 | * |
220 | * @param string $equation |
221 | * @return int |
222 | */ |
223 | protected function evaluateEquation(string $equation): int |
224 | { |
225 | return eval("return ($equation);"); |
226 | } |
227 | |
228 | } |