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