Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
14.48% |
65 / 449 |
|
16.22% |
6 / 37 |
CRAP | |
0.00% |
0 / 1 |
qa | |
14.48% |
65 / 449 |
|
16.22% |
6 / 37 |
7958.77 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
init | |
0.00% |
0 / 35 |
|
0.00% |
0 / 1 |
110 | |||
is_installed | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
is_available | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
6 | |||
has_config | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
get_name | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
get_service_name | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
set_name | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
execute_demo | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
get_template | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
20 | |||
get_demo_template | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
12 | |||
get_hidden_fields | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
garbage_collect | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
20 | |||
uninstall | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
install | |
100.00% |
44 / 44 |
|
100.00% |
1 / 1 |
3 | |||
validate | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
30 | |||
select_question | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
6 | |||
reselect_question | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
6 | |||
new_attempt | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
2 | |||
load_confirm_id | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
6 | |||
load_answer | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
20 | |||
check_answer | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
30 | |||
get_attempt_count | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
reset | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
is_solved | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
acp_page | |
0.00% |
0 / 70 |
|
0.00% |
0 / 1 |
462 | |||
acp_question_list | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
6 | |||
acp_get_question_data | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
20 | |||
acp_get_question_input | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
2 | |||
acp_update_question | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
2 | |||
acp_add_question | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
2 | |||
acp_insert_answers | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
6 | |||
acp_delete_question | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 | |||
validate_input | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
90 | |||
get_languages | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
6 | |||
acp_is_last | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | /** |
3 | * |
4 | * This file is part of the phpBB Forum Software package. |
5 | * |
6 | * @copyright (c) phpBB Limited <https://www.phpbb.com> |
7 | * @license GNU General Public License, version 2 (GPL-2.0) |
8 | * |
9 | * For full copyright and license information, please see |
10 | * the docs/CREDITS.txt file. |
11 | * |
12 | */ |
13 | |
14 | namespace phpbb\captcha\plugins; |
15 | |
16 | /** |
17 | * And now to something completely different. Let's make a captcha without extending the abstract class. |
18 | * QA CAPTCHA sample implementation |
19 | */ |
20 | class qa |
21 | { |
22 | var $confirm_id; |
23 | var $answer; |
24 | var $question_ids = []; |
25 | var $question_text; |
26 | var $question_lang; |
27 | var $question_strict; |
28 | var $attempts = 0; |
29 | var $type; |
30 | // dirty trick: 0 is false, but can still encode that the captcha is not yet validated |
31 | var $solved = 0; |
32 | |
33 | protected $table_captcha_questions; |
34 | protected $table_captcha_answers; |
35 | protected $table_qa_confirm; |
36 | |
37 | /** |
38 | * @var string name of the service. |
39 | */ |
40 | protected $service_name; |
41 | |
42 | /** @var int Question ID */ |
43 | protected $question = -1; |
44 | |
45 | /** |
46 | * Constructor |
47 | * |
48 | * @param string $table_captcha_questions |
49 | * @param string $table_captcha_answers |
50 | * @param string $table_qa_confirm |
51 | */ |
52 | function __construct($table_captcha_questions, $table_captcha_answers, $table_qa_confirm) |
53 | { |
54 | $this->table_captcha_questions = $table_captcha_questions; |
55 | $this->table_captcha_answers = $table_captcha_answers; |
56 | $this->table_qa_confirm = $table_qa_confirm; |
57 | } |
58 | |
59 | /** |
60 | * @param int $type as per the CAPTCHA API docs, the type |
61 | */ |
62 | function init($type) |
63 | { |
64 | global $config, $db, $user, $request; |
65 | |
66 | // load our language file |
67 | $user->add_lang('captcha_qa'); |
68 | |
69 | // read input |
70 | $this->confirm_id = $request->variable('qa_confirm_id', ''); |
71 | $this->answer = $request->variable('qa_answer', '', true); |
72 | |
73 | $this->type = (int) $type; |
74 | $this->question_lang = $user->lang_name; |
75 | |
76 | // we need all defined questions - shouldn't be too many, so we can just grab them |
77 | // try the user's lang first |
78 | $sql = 'SELECT question_id |
79 | FROM ' . $this->table_captcha_questions . " |
80 | WHERE lang_iso = '" . $db->sql_escape($user->lang_name) . "'"; |
81 | $result = $db->sql_query($sql, 3600); |
82 | |
83 | while ($row = $db->sql_fetchrow($result)) |
84 | { |
85 | $this->question_ids[$row['question_id']] = $row['question_id']; |
86 | } |
87 | $db->sql_freeresult($result); |
88 | |
89 | // fallback to the board default lang |
90 | if (!count($this->question_ids)) |
91 | { |
92 | $this->question_lang = $config['default_lang']; |
93 | |
94 | $sql = 'SELECT question_id |
95 | FROM ' . $this->table_captcha_questions . " |
96 | WHERE lang_iso = '" . $db->sql_escape($config['default_lang']) . "'"; |
97 | $result = $db->sql_query($sql, 7200); |
98 | |
99 | while ($row = $db->sql_fetchrow($result)) |
100 | { |
101 | $this->question_ids[$row['question_id']] = $row['question_id']; |
102 | } |
103 | $db->sql_freeresult($result); |
104 | } |
105 | |
106 | // final fallback to any language |
107 | if (!count($this->question_ids)) |
108 | { |
109 | $this->question_lang = ''; |
110 | |
111 | $sql = 'SELECT q.question_id, q.lang_iso |
112 | FROM ' . $this->table_captcha_questions . ' q, ' . $this->table_captcha_answers . ' a |
113 | WHERE q.question_id = a.question_id'; |
114 | $result = $db->sql_query($sql, 7200); |
115 | |
116 | while ($row = $db->sql_fetchrow($result)) |
117 | { |
118 | if (empty($this->question_lang)) |
119 | { |
120 | $this->question_lang = $row['lang_iso']; |
121 | } |
122 | $this->question_ids[$row['question_id']] = $row['question_id']; |
123 | } |
124 | $db->sql_freeresult($result); |
125 | } |
126 | |
127 | // okay, if there is a confirm_id, we try to load that confirm's state. If not, we try to find one |
128 | if (!$this->load_answer() && (!$this->load_confirm_id() || !$this->load_answer())) |
129 | { |
130 | // we have no valid confirm ID, better get ready to ask something |
131 | $this->select_question(); |
132 | } |
133 | } |
134 | |
135 | /** |
136 | * See if the captcha has created its tables. |
137 | */ |
138 | public function is_installed() |
139 | { |
140 | global $phpbb_container; |
141 | |
142 | $db_tool = $phpbb_container->get('dbal.tools'); |
143 | |
144 | return $db_tool->sql_table_exists($this->table_captcha_questions); |
145 | } |
146 | |
147 | /** |
148 | * API function - for the captcha to be available, it must have installed itself and there has to be at least one question in the board's default lang |
149 | */ |
150 | public function is_available() |
151 | { |
152 | global $config, $db, $user; |
153 | |
154 | // load language file for pretty display in the ACP dropdown |
155 | $user->add_lang('captcha_qa'); |
156 | |
157 | if (!$this->is_installed()) |
158 | { |
159 | return false; |
160 | } |
161 | |
162 | $sql = 'SELECT COUNT(question_id) AS question_count |
163 | FROM ' . $this->table_captcha_questions . " |
164 | WHERE lang_iso = '" . $db->sql_escape($config['default_lang']) . "'"; |
165 | $result = $db->sql_query($sql); |
166 | $row = $db->sql_fetchrow($result); |
167 | $db->sql_freeresult($result); |
168 | |
169 | return ((bool) $row['question_count']); |
170 | } |
171 | |
172 | /** |
173 | * API function |
174 | */ |
175 | function has_config() |
176 | { |
177 | return true; |
178 | } |
179 | |
180 | /** |
181 | * API function |
182 | */ |
183 | public static function get_name() |
184 | { |
185 | return 'CAPTCHA_QA'; |
186 | } |
187 | |
188 | /** |
189 | * @return string the name of the service corresponding to the plugin |
190 | */ |
191 | function get_service_name() |
192 | { |
193 | return $this->service_name; |
194 | } |
195 | |
196 | /** |
197 | * Set the name of the plugin |
198 | * |
199 | * @param string $name |
200 | */ |
201 | public function set_name($name) |
202 | { |
203 | $this->service_name = $name; |
204 | } |
205 | |
206 | /** |
207 | * API function - not needed as we don't display an image |
208 | */ |
209 | function execute_demo() |
210 | { |
211 | } |
212 | |
213 | /** |
214 | * API function - not needed as we don't display an image |
215 | */ |
216 | function execute() |
217 | { |
218 | } |
219 | |
220 | /** |
221 | * API function - send the question to the template |
222 | */ |
223 | function get_template() |
224 | { |
225 | global $phpbb_log, $template, $user; |
226 | |
227 | if ($this->is_solved()) |
228 | { |
229 | return false; |
230 | } |
231 | else if (empty($this->question_text) || !count($this->question_ids)) |
232 | { |
233 | /** @var \phpbb\log\log_interface $phpbb_log */ |
234 | $phpbb_log->add('critical', $user->data['user_id'], $user->ip, 'LOG_ERROR_CAPTCHA', time(), array($user->lang('CONFIRM_QUESTION_MISSING'))); |
235 | return false; |
236 | } |
237 | else |
238 | { |
239 | $template->assign_vars(array( |
240 | 'QA_CONFIRM_QUESTION' => $this->question_text, |
241 | 'QA_CONFIRM_ID' => $this->confirm_id, |
242 | 'S_CONFIRM_CODE' => true, |
243 | 'S_TYPE' => $this->type, |
244 | )); |
245 | |
246 | return 'captcha_qa.html'; |
247 | } |
248 | } |
249 | |
250 | /** |
251 | * API function - we just display a mockup so that the captcha doesn't need to be installed |
252 | */ |
253 | function get_demo_template() |
254 | { |
255 | global $config, $db, $template; |
256 | |
257 | if ($this->is_available()) |
258 | { |
259 | $sql = 'SELECT question_text |
260 | FROM ' . $this->table_captcha_questions . " |
261 | WHERE lang_iso = '" . $db->sql_escape($config['default_lang']) . "'"; |
262 | $result = $db->sql_query_limit($sql, 1); |
263 | if ($row = $db->sql_fetchrow($result)) |
264 | { |
265 | $template->assign_vars(array( |
266 | 'QA_CONFIRM_QUESTION' => $row['question_text'], |
267 | )); |
268 | } |
269 | $db->sql_freeresult($result); |
270 | } |
271 | return 'captcha_qa_acp_demo.html'; |
272 | } |
273 | |
274 | /** |
275 | * API function |
276 | */ |
277 | function get_hidden_fields() |
278 | { |
279 | $hidden_fields = array(); |
280 | |
281 | // this is required - otherwise we would forget about the captcha being already solved |
282 | if ($this->solved) |
283 | { |
284 | $hidden_fields['qa_answer'] = $this->answer; |
285 | } |
286 | $hidden_fields['qa_confirm_id'] = $this->confirm_id; |
287 | |
288 | return $hidden_fields; |
289 | } |
290 | |
291 | /** |
292 | * API function |
293 | */ |
294 | function garbage_collect($type = 0) |
295 | { |
296 | global $db; |
297 | |
298 | $sql = 'SELECT c.confirm_id |
299 | FROM ' . $this->table_qa_confirm . ' c |
300 | LEFT JOIN ' . SESSIONS_TABLE . ' s |
301 | ON (c.session_id = s.session_id) |
302 | WHERE s.session_id IS NULL' . |
303 | ((empty($type)) ? '' : ' AND c.confirm_type = ' . (int) $type); |
304 | $result = $db->sql_query($sql); |
305 | |
306 | if ($row = $db->sql_fetchrow($result)) |
307 | { |
308 | $sql_in = array(); |
309 | |
310 | do |
311 | { |
312 | $sql_in[] = (string) $row['confirm_id']; |
313 | } |
314 | while ($row = $db->sql_fetchrow($result)); |
315 | |
316 | if (count($sql_in)) |
317 | { |
318 | $sql = 'DELETE FROM ' . $this->table_qa_confirm . ' |
319 | WHERE ' . $db->sql_in_set('confirm_id', $sql_in); |
320 | $db->sql_query($sql); |
321 | } |
322 | } |
323 | $db->sql_freeresult($result); |
324 | } |
325 | |
326 | /** |
327 | * API function - we don't drop the tables here, as that would cause the loss of all entered questions. |
328 | */ |
329 | function uninstall() |
330 | { |
331 | $this->garbage_collect(0); |
332 | } |
333 | |
334 | /** |
335 | * API function - set up shop |
336 | */ |
337 | function install() |
338 | { |
339 | global $phpbb_container; |
340 | |
341 | $db_tool = $phpbb_container->get('dbal.tools'); |
342 | $schemas = array( |
343 | $this->table_captcha_questions => array ( |
344 | 'COLUMNS' => array( |
345 | 'question_id' => array('UINT', null, 'auto_increment'), |
346 | 'strict' => array('BOOL', 0), |
347 | 'lang_id' => array('UINT', 0), |
348 | 'lang_iso' => array('VCHAR:30', ''), |
349 | 'question_text' => array('TEXT_UNI', ''), |
350 | ), |
351 | 'PRIMARY_KEY' => 'question_id', |
352 | 'KEYS' => array( |
353 | 'lang' => array('INDEX', 'lang_iso'), |
354 | ), |
355 | ), |
356 | $this->table_captcha_answers => array ( |
357 | 'COLUMNS' => array( |
358 | 'question_id' => array('UINT', 0), |
359 | 'answer_text' => array('STEXT_UNI', ''), |
360 | ), |
361 | 'KEYS' => array( |
362 | 'qid' => array('INDEX', 'question_id'), |
363 | ), |
364 | ), |
365 | $this->table_qa_confirm => array ( |
366 | 'COLUMNS' => array( |
367 | 'session_id' => array('CHAR:32', ''), |
368 | 'confirm_id' => array('CHAR:32', ''), |
369 | 'lang_iso' => array('VCHAR:30', ''), |
370 | 'question_id' => array('UINT', 0), |
371 | 'attempts' => array('UINT', 0), |
372 | 'confirm_type' => array('USINT', 0), |
373 | ), |
374 | 'KEYS' => array( |
375 | 'session_id' => array('INDEX', 'session_id'), |
376 | 'lookup' => array('INDEX', array('confirm_id', 'session_id', 'lang_iso')), |
377 | ), |
378 | 'PRIMARY_KEY' => 'confirm_id', |
379 | ), |
380 | ); |
381 | |
382 | foreach ($schemas as $table => $schema) |
383 | { |
384 | if (!$db_tool->sql_table_exists($table)) |
385 | { |
386 | $db_tool->sql_create_table($table, $schema); |
387 | } |
388 | } |
389 | } |
390 | |
391 | /** |
392 | * API function - see what has to be done to validate |
393 | */ |
394 | function validate() |
395 | { |
396 | global $phpbb_log, $user; |
397 | |
398 | $error = ''; |
399 | |
400 | if (!count($this->question_ids)) |
401 | { |
402 | /** @var \phpbb\log\log_interface $phpbb_log */ |
403 | $phpbb_log->add('critical', $user->data['user_id'], $user->ip, 'LOG_ERROR_CAPTCHA', time(), array($user->lang('CONFIRM_QUESTION_MISSING'))); |
404 | return $user->lang('CONFIRM_QUESTION_MISSING'); |
405 | } |
406 | |
407 | if (!$this->confirm_id) |
408 | { |
409 | $error = $user->lang['CONFIRM_QUESTION_WRONG']; |
410 | } |
411 | else |
412 | { |
413 | if ($this->check_answer()) |
414 | { |
415 | $this->solved = true; |
416 | } |
417 | else |
418 | { |
419 | $error = $user->lang['CONFIRM_QUESTION_WRONG']; |
420 | } |
421 | } |
422 | |
423 | if (strlen($error)) |
424 | { |
425 | // okay, incorrect answer. Let's ask a new question. |
426 | $this->new_attempt(); |
427 | $this->solved = false; |
428 | |
429 | return $error; |
430 | } |
431 | else |
432 | { |
433 | return false; |
434 | } |
435 | } |
436 | |
437 | /** |
438 | * Select a question |
439 | */ |
440 | function select_question() |
441 | { |
442 | global $db, $user; |
443 | |
444 | if (!count($this->question_ids)) |
445 | { |
446 | return; |
447 | } |
448 | $this->confirm_id = md5(unique_id()); |
449 | $this->question = (int) array_rand($this->question_ids); |
450 | |
451 | $sql = 'INSERT INTO ' . $this->table_qa_confirm . ' ' . $db->sql_build_array('INSERT', array( |
452 | 'confirm_id' => (string) $this->confirm_id, |
453 | 'session_id' => (string) $user->session_id, |
454 | 'lang_iso' => (string) $this->question_lang, |
455 | 'confirm_type' => (int) $this->type, |
456 | 'question_id' => $this->question, |
457 | )); |
458 | $db->sql_query($sql); |
459 | |
460 | $this->load_answer(); |
461 | } |
462 | |
463 | /** |
464 | * New Question, if desired. |
465 | */ |
466 | function reselect_question() |
467 | { |
468 | global $db, $user; |
469 | |
470 | if (!count($this->question_ids)) |
471 | { |
472 | return; |
473 | } |
474 | |
475 | $this->question = (int) array_rand($this->question_ids); |
476 | $this->solved = 0; |
477 | |
478 | $sql = 'UPDATE ' . $this->table_qa_confirm . ' |
479 | SET question_id = ' . $this->question . " |
480 | WHERE confirm_id = '" . $db->sql_escape($this->confirm_id) . "' |
481 | AND session_id = '" . $db->sql_escape($user->session_id) . "'"; |
482 | $db->sql_query($sql); |
483 | |
484 | $this->load_answer(); |
485 | } |
486 | |
487 | /** |
488 | * Wrong answer, so we increase the attempts and use a different question. |
489 | */ |
490 | function new_attempt() |
491 | { |
492 | global $db, $user; |
493 | |
494 | // yah, I would prefer a stronger rand, but this should work |
495 | $this->question = (int) array_rand($this->question_ids); |
496 | $this->solved = 0; |
497 | |
498 | $sql = 'UPDATE ' . $this->table_qa_confirm . ' |
499 | SET question_id = ' . $this->question . ", |
500 | attempts = attempts + 1 |
501 | WHERE confirm_id = '" . $db->sql_escape($this->confirm_id) . "' |
502 | AND session_id = '" . $db->sql_escape($user->session_id) . "'"; |
503 | $db->sql_query($sql); |
504 | |
505 | $this->load_answer(); |
506 | } |
507 | |
508 | |
509 | /** |
510 | * See if there is already an entry for the current session. |
511 | */ |
512 | function load_confirm_id() |
513 | { |
514 | global $db, $user; |
515 | |
516 | $sql = 'SELECT confirm_id |
517 | FROM ' . $this->table_qa_confirm . " |
518 | WHERE |
519 | session_id = '" . $db->sql_escape($user->session_id) . "' |
520 | AND lang_iso = '" . $db->sql_escape($this->question_lang) . "' |
521 | AND confirm_type = " . $this->type; |
522 | $result = $db->sql_query_limit($sql, 1); |
523 | $row = $db->sql_fetchrow($result); |
524 | $db->sql_freeresult($result); |
525 | |
526 | if ($row) |
527 | { |
528 | $this->confirm_id = $row['confirm_id']; |
529 | return true; |
530 | } |
531 | return false; |
532 | } |
533 | |
534 | /** |
535 | * Look up everything we need and populate the instance variables. |
536 | */ |
537 | function load_answer() |
538 | { |
539 | global $db, $user; |
540 | |
541 | if (!strlen($this->confirm_id) || !count($this->question_ids)) |
542 | { |
543 | return false; |
544 | } |
545 | |
546 | $sql = 'SELECT con.question_id, attempts, question_text, strict |
547 | FROM ' . $this->table_qa_confirm . ' con, ' . $this->table_captcha_questions . " qes |
548 | WHERE con.question_id = qes.question_id |
549 | AND confirm_id = '" . $db->sql_escape($this->confirm_id) . "' |
550 | AND session_id = '" . $db->sql_escape($user->session_id) . "' |
551 | AND qes.lang_iso = '" . $db->sql_escape($this->question_lang) . "' |
552 | AND confirm_type = " . $this->type; |
553 | $result = $db->sql_query($sql); |
554 | $row = $db->sql_fetchrow($result); |
555 | $db->sql_freeresult($result); |
556 | |
557 | if ($row) |
558 | { |
559 | $this->question = (int) $row['question_id']; |
560 | |
561 | $this->attempts = $row['attempts']; |
562 | $this->question_strict = $row['strict']; |
563 | $this->question_text = $row['question_text']; |
564 | |
565 | return true; |
566 | } |
567 | |
568 | return false; |
569 | } |
570 | |
571 | /** |
572 | * The actual validation |
573 | */ |
574 | function check_answer() |
575 | { |
576 | global $db, $request; |
577 | |
578 | $answer = ($this->question_strict) ? $request->variable('qa_answer', '', true) : utf8_clean_string($request->variable('qa_answer', '', true)); |
579 | |
580 | $sql = 'SELECT answer_text |
581 | FROM ' . $this->table_captcha_answers . ' |
582 | WHERE question_id = ' . $this->question; |
583 | $result = $db->sql_query($sql); |
584 | |
585 | while ($row = $db->sql_fetchrow($result)) |
586 | { |
587 | $solution = ($this->question_strict) ? $row['answer_text'] : utf8_clean_string($row['answer_text']); |
588 | |
589 | if ($solution === $answer) |
590 | { |
591 | $this->solved = true; |
592 | |
593 | break; |
594 | } |
595 | } |
596 | $db->sql_freeresult($result); |
597 | |
598 | return $this->solved; |
599 | } |
600 | |
601 | /** |
602 | * API function |
603 | */ |
604 | function get_attempt_count() |
605 | { |
606 | return $this->attempts; |
607 | } |
608 | |
609 | /** |
610 | * API function |
611 | */ |
612 | function reset() |
613 | { |
614 | global $db, $user; |
615 | |
616 | $sql = 'DELETE FROM ' . $this->table_qa_confirm . " |
617 | WHERE session_id = '" . $db->sql_escape($user->session_id) . "' |
618 | AND confirm_type = " . (int) $this->type; |
619 | $db->sql_query($sql); |
620 | |
621 | // we leave the class usable by generating a new question |
622 | $this->select_question(); |
623 | } |
624 | |
625 | /** |
626 | * API function |
627 | */ |
628 | function is_solved() |
629 | { |
630 | global $request; |
631 | |
632 | if ($request->variable('qa_answer', false) && $this->solved === 0) |
633 | { |
634 | $this->validate(); |
635 | } |
636 | |
637 | return (bool) $this->solved; |
638 | } |
639 | |
640 | /** |
641 | * API function - The ACP backend, this marks the end of the easy methods |
642 | */ |
643 | function acp_page($id, $module) |
644 | { |
645 | global $config, $request, $phpbb_log, $template, $user; |
646 | |
647 | $user->add_lang('acp/board'); |
648 | $user->add_lang('captcha_qa'); |
649 | |
650 | if (!self::is_installed()) |
651 | { |
652 | $this->install(); |
653 | } |
654 | |
655 | $module->tpl_name = 'captcha_qa_acp'; |
656 | $module->page_title = 'ACP_VC_SETTINGS'; |
657 | $form_key = 'acp_captcha'; |
658 | add_form_key($form_key); |
659 | |
660 | $submit = $request->variable('submit', false); |
661 | $question_id = $request->variable('question_id', 0); |
662 | $action = $request->variable('action', ''); |
663 | |
664 | // we have two pages, so users might want to navigate from one to the other |
665 | $list_url = $module->u_action . "&configure=1&select_captcha=" . $this->get_service_name(); |
666 | |
667 | $template->assign_vars(array( |
668 | 'U_ACTION' => $module->u_action, |
669 | 'QUESTION_ID' => $question_id , |
670 | 'CLASS' => $this->get_service_name(), |
671 | )); |
672 | |
673 | // show the list? |
674 | if (!$question_id && $action != 'add') |
675 | { |
676 | $this->acp_question_list($module); |
677 | } |
678 | else if ($question_id && $action == 'delete') |
679 | { |
680 | if ($this->get_service_name() !== $config['captcha_plugin'] || !$this->acp_is_last($question_id)) |
681 | { |
682 | if (confirm_box(true)) |
683 | { |
684 | $this->acp_delete_question($question_id); |
685 | |
686 | trigger_error($user->lang['QUESTION_DELETED'] . adm_back_link($list_url)); |
687 | } |
688 | else |
689 | { |
690 | confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields(array( |
691 | 'question_id' => $question_id, |
692 | 'action' => $action, |
693 | 'configure' => 1, |
694 | 'select_captcha' => $this->get_service_name(), |
695 | )) |
696 | ); |
697 | } |
698 | } |
699 | else |
700 | { |
701 | trigger_error($user->lang['QA_LAST_QUESTION'] . adm_back_link($list_url), E_USER_WARNING); |
702 | } |
703 | } |
704 | else |
705 | { |
706 | // okay, show the editor |
707 | $question_input = $this->acp_get_question_input(); |
708 | $langs = $this->get_languages(); |
709 | |
710 | foreach ($langs as $lang => $entry) |
711 | { |
712 | $template->assign_block_vars('langs', array( |
713 | 'ISO' => $lang, |
714 | 'NAME' => $entry['name'], |
715 | )); |
716 | } |
717 | |
718 | $template->assign_vars(array( |
719 | 'U_LIST' => $list_url, |
720 | )); |
721 | |
722 | if ($question_id) |
723 | { |
724 | if ($question = $this->acp_get_question_data($question_id)) |
725 | { |
726 | $template->assign_vars(array( |
727 | 'QUESTION_TEXT' => ($question_input['question_text']) ? $question_input['question_text'] : $question['question_text'], |
728 | 'LANG_ISO' => ($question_input['lang_iso']) ? $question_input['lang_iso'] : $question['lang_iso'], |
729 | 'STRICT' => (isset($_REQUEST['strict'])) ? $question_input['strict'] : $question['strict'], |
730 | 'ANSWERS' => implode("\n", $question['answers']), |
731 | )); |
732 | } |
733 | else |
734 | { |
735 | trigger_error($user->lang['FORM_INVALID'] . adm_back_link($list_url)); |
736 | } |
737 | } |
738 | else |
739 | { |
740 | $template->assign_vars(array( |
741 | 'QUESTION_TEXT' => $question_input['question_text'], |
742 | 'LANG_ISO' => $question_input['lang_iso'], |
743 | 'STRICT' => $question_input['strict'], |
744 | 'ANSWERS' => (is_array($question_input['answers'])) ? implode("\n", $question_input['answers']) : '', |
745 | )); |
746 | } |
747 | |
748 | if ($submit && check_form_key($form_key)) |
749 | { |
750 | if (!$this->validate_input($question_input)) |
751 | { |
752 | $template->assign_vars(array( |
753 | 'S_ERROR' => true, |
754 | )); |
755 | } |
756 | else |
757 | { |
758 | if ($question_id) |
759 | { |
760 | $this->acp_update_question($question_input, $question_id); |
761 | } |
762 | else |
763 | { |
764 | $this->acp_add_question($question_input); |
765 | } |
766 | |
767 | $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_CONFIG_VISUAL'); |
768 | trigger_error($user->lang['CONFIG_UPDATED'] . adm_back_link($list_url)); |
769 | } |
770 | } |
771 | else if ($submit) |
772 | { |
773 | trigger_error($user->lang['FORM_INVALID'] . adm_back_link($list_url), E_USER_WARNING); |
774 | } |
775 | } |
776 | } |
777 | |
778 | /** |
779 | * This handles the list overview |
780 | */ |
781 | function acp_question_list($module) |
782 | { |
783 | global $db, $template; |
784 | |
785 | $sql = 'SELECT * |
786 | FROM ' . $this->table_captcha_questions; |
787 | $result = $db->sql_query($sql); |
788 | |
789 | $template->assign_vars(array( |
790 | 'S_LIST' => true, |
791 | )); |
792 | |
793 | while ($row = $db->sql_fetchrow($result)) |
794 | { |
795 | $url = $module->u_action . "&question_id={$row['question_id']}&configure=1&select_captcha=" . $this->get_service_name() . '&'; |
796 | |
797 | $template->assign_block_vars('questions', array( |
798 | 'QUESTION_TEXT' => $row['question_text'], |
799 | 'QUESTION_ID' => $row['question_id'], |
800 | 'QUESTION_LANG' => $row['lang_iso'], |
801 | 'U_DELETE' => "{$url}action=delete", |
802 | 'U_EDIT' => "{$url}action=edit", |
803 | )); |
804 | } |
805 | $db->sql_freeresult($result); |
806 | } |
807 | |
808 | /** |
809 | * Grab a question and bring it into a format the editor understands |
810 | */ |
811 | function acp_get_question_data($question_id) |
812 | { |
813 | global $db; |
814 | |
815 | if ($question_id) |
816 | { |
817 | $sql = 'SELECT * |
818 | FROM ' . $this->table_captcha_questions . ' |
819 | WHERE question_id = ' . $question_id; |
820 | $result = $db->sql_query($sql); |
821 | $question = $db->sql_fetchrow($result); |
822 | $db->sql_freeresult($result); |
823 | |
824 | if (!$question) |
825 | { |
826 | return false; |
827 | } |
828 | |
829 | $question['answers'] = array(); |
830 | |
831 | $sql = 'SELECT * |
832 | FROM ' . $this->table_captcha_answers . ' |
833 | WHERE question_id = ' . $question_id; |
834 | $result = $db->sql_query($sql); |
835 | |
836 | while ($row = $db->sql_fetchrow($result)) |
837 | { |
838 | $question['answers'][] = $row['answer_text']; |
839 | } |
840 | $db->sql_freeresult($result); |
841 | |
842 | return $question; |
843 | } |
844 | |
845 | return false; |
846 | } |
847 | |
848 | /** |
849 | * Grab a question from input and bring it into a format the editor understands |
850 | */ |
851 | function acp_get_question_input() |
852 | { |
853 | global $request; |
854 | |
855 | $answers = $request->variable('answers', '', true); |
856 | |
857 | // Convert answers into array and filter if answers are set |
858 | if (strlen($answers)) |
859 | { |
860 | $answers = array_filter(array_map('trim', explode("\n", $answers)), function ($value) { |
861 | return $value !== ''; |
862 | }); |
863 | } |
864 | |
865 | $question = array( |
866 | 'question_text' => $request->variable('question_text', '', true), |
867 | 'strict' => $request->variable('strict', false), |
868 | 'lang_iso' => $request->variable('lang_iso', ''), |
869 | 'answers' => $answers, |
870 | ); |
871 | return $question; |
872 | } |
873 | |
874 | /** |
875 | * Update a question. |
876 | * param mixed $data : an array as created from acp_get_question_input or acp_get_question_data |
877 | */ |
878 | function acp_update_question($data, $question_id) |
879 | { |
880 | global $db, $cache; |
881 | |
882 | // easier to delete all answers than to figure out which to update |
883 | $sql = 'DELETE FROM ' . $this->table_captcha_answers . " WHERE question_id = $question_id"; |
884 | $db->sql_query($sql); |
885 | |
886 | $langs = $this->get_languages(); |
887 | $question_ary = $data; |
888 | $question_ary['lang_id'] = $langs[$question_ary['lang_iso']]['id']; |
889 | unset($question_ary['answers']); |
890 | |
891 | $sql = 'UPDATE ' . $this->table_captcha_questions . ' |
892 | SET ' . $db->sql_build_array('UPDATE', $question_ary) . " |
893 | WHERE question_id = $question_id"; |
894 | $db->sql_query($sql); |
895 | |
896 | $this->acp_insert_answers($data, $question_id); |
897 | |
898 | $cache->destroy('sql', $this->table_captcha_questions); |
899 | } |
900 | |
901 | /** |
902 | * Insert a question. |
903 | * param mixed $data : an array as created from acp_get_question_input or acp_get_question_data |
904 | */ |
905 | function acp_add_question($data) |
906 | { |
907 | global $db, $cache; |
908 | |
909 | $langs = $this->get_languages(); |
910 | $question_ary = $data; |
911 | |
912 | $question_ary['lang_id'] = $langs[$data['lang_iso']]['id']; |
913 | unset($question_ary['answers']); |
914 | |
915 | $sql = 'INSERT INTO ' . $this->table_captcha_questions . ' ' . $db->sql_build_array('INSERT', $question_ary); |
916 | $db->sql_query($sql); |
917 | |
918 | $question_id = $db->sql_nextid(); |
919 | |
920 | $this->acp_insert_answers($data, $question_id); |
921 | |
922 | $cache->destroy('sql', $this->table_captcha_questions); |
923 | } |
924 | |
925 | /** |
926 | * Insert the answers. |
927 | * param mixed $data : an array as created from acp_get_question_input or acp_get_question_data |
928 | */ |
929 | function acp_insert_answers($data, $question_id) |
930 | { |
931 | global $db, $cache; |
932 | |
933 | foreach ($data['answers'] as $answer) |
934 | { |
935 | $answer_ary = array( |
936 | 'question_id' => $question_id, |
937 | 'answer_text' => $answer, |
938 | ); |
939 | |
940 | $sql = 'INSERT INTO ' . $this->table_captcha_answers . ' ' . $db->sql_build_array('INSERT', $answer_ary); |
941 | $db->sql_query($sql); |
942 | } |
943 | |
944 | $cache->destroy('sql', $this->table_captcha_answers); |
945 | } |
946 | |
947 | /** |
948 | * Delete a question. |
949 | */ |
950 | function acp_delete_question($question_id) |
951 | { |
952 | global $db, $cache; |
953 | |
954 | $tables = array($this->table_captcha_questions, $this->table_captcha_answers); |
955 | |
956 | foreach ($tables as $table) |
957 | { |
958 | $sql = "DELETE FROM $table |
959 | WHERE question_id = $question_id"; |
960 | $db->sql_query($sql); |
961 | } |
962 | |
963 | $cache->destroy('sql', $tables); |
964 | } |
965 | |
966 | /** |
967 | * Check if the entered data can be inserted/used |
968 | * param mixed $data : an array as created from acp_get_question_input or acp_get_question_data |
969 | */ |
970 | function validate_input($question_data) |
971 | { |
972 | $langs = $this->get_languages(); |
973 | |
974 | if (!isset($question_data['lang_iso']) || |
975 | !isset($question_data['question_text']) || |
976 | !isset($question_data['strict']) || |
977 | !isset($question_data['answers'])) |
978 | { |
979 | return false; |
980 | } |
981 | |
982 | if (!isset($langs[$question_data['lang_iso']]) || |
983 | !strlen($question_data['question_text']) || |
984 | !count($question_data['answers']) || |
985 | !is_array($question_data['answers'])) |
986 | { |
987 | return false; |
988 | } |
989 | |
990 | return true; |
991 | } |
992 | |
993 | /** |
994 | * List the installed language packs |
995 | */ |
996 | function get_languages() |
997 | { |
998 | global $db; |
999 | |
1000 | $sql = 'SELECT * |
1001 | FROM ' . LANG_TABLE; |
1002 | $result = $db->sql_query($sql); |
1003 | |
1004 | $langs = array(); |
1005 | while ($row = $db->sql_fetchrow($result)) |
1006 | { |
1007 | $langs[$row['lang_iso']] = array( |
1008 | 'name' => $row['lang_local_name'], |
1009 | 'id' => (int) $row['lang_id'], |
1010 | ); |
1011 | } |
1012 | $db->sql_freeresult($result); |
1013 | |
1014 | return $langs; |
1015 | } |
1016 | |
1017 | |
1018 | |
1019 | /** |
1020 | * See if there is a question other than the one we have |
1021 | */ |
1022 | function acp_is_last($question_id) |
1023 | { |
1024 | global $config, $db; |
1025 | |
1026 | if ($question_id) |
1027 | { |
1028 | $sql = 'SELECT question_id |
1029 | FROM ' . $this->table_captcha_questions . " |
1030 | WHERE lang_iso = '" . $db->sql_escape($config['default_lang']) . "' |
1031 | AND question_id <> " . (int) $question_id; |
1032 | $result = $db->sql_query_limit($sql, 1); |
1033 | $question = $db->sql_fetchrow($result); |
1034 | $db->sql_freeresult($result); |
1035 | |
1036 | if (!$question) |
1037 | { |
1038 | return true; |
1039 | } |
1040 | } |
1041 | |
1042 | return false; |
1043 | } |
1044 | } |