b2evolution PHP Cross Reference Blogging Systems

Source: /plugins/_captcha_qstn.plugin.php - 565 lines - 15579 bytes - Summary - Text - Print

Description: This file implements the Captcha Questions plugin. The core functionality was provided by Francois PLANQUE.

   1  <?php
   2  /**
   3   * This file implements the Captcha Questions plugin.
   4   *
   5   * The core functionality was provided by Francois PLANQUE.
   6   *
   7   * This file is part of the b2evolution/evocms project - {@link http://b2evolution.net/}.
   8   * See also {@link http://sourceforge.net/projects/evocms/}.
   9   *
  10   * @copyright (c)2012-2014 by Francois PLANQUE - {@link http://fplanque.net/}.
  11   *
  12   * @license http://b2evolution.net/about/license.html GNU General Public License (GPL)
  13   * {@internal
  14   * b2evolution is free software; you can redistribute it and/or modify
  15   * it under the terms of the GNU General Public License as published by
  16   * the Free Software Foundation; either version 2 of the License, or
  17   * (at your option) any later version.
  18   *
  19   * b2evolution is distributed in the hope that it will be useful,
  20   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  21   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  22   * GNU General Public License for more details.
  23   * }}
  24   *
  25   * @package plugins
  26   *
  27   * @author fplanque: Francois PLANQUE
  28   *
  29   * @version $Id: _captcha_qstn.plugin.php 72 2012-03-05 23:35:41Z yura $
  30   */
  31  if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );
  32  
  33  
  34  /**
  35   * The Captcha Questions Plugin.
  36   *
  37   * It displays an captcha question through {@link CaptchaValidated()} and validates
  38   * it in {@link CaptchaValidated()}.
  39   */
  40  class captcha_qstn_plugin extends Plugin
  41  {
  42      //var $help_url = 'http://b2evolution.net/man/captcha_qstn';
  43      var $version = '5.0.0';
  44      var $group = 'antispam';
  45      var $code = 'captcha_qstn';
  46  
  47  
  48      /**
  49       * Delete unnecessary captcha data from DB table.
  50       *
  51       * @todo This should be added to the "scheduler", once available
  52       */
  53  	function captcha_qstn_plugin()
  54      {
  55          $this->name = $this->T_('Captcha questions');
  56          $this->short_desc = $this->T_('Use questions to tell humans and robots apart.');
  57      }
  58  
  59  
  60  	function GetDefaultSettings()
  61      {
  62          global $Settings, $test_install_all_features;
  63  
  64          $default_questions = '';
  65  
  66          if( $test_install_all_features )
  67          {
  68              $default_questions = $this->T_('What is the color of the sky? blue|grey|gray|dark')."\r\n".
  69                  $this->T_('What animal is Bugs Bunny? rabbit|a rabbit')."\r\n".
  70                  $this->T_('What color is a carrot? orange|yellow')."\r\n".
  71                  $this->T_('What color is a tomato? red');
  72          }
  73  
  74          return array(
  75                  'use_for_anonymous_comment' => array(
  76                      'label' => $this->T_('Use for anonymous comment forms'),
  77                      'defaultvalue' => 1,
  78                      'note' => $this->T_('Should this plugin be used for anonymous users on comment forms?'),
  79                      'type' => 'checkbox',
  80                  ),
  81                  'use_for_registration' => array(
  82                      'label' => $this->T_('Use for new registration forms'),
  83                      'defaultvalue' => 1,
  84                      'note' => $this->T_('Should this plugin be used on registration forms?'),
  85                      'type' => 'checkbox',
  86                  ),
  87                  'use_for_anonymous_message' => array(
  88                      'label' => $this->T_('Use for anonymous messaging forms'),
  89                      'defaultvalue' => 1,
  90                      'note' => $this->T_('Should this plugin be used for anonymous users on messaging forms?'),
  91                      'type' => 'checkbox',
  92                  ),
  93                  'questions' => array(
  94                      'label' => $this->T_('Questions'),
  95                      'defaultvalue' => $default_questions,
  96                      'note' => $this->T_('Type each question in one line with following format:<br />Question text? answer1|answer2|...|answerN'),
  97                      'cols' => 80,
  98                      'rows' => 10,
  99                      'type' => 'html_textarea',
 100                  ),
 101              );
 102      }
 103  
 104  
 105      /**
 106       * We want a table to store our Captcha data (private key, timestamp, ..).
 107       *
 108       * @return array
 109       */
 110  	function GetDbLayout()
 111      {
 112          return array(
 113                  'CREATE TABLE '.$this->get_sql_table('questions').' (
 114                      cptq_ID       INT UNSIGNED NOT NULL AUTO_INCREMENT,
 115                      cptq_question VARCHAR( 255 ) NOT NULL,
 116                      cptq_answers  VARCHAR( 255 ) NOT NULL,
 117                      PRIMARY KEY( cptq_ID )
 118                  ) ENGINE = innodb DEFAULT CHARSET = utf8',
 119  
 120                  // Assign a random question ID for each IP address
 121                  'CREATE TABLE '.$this->get_sql_table('ip_question').' (
 122                      cptip_IP      INT UNSIGNED NOT NULL,
 123                      cptip_cptq_ID INT UNSIGNED NOT NULL,
 124                      KEY( cptip_IP, cptip_cptq_ID )
 125                  ) ENGINE = innodb',
 126              );
 127      }
 128  
 129  
 130      /**
 131       * Check the available questions in DB table.
 132       */
 133  	function BeforeEnable()
 134      {
 135          // Convert value of setting "questions" into DB table records
 136          $this->PluginSettingsUpdateAction();
 137  
 138          if( $error = $this->validate_questions() )
 139          {
 140              return $error;
 141          }
 142  
 143          return true;
 144      }
 145  
 146  
 147      /**
 148       * Update the questions
 149       */
 150  	function PluginSettingsUpdateAction()
 151      {
 152          global $DB;
 153  
 154          // Store in this array all questions IDs which are inserted or updated on currect action
 155          $questions_IDs = array( -1 );
 156          // Store in this array only questions with correct format
 157          $questions_formated = array();
 158  
 159          $questions = $this->Settings->get( 'questions' );
 160          $questions = explode( "\n", str_replace( "\r\n", "\n", $questions ) );
 161  
 162          foreach( $questions as $question )
 163          {
 164              if( preg_match( '/(.+\?)(.+)/i', $question, $match ) )
 165              {    // Check a question for correct format
 166                  $question = trim( $match[1] );
 167                  $answers = trim( $match[2] );
 168                  if( !empty( $question ) && !empty( $answers ) )
 169                  {    // Save this question in DB
 170                      $SQL = new SQL();
 171                      $SQL->SELECT( 'cptq_ID' );
 172                      $SQL->FROM( $this->get_sql_table( 'questions' ) );
 173                      $SQL->WHERE( 'cptq_question = '.$DB->quote( $question ) );
 174  
 175                      if( $question_ID = $DB->get_var( $SQL->get() ) )
 176                      {    // This question already exists, we should only update the answers
 177                          $DB->query( 'UPDATE '.$this->get_sql_table( 'questions' ).'
 178                                SET cptq_answers = '.$DB->quote( $answers ).'
 179                              WHERE cptq_ID = '.$question_ID );
 180                          $questions_IDs[] = $question_ID;
 181                      }
 182                      else
 183                      {    // Insert new question
 184                          $DB->query( 'INSERT INTO '.$this->get_sql_table( 'questions' ).'
 185                              ( cptq_question, cptq_answers ) VALUES
 186                              ( '.$DB->quote( $question ).', '.$DB->quote( $answers ).' )' );
 187                          $questions_IDs[] = $DB->insert_id;
 188                      }
 189  
 190                      $questions_formated[] = $question.' '.$answers;
 191                  }
 192              }
 193          }
 194  
 195          // Delete the old questions from DB
 196          $DB->query( 'DELETE FROM '.$this->get_sql_table( 'questions' ).'
 197              WHERE cptq_ID NOT IN ( '.implode( ', ', $questions_IDs ).' )' );
 198  
 199          // Resave the questions in order to delere all whitespaces and questions with incorrect format
 200          $this->Settings->set( 'questions', implode( "\r\n", $questions_formated ) );
 201          $this->Settings->dbupdate();
 202      }
 203  
 204  
 205      /**
 206       * Validate the given answer against our stored one.
 207       *
 208       * This event is provided for other plugins and gets used internally
 209       * for other events we're hooking into.
 210       *
 211       * @param array Associative array of parameters.
 212       * @param string Form type ( comment|register|message )
 213       * @return boolean|NULL
 214       */
 215  	function CaptchaValidated( & $params, $form_type )
 216      {
 217          global $DB, $localtimenow, $Session;
 218  
 219          if( ! $this->does_apply( $params, $form_type ) )
 220          {
 221              return;
 222          }
 223  
 224          $posted_answer = evo_strtolower( param( 'captcha_qstn_'.$this->ID.'_answer', 'string', '' ) );
 225  
 226          if( empty( $posted_answer ) )
 227          {
 228              $this->debug_log( 'captcha_qstn_'.$this->ID.'_answer' );
 229              $params['validate_error'] = $this->T_('Please enter the captcha answer.');
 230              return false;
 231          }
 232  
 233          $question = $this->CaptchaQuestion();
 234  
 235          $posted_answer_is_correct = false;
 236  
 237          $answers = explode( '|', evo_strtolower( $question->cptq_answers ) );
 238          foreach( $answers as $answer )
 239          {
 240              if( $posted_answer == $answer )
 241              {    // Correct answer is found in DB
 242                  $posted_answer_is_correct = true;
 243                  break;
 244              }
 245          }
 246  
 247          if( !$posted_answer_is_correct )
 248          {
 249              $this->debug_log( 'Posted ('.$posted_answer.') and saved ('.$question->cptq_answers.') answer do not match!' );
 250              $params['validate_error'] = $this->T_('The entered answer is incorrect.');
 251              return false;
 252          }
 253  
 254          // If answer is correct:
 255          //   We should clean the question ID that was assigned for current session and IP address
 256          //   It gives to assign new question on the next captcha event
 257          $this->CaptchaQuestionCleanup();
 258  
 259          return true;
 260      }
 261  
 262  
 263      /**
 264       * Get question for current user
 265       *
 266       * @return object Question row from DB table "questions"
 267       */
 268  	function CaptchaQuestion()
 269      {
 270          global $DB, $Session;
 271  
 272          $question = NULL;
 273  
 274          $IP = ip2int( $_SERVER['REMOTE_ADDR'] );
 275  
 276          // Get question ID from Session
 277          $this->question_ID = $Session->get( 'captcha_qstn_'.$this->ID.'_ID' );
 278  
 279          if( empty( $this->question_ID ) )
 280          {    // Get question from DB by current IP address
 281              $SQL = new SQL();
 282              $SQL->SELECT( 'cq.*' );
 283              $SQL->FROM( $this->get_sql_table( 'ip_question' ) );
 284              $SQL->FROM_add( 'INNER JOIN '.$this->get_sql_table( 'questions' ).' AS cq ON cptip_cptq_ID = cptq_ID' );
 285              $SQL->WHERE( 'cptip_IP = '.$DB->quote( $IP ) );
 286  
 287              if( $question = $DB->get_row( $SQL->get() ) )
 288              {
 289                  $this->question_ID = $question->cptq_ID;
 290              }
 291          }
 292  
 293          if( empty( $this->question_ID ) )
 294          {    // Assign new random question for current IP address
 295              $question = $this->CaptchaQuestionNew();
 296          }
 297  
 298          if( empty( $question ) && !empty( $this->question_ID ) )
 299          {    // Get question data
 300              $SQL = new SQL();
 301              $SQL->SELECT( '*' );
 302              $SQL->FROM( $this->get_sql_table( 'questions' ) );
 303              $SQL->WHERE( 'cptq_ID = '.$DB->quote( $this->question_ID ) );
 304              $question = $DB->get_row( $SQL->get() );
 305  
 306              if( empty( $question ) )
 307              {    // Assign random question if previous question doesn't exist in DB
 308                  // This case may happens when admin changed the questions but user has the old question ID in the session or in DB table "ip_question"
 309                  $question = $this->CaptchaQuestionNew();
 310              }
 311          }
 312  
 313          return $question;
 314      }
 315  
 316      /**
 317       * Assign new random question for current IP address
 318       *
 319       * @return object Question row from DB table "questions"
 320       */
 321  	function CaptchaQuestionNew()
 322      {
 323          global $DB, $Session;
 324  
 325          $IP = ip2int( $_SERVER['REMOTE_ADDR'] );
 326  
 327          // Get new random question from DB
 328          $SQL = new SQL();
 329          $SQL->SELECT( '*' );
 330          $SQL->FROM( $this->get_sql_table( 'questions' ) );
 331          $SQL->ORDER_BY( 'RAND()' );
 332          $SQL->LIMIT( 1 );
 333          $question = $DB->get_row( $SQL->get() );
 334  
 335          // Insert a record for current IP address with assigned question ID
 336          $DB->query( 'INSERT INTO '.$this->get_sql_table( 'ip_question' ).'
 337              ( cptip_IP, cptip_cptq_ID ) VALUES
 338              ( '.$DB->quote( $IP ).', '.$DB->quote( $question->cptq_ID ).' )' );
 339  
 340          // Save the assigned question ID in the session
 341          $Session->set( 'captcha_qstn_'.$this->ID.'_ID', $question->cptq_ID );
 342          $Session->dbsave();
 343  
 344          $this->question_ID = $question->cptq_ID;
 345  
 346          return $question;
 347      }
 348  
 349  
 350      /**
 351       * Cleanup used captcha data
 352       */
 353  	function CaptchaQuestionCleanup()
 354      {
 355          global $DB, $Session;
 356  
 357          $IP = ip2int( $_SERVER['REMOTE_ADDR'] );
 358  
 359          // Remove question ID from session
 360          $Session->delete( 'captcha_qstn_'.$this->ID.'_ID' );
 361  
 362          // Remove question ID from DB table for current IP address
 363          $DB->query( 'DELETE FROM '.$this->get_sql_table( 'ip_question' ).'
 364              WHERE cptip_IP = '.$DB->quote( $IP ) );
 365      }
 366  
 367  
 368      /**
 369       * When a comment form gets displayed, we inject our captcha and an input field to
 370       * enter the answer.
 371       *
 372       * The question ID is saved into the user's Session and in the DB table "ip_question".
 373       *
 374       * @param array Associative array of parameters
 375       *   - 'Form': the form where payload should get added (by reference, OPTIONALLY!)
 376       *   - 'form_use_fieldset':
 377       *   - 'key': A key that is associated to the caller of the event (string, OPTIONALLY!)
 378       * @param string Form type ( comment|register|message )
 379       * @return boolean|NULL true, if displayed; false, if error; NULL if it does not apply
 380       */
 381  	function CaptchaPayload( & $params, $form_type )
 382      {
 383          global $DB, $Session;
 384  
 385          if( ! $this->does_apply( $params, $form_type ) )
 386          {
 387              return;
 388          }
 389  
 390          $question = $this->CaptchaQuestion();
 391  
 392          if( empty( $question ) )
 393          {    // No the defined questions
 394              return;
 395          }
 396  
 397          $this->debug_log( 'Question ID is: ('.$this->question_ID.')' );
 398  
 399          if( ! isset( $params['Form'] ) )
 400          {    // there's no Form where we add to, but we create our own form:
 401              $Form = new Form( regenerate_url() );
 402              $Form->begin_form();
 403          }
 404          else
 405          {
 406              $Form = & $params['Form'];
 407              if( ! isset( $params['form_use_fieldset'] ) || $params['form_use_fieldset'] )
 408              {
 409                  $Form->begin_fieldset();
 410              }
 411          }
 412  
 413          $Form->info( $this->T_('Captcha question'), $question->cptq_question );
 414          $Form->text_input( 'captcha_qstn_'.$this->ID.'_answer', param( 'captcha_qstn_'.$this->ID.'_answer', 'string', '' ), 10, $this->T_('Captcha answer'), $this->T_('Please answer on question above.') );
 415  
 416          if( ! isset($params['Form']) )
 417          {    // there's no Form where we add to, but our own form:
 418              $Form->end_form( array( array( 'submit', 'submit', $this->T_('Validate me'), 'ActionButton' ) ) );
 419          }
 420          else
 421          {
 422              if( ! isset($params['form_use_fieldset']) || $params['form_use_fieldset'] )
 423              {
 424                  $Form->end_fieldset();
 425              }
 426          }
 427  
 428          return true;
 429      }
 430  
 431  
 432      /**
 433       * We display our captcha with comment forms.
 434       */
 435  	function DisplayCommentFormFieldset( & $params )
 436      {
 437          $this->CaptchaPayload( $params, 'comment' );
 438      }
 439  
 440  
 441      /**
 442       * Validate the answer against our stored one.
 443       *
 444       * In case of error we add a message of category 'error' which prevents the comment from
 445       * being posted.
 446       *
 447       * @param array Associative array of parameters.
 448       * @param string Form type ( comment|register|message )
 449       */
 450  	function BeforeCommentFormInsert( & $params, $form_type = 'comment' )
 451      {
 452          if( ! empty( $params['is_preview'] ) )
 453          {
 454              return;
 455          }
 456  
 457          if( $this->CaptchaValidated( $params, $form_type ) === false )
 458          {
 459              $validate_error = $params['validate_error'];
 460              param_error( 'captcha_qstn_'.$this->ID.'_answer', $validate_error );
 461          }
 462      }
 463  
 464  
 465      /**
 466       * We display our captcha with the register form.
 467       */
 468  	function DisplayRegisterFormFieldset( & $params )
 469      {
 470          $this->CaptchaPayload( $params, 'register' );
 471      }
 472  
 473  
 474      /**
 475       * Validate the given private key against our stored one.
 476       *
 477       * In case of error we add a message of category 'error' which prevents the
 478       * user from being registered.
 479       */
 480  	function RegisterFormSent( & $params )
 481      {
 482          $this->BeforeCommentFormInsert( $params, 'register' ); // we do the same as when validating comment forms
 483      }
 484  
 485  
 486      /**
 487       * We display our captcha with the message form.
 488       */
 489  	function DisplayMessageFormFieldset( & $params )
 490      {
 491          $this->CaptchaPayload( $params, 'message' );
 492      }
 493  
 494  
 495      /**
 496       * Validate the given private key against our stored one.
 497       *
 498       * In case of error we add a message of category 'error' which prevents the
 499       * user from being registered.
 500       */
 501  	function MessageFormSent( & $params )
 502      {
 503          $this->BeforeCommentFormInsert( $params, 'message' ); // we do the same as when validating comment forms
 504      }
 505  
 506  
 507      /* PRIVATE methods */
 508  
 509      /**
 510       * Checks if we should captcha the current request, according to the settings made.
 511       *
 512       * @param array Associative array of parameters.
 513       * @param string Form type ( comment|register|message )
 514       * @return boolean
 515       */
 516  	function does_apply( $params, $form_type )
 517      {
 518          global $current_User;
 519  
 520          switch( $form_type )
 521          {
 522              case 'comment':
 523                  if( !is_logged_in() )
 524                  {
 525                      return $this->Settings->get( 'use_for_anonymous_comment' );
 526                  }
 527                  break;
 528  
 529              case 'register':
 530                  return $this->Settings->get( 'use_for_registration' );
 531  
 532              case 'message':
 533                  if( !is_logged_in() )
 534                  {
 535                      return $this->Settings->get( 'use_for_anonymous_message' );
 536                  }
 537                  break;
 538          }
 539  
 540          return false;
 541      }
 542  
 543  
 544      /**
 545       * Check if questions exist in DB.
 546       *
 547       * @access private
 548       */
 549  	function validate_questions()
 550      {
 551          global $DB;
 552  
 553          $SQL = new SQL();
 554          $SQL->SELECT( 'cptq_ID' );
 555          $SQL->FROM( $this->get_sql_table( 'questions' ) );
 556  
 557          if( ! $DB->get_var( $SQL->get() ) )
 558          {
 559              return $this->T_( 'Not Enabled: You should create at least one question!' );
 560          }
 561      }
 562  
 563  }
 564  
 565  ?>

title

Description

title

Description

title

Description

title

title

Body