Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
5.22% |
21 / 402 |
|
14.29% |
5 / 35 |
CRAP | |
0.00% |
0 / 1 |
qa | |
5.22% |
21 / 402 |
|
14.29% |
5 / 35 |
9853.85 | |
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 | |||
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 / 68 |
|
0.00% |
0 / 1 |
420 | |||
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 | private $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 - see what has to be done to validate |
328 | */ |
329 | function validate() |
330 | { |
331 | global $phpbb_log, $user; |
332 | |
333 | $error = ''; |
334 | |
335 | if (!count($this->question_ids)) |
336 | { |
337 | /** @var \phpbb\log\log_interface $phpbb_log */ |
338 | $phpbb_log->add('critical', $user->data['user_id'], $user->ip, 'LOG_ERROR_CAPTCHA', time(), array($user->lang('CONFIRM_QUESTION_MISSING'))); |
339 | return $user->lang('CONFIRM_QUESTION_MISSING'); |
340 | } |
341 | |
342 | if (!$this->confirm_id) |
343 | { |
344 | $error = $user->lang['CONFIRM_QUESTION_WRONG']; |
345 | } |
346 | else |
347 | { |
348 | if ($this->check_answer()) |
349 | { |
350 | $this->solved = true; |
351 | } |
352 | else |
353 | { |
354 | $error = $user->lang['CONFIRM_QUESTION_WRONG']; |
355 | } |
356 | } |
357 | |
358 | if (strlen($error)) |
359 | { |
360 | // okay, incorrect answer. Let's ask a new question. |
361 | $this->new_attempt(); |
362 | $this->solved = false; |
363 | |
364 | return $error; |
365 | } |
366 | else |
367 | { |
368 | return false; |
369 | } |
370 | } |
371 | |
372 | /** |
373 | * Select a question |
374 | */ |
375 | function select_question() |
376 | { |
377 | global $db, $user; |
378 | |
379 | if (!count($this->question_ids)) |
380 | { |
381 | return; |
382 | } |
383 | $this->confirm_id = md5(unique_id()); |
384 | $this->question = (int) array_rand($this->question_ids); |
385 | |
386 | $sql = 'INSERT INTO ' . $this->table_qa_confirm . ' ' . $db->sql_build_array('INSERT', array( |
387 | 'confirm_id' => (string) $this->confirm_id, |
388 | 'session_id' => (string) $user->session_id, |
389 | 'lang_iso' => (string) $this->question_lang, |
390 | 'confirm_type' => (int) $this->type, |
391 | 'question_id' => $this->question, |
392 | )); |
393 | $db->sql_query($sql); |
394 | |
395 | $this->load_answer(); |
396 | } |
397 | |
398 | /** |
399 | * New Question, if desired. |
400 | */ |
401 | function reselect_question() |
402 | { |
403 | global $db, $user; |
404 | |
405 | if (!count($this->question_ids)) |
406 | { |
407 | return; |
408 | } |
409 | |
410 | $this->question = (int) array_rand($this->question_ids); |
411 | $this->solved = 0; |
412 | |
413 | $sql = 'UPDATE ' . $this->table_qa_confirm . ' |
414 | SET question_id = ' . $this->question . " |
415 | WHERE confirm_id = '" . $db->sql_escape($this->confirm_id) . "' |
416 | AND session_id = '" . $db->sql_escape($user->session_id) . "'"; |
417 | $db->sql_query($sql); |
418 | |
419 | $this->load_answer(); |
420 | } |
421 | |
422 | /** |
423 | * Wrong answer, so we increase the attempts and use a different question. |
424 | */ |
425 | function new_attempt() |
426 | { |
427 | global $db, $user; |
428 | |
429 | // yah, I would prefer a stronger rand, but this should work |
430 | $this->question = (int) array_rand($this->question_ids); |
431 | $this->solved = 0; |
432 | |
433 | $sql = 'UPDATE ' . $this->table_qa_confirm . ' |
434 | SET question_id = ' . $this->question . ", |
435 | attempts = attempts + 1 |
436 | WHERE confirm_id = '" . $db->sql_escape($this->confirm_id) . "' |
437 | AND session_id = '" . $db->sql_escape($user->session_id) . "'"; |
438 | $db->sql_query($sql); |
439 | |
440 | $this->load_answer(); |
441 | } |
442 | |
443 | |
444 | /** |
445 | * See if there is already an entry for the current session. |
446 | */ |
447 | function load_confirm_id() |
448 | { |
449 | global $db, $user; |
450 | |
451 | $sql = 'SELECT confirm_id |
452 | FROM ' . $this->table_qa_confirm . " |
453 | WHERE |
454 | session_id = '" . $db->sql_escape($user->session_id) . "' |
455 | AND lang_iso = '" . $db->sql_escape($this->question_lang) . "' |
456 | AND confirm_type = " . $this->type; |
457 | $result = $db->sql_query_limit($sql, 1); |
458 | $row = $db->sql_fetchrow($result); |
459 | $db->sql_freeresult($result); |
460 | |
461 | if ($row) |
462 | { |
463 | $this->confirm_id = $row['confirm_id']; |
464 | return true; |
465 | } |
466 | return false; |
467 | } |
468 | |
469 | /** |
470 | * Look up everything we need and populate the instance variables. |
471 | */ |
472 | function load_answer() |
473 | { |
474 | global $db, $user; |
475 | |
476 | if (!strlen($this->confirm_id) || !count($this->question_ids)) |
477 | { |
478 | return false; |
479 | } |
480 | |
481 | $sql = 'SELECT con.question_id, attempts, question_text, strict |
482 | FROM ' . $this->table_qa_confirm . ' con, ' . $this->table_captcha_questions . " qes |
483 | WHERE con.question_id = qes.question_id |
484 | AND confirm_id = '" . $db->sql_escape($this->confirm_id) . "' |
485 | AND session_id = '" . $db->sql_escape($user->session_id) . "' |
486 | AND qes.lang_iso = '" . $db->sql_escape($this->question_lang) . "' |
487 | AND confirm_type = " . $this->type; |
488 | $result = $db->sql_query($sql); |
489 | $row = $db->sql_fetchrow($result); |
490 | $db->sql_freeresult($result); |
491 | |
492 | if ($row) |
493 | { |
494 | $this->question = (int) $row['question_id']; |
495 | |
496 | $this->attempts = $row['attempts']; |
497 | $this->question_strict = $row['strict']; |
498 | $this->question_text = $row['question_text']; |
499 | |
500 | return true; |
501 | } |
502 | |
503 | return false; |
504 | } |
505 | |
506 | /** |
507 | * The actual validation |
508 | */ |
509 | function check_answer() |
510 | { |
511 | global $db, $request; |
512 | |
513 | $answer = ($this->question_strict) ? $request->variable('qa_answer', '', true) : utf8_clean_string($request->variable('qa_answer', '', true)); |
514 | |
515 | $sql = 'SELECT answer_text |
516 | FROM ' . $this->table_captcha_answers . ' |
517 | WHERE question_id = ' . $this->question; |
518 | $result = $db->sql_query($sql); |
519 | |
520 | while ($row = $db->sql_fetchrow($result)) |
521 | { |
522 | $solution = ($this->question_strict) ? $row['answer_text'] : utf8_clean_string($row['answer_text']); |
523 | |
524 | if ($solution === $answer) |
525 | { |
526 | $this->solved = true; |
527 | |
528 | break; |
529 | } |
530 | } |
531 | $db->sql_freeresult($result); |
532 | |
533 | return $this->solved; |
534 | } |
535 | |
536 | /** |
537 | * API function |
538 | */ |
539 | function get_attempt_count() |
540 | { |
541 | return $this->attempts; |
542 | } |
543 | |
544 | /** |
545 | * API function |
546 | */ |
547 | function reset() |
548 | { |
549 | global $db, $user; |
550 | |
551 | $sql = 'DELETE FROM ' . $this->table_qa_confirm . " |
552 | WHERE session_id = '" . $db->sql_escape($user->session_id) . "' |
553 | AND confirm_type = " . (int) $this->type; |
554 | $db->sql_query($sql); |
555 | |
556 | // we leave the class usable by generating a new question |
557 | $this->select_question(); |
558 | } |
559 | |
560 | /** |
561 | * API function |
562 | */ |
563 | function is_solved() |
564 | { |
565 | global $request; |
566 | |
567 | if ($request->variable('qa_answer', false) && $this->solved === 0) |
568 | { |
569 | $this->validate(); |
570 | } |
571 | |
572 | return (bool) $this->solved; |
573 | } |
574 | |
575 | /** |
576 | * API function - The ACP backend, this marks the end of the easy methods |
577 | */ |
578 | function acp_page($id, $module) |
579 | { |
580 | global $config, $request, $phpbb_log, $template, $user; |
581 | |
582 | $user->add_lang('acp/board'); |
583 | $user->add_lang('captcha_qa'); |
584 | |
585 | $module->tpl_name = 'captcha_qa_acp'; |
586 | $module->page_title = 'ACP_VC_SETTINGS'; |
587 | $form_key = 'acp_captcha'; |
588 | add_form_key($form_key); |
589 | |
590 | $submit = $request->variable('submit', false); |
591 | $question_id = $request->variable('question_id', 0); |
592 | $action = $request->variable('action', ''); |
593 | |
594 | // we have two pages, so users might want to navigate from one to the other |
595 | $list_url = $module->u_action . "&configure=1&select_captcha=" . $this->get_service_name(); |
596 | |
597 | $template->assign_vars(array( |
598 | 'U_ACTION' => $module->u_action, |
599 | 'QUESTION_ID' => $question_id , |
600 | 'CLASS' => $this->get_service_name(), |
601 | )); |
602 | |
603 | // show the list? |
604 | if (!$question_id && $action != 'add') |
605 | { |
606 | $this->acp_question_list($module); |
607 | } |
608 | else if ($question_id && $action == 'delete') |
609 | { |
610 | if ($this->get_service_name() !== $config['captcha_plugin'] || !$this->acp_is_last($question_id)) |
611 | { |
612 | if (confirm_box(true)) |
613 | { |
614 | $this->acp_delete_question($question_id); |
615 | |
616 | trigger_error($user->lang['QUESTION_DELETED'] . adm_back_link($list_url)); |
617 | } |
618 | else |
619 | { |
620 | confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields(array( |
621 | 'question_id' => $question_id, |
622 | 'action' => $action, |
623 | 'configure' => 1, |
624 | 'select_captcha' => $this->get_service_name(), |
625 | )) |
626 | ); |
627 | } |
628 | } |
629 | else |
630 | { |
631 | trigger_error($user->lang['QA_LAST_QUESTION'] . adm_back_link($list_url), E_USER_WARNING); |
632 | } |
633 | } |
634 | else |
635 | { |
636 | // okay, show the editor |
637 | $question_input = $this->acp_get_question_input(); |
638 | $langs = $this->get_languages(); |
639 | |
640 | foreach ($langs as $lang => $entry) |
641 | { |
642 | $template->assign_block_vars('langs', array( |
643 | 'ISO' => $lang, |
644 | 'NAME' => $entry['name'], |
645 | )); |
646 | } |
647 | |
648 | $template->assign_vars(array( |
649 | 'U_LIST' => $list_url, |
650 | )); |
651 | |
652 | if ($question_id) |
653 | { |
654 | if ($question = $this->acp_get_question_data($question_id)) |
655 | { |
656 | $template->assign_vars(array( |
657 | 'QUESTION_TEXT' => ($question_input['question_text']) ? $question_input['question_text'] : $question['question_text'], |
658 | 'LANG_ISO' => ($question_input['lang_iso']) ? $question_input['lang_iso'] : $question['lang_iso'], |
659 | 'STRICT' => (isset($_REQUEST['strict'])) ? $question_input['strict'] : $question['strict'], |
660 | 'ANSWERS' => implode("\n", $question['answers']), |
661 | )); |
662 | } |
663 | else |
664 | { |
665 | trigger_error($user->lang['FORM_INVALID'] . adm_back_link($list_url)); |
666 | } |
667 | } |
668 | else |
669 | { |
670 | $template->assign_vars(array( |
671 | 'QUESTION_TEXT' => $question_input['question_text'], |
672 | 'LANG_ISO' => $question_input['lang_iso'], |
673 | 'STRICT' => $question_input['strict'], |
674 | 'ANSWERS' => (is_array($question_input['answers'])) ? implode("\n", $question_input['answers']) : '', |
675 | )); |
676 | } |
677 | |
678 | if ($submit && check_form_key($form_key)) |
679 | { |
680 | if (!$this->validate_input($question_input)) |
681 | { |
682 | $template->assign_vars(array( |
683 | 'S_ERROR' => true, |
684 | )); |
685 | } |
686 | else |
687 | { |
688 | if ($question_id) |
689 | { |
690 | $this->acp_update_question($question_input, $question_id); |
691 | } |
692 | else |
693 | { |
694 | $this->acp_add_question($question_input); |
695 | } |
696 | |
697 | $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_CONFIG_VISUAL'); |
698 | trigger_error($user->lang['CONFIG_UPDATED'] . adm_back_link($list_url)); |
699 | } |
700 | } |
701 | else if ($submit) |
702 | { |
703 | trigger_error($user->lang['FORM_INVALID'] . adm_back_link($list_url), E_USER_WARNING); |
704 | } |
705 | } |
706 | } |
707 | |
708 | /** |
709 | * This handles the list overview |
710 | */ |
711 | function acp_question_list($module) |
712 | { |
713 | global $db, $template; |
714 | |
715 | $sql = 'SELECT * |
716 | FROM ' . $this->table_captcha_questions; |
717 | $result = $db->sql_query($sql); |
718 | |
719 | $template->assign_vars(array( |
720 | 'S_LIST' => true, |
721 | )); |
722 | |
723 | while ($row = $db->sql_fetchrow($result)) |
724 | { |
725 | $url = $module->u_action . "&question_id={$row['question_id']}&configure=1&select_captcha=" . $this->get_service_name() . '&'; |
726 | |
727 | $template->assign_block_vars('questions', array( |
728 | 'QUESTION_TEXT' => $row['question_text'], |
729 | 'QUESTION_ID' => $row['question_id'], |
730 | 'QUESTION_LANG' => $row['lang_iso'], |
731 | 'U_DELETE' => "{$url}action=delete", |
732 | 'U_EDIT' => "{$url}action=edit", |
733 | )); |
734 | } |
735 | $db->sql_freeresult($result); |
736 | } |
737 | |
738 | /** |
739 | * Grab a question and bring it into a format the editor understands |
740 | */ |
741 | function acp_get_question_data($question_id) |
742 | { |
743 | global $db; |
744 | |
745 | if ($question_id) |
746 | { |
747 | $sql = 'SELECT * |
748 | FROM ' . $this->table_captcha_questions . ' |
749 | WHERE question_id = ' . $question_id; |
750 | $result = $db->sql_query($sql); |
751 | $question = $db->sql_fetchrow($result); |
752 | $db->sql_freeresult($result); |
753 | |
754 | if (!$question) |
755 | { |
756 | return false; |
757 | } |
758 | |
759 | $question['answers'] = array(); |
760 | |
761 | $sql = 'SELECT * |
762 | FROM ' . $this->table_captcha_answers . ' |
763 | WHERE question_id = ' . $question_id; |
764 | $result = $db->sql_query($sql); |
765 | |
766 | while ($row = $db->sql_fetchrow($result)) |
767 | { |
768 | $question['answers'][] = $row['answer_text']; |
769 | } |
770 | $db->sql_freeresult($result); |
771 | |
772 | return $question; |
773 | } |
774 | |
775 | return false; |
776 | } |
777 | |
778 | /** |
779 | * Grab a question from input and bring it into a format the editor understands |
780 | */ |
781 | function acp_get_question_input() |
782 | { |
783 | global $request; |
784 | |
785 | $answers = $request->variable('answers', '', true); |
786 | |
787 | // Convert answers into array and filter if answers are set |
788 | if (strlen($answers)) |
789 | { |
790 | $answers = array_filter(array_map('trim', explode("\n", $answers)), function ($value) { |
791 | return $value !== ''; |
792 | }); |
793 | } |
794 | |
795 | $question = array( |
796 | 'question_text' => $request->variable('question_text', '', true), |
797 | 'strict' => $request->variable('strict', false), |
798 | 'lang_iso' => $request->variable('lang_iso', ''), |
799 | 'answers' => $answers, |
800 | ); |
801 | return $question; |
802 | } |
803 | |
804 | /** |
805 | * Update a question. |
806 | * param mixed $data : an array as created from acp_get_question_input or acp_get_question_data |
807 | */ |
808 | function acp_update_question($data, $question_id) |
809 | { |
810 | global $db, $cache; |
811 | |
812 | // easier to delete all answers than to figure out which to update |
813 | $sql = 'DELETE FROM ' . $this->table_captcha_answers . " WHERE question_id = $question_id"; |
814 | $db->sql_query($sql); |
815 | |
816 | $langs = $this->get_languages(); |
817 | $question_ary = $data; |
818 | $question_ary['lang_id'] = $langs[$question_ary['lang_iso']]['id']; |
819 | unset($question_ary['answers']); |
820 | |
821 | $sql = 'UPDATE ' . $this->table_captcha_questions . ' |
822 | SET ' . $db->sql_build_array('UPDATE', $question_ary) . " |
823 | WHERE question_id = $question_id"; |
824 | $db->sql_query($sql); |
825 | |
826 | $this->acp_insert_answers($data, $question_id); |
827 | |
828 | $cache->destroy('sql', $this->table_captcha_questions); |
829 | } |
830 | |
831 | /** |
832 | * Insert a question. |
833 | * param mixed $data : an array as created from acp_get_question_input or acp_get_question_data |
834 | */ |
835 | function acp_add_question($data) |
836 | { |
837 | global $db, $cache; |
838 | |
839 | $langs = $this->get_languages(); |
840 | $question_ary = $data; |
841 | |
842 | $question_ary['lang_id'] = $langs[$data['lang_iso']]['id']; |
843 | unset($question_ary['answers']); |
844 | |
845 | $sql = 'INSERT INTO ' . $this->table_captcha_questions . ' ' . $db->sql_build_array('INSERT', $question_ary); |
846 | $db->sql_query($sql); |
847 | |
848 | $question_id = $db->sql_nextid(); |
849 | |
850 | $this->acp_insert_answers($data, $question_id); |
851 | |
852 | $cache->destroy('sql', $this->table_captcha_questions); |
853 | } |
854 | |
855 | /** |
856 | * Insert the answers. |
857 | * param mixed $data : an array as created from acp_get_question_input or acp_get_question_data |
858 | */ |
859 | function acp_insert_answers($data, $question_id) |
860 | { |
861 | global $db, $cache; |
862 | |
863 | foreach ($data['answers'] as $answer) |
864 | { |
865 | $answer_ary = array( |
866 | 'question_id' => $question_id, |
867 | 'answer_text' => $answer, |
868 | ); |
869 | |
870 | $sql = 'INSERT INTO ' . $this->table_captcha_answers . ' ' . $db->sql_build_array('INSERT', $answer_ary); |
871 | $db->sql_query($sql); |
872 | } |
873 | |
874 | $cache->destroy('sql', $this->table_captcha_answers); |
875 | } |
876 | |
877 | /** |
878 | * Delete a question. |
879 | */ |
880 | function acp_delete_question($question_id) |
881 | { |
882 | global $db, $cache; |
883 | |
884 | $tables = array($this->table_captcha_questions, $this->table_captcha_answers); |
885 | |
886 | foreach ($tables as $table) |
887 | { |
888 | $sql = "DELETE FROM $table |
889 | WHERE question_id = $question_id"; |
890 | $db->sql_query($sql); |
891 | } |
892 | |
893 | $cache->destroy('sql', $tables); |
894 | } |
895 | |
896 | /** |
897 | * Check if the entered data can be inserted/used |
898 | * param mixed $data : an array as created from acp_get_question_input or acp_get_question_data |
899 | */ |
900 | function validate_input($question_data) |
901 | { |
902 | $langs = $this->get_languages(); |
903 | |
904 | if (!isset($question_data['lang_iso']) || |
905 | !isset($question_data['question_text']) || |
906 | !isset($question_data['strict']) || |
907 | !isset($question_data['answers'])) |
908 | { |
909 | return false; |
910 | } |
911 | |
912 | if (!isset($langs[$question_data['lang_iso']]) || |
913 | !strlen($question_data['question_text']) || |
914 | !count($question_data['answers']) || |
915 | !is_array($question_data['answers'])) |
916 | { |
917 | return false; |
918 | } |
919 | |
920 | return true; |
921 | } |
922 | |
923 | /** |
924 | * List the installed language packs |
925 | */ |
926 | function get_languages() |
927 | { |
928 | global $db; |
929 | |
930 | $sql = 'SELECT * |
931 | FROM ' . LANG_TABLE; |
932 | $result = $db->sql_query($sql); |
933 | |
934 | $langs = array(); |
935 | while ($row = $db->sql_fetchrow($result)) |
936 | { |
937 | $langs[$row['lang_iso']] = array( |
938 | 'name' => $row['lang_local_name'], |
939 | 'id' => (int) $row['lang_id'], |
940 | ); |
941 | } |
942 | $db->sql_freeresult($result); |
943 | |
944 | return $langs; |
945 | } |
946 | |
947 | |
948 | |
949 | /** |
950 | * See if there is a question other than the one we have |
951 | */ |
952 | function acp_is_last($question_id) |
953 | { |
954 | global $config, $db; |
955 | |
956 | if ($question_id) |
957 | { |
958 | $sql = 'SELECT question_id |
959 | FROM ' . $this->table_captcha_questions . " |
960 | WHERE lang_iso = '" . $db->sql_escape($config['default_lang']) . "' |
961 | AND question_id <> " . (int) $question_id; |
962 | $result = $db->sql_query_limit($sql, 1); |
963 | $question = $db->sql_fetchrow($result); |
964 | $db->sql_freeresult($result); |
965 | |
966 | if (!$question) |
967 | { |
968 | return true; |
969 | } |
970 | } |
971 | |
972 | return false; |
973 | } |
974 | } |