Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

Differences Between: [Versions 400 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16   // This file is part of Moodle - http://moodle.org/
  17  //
  18  // Moodle is free software: you can redistribute it and/or modify
  19  // it under the terms of the GNU General Public License as published by
  20  // the Free Software Foundation, either version 3 of the License, or
  21  // (at your option) any later version.
  22  //
  23  // Moodle is distributed in the hope that it will be useful,
  24  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  25  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  26  // GNU General Public License for more details.
  27  //
  28  // You should have received a copy of the GNU General Public License
  29  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  30  
  31  /**
  32   * This is a PHP library that handles calling reCAPTCHA v2.
  33   *
  34   *    - Documentation
  35   *          {@link https://developers.google.com/recaptcha/docs/display}
  36   *    - Get a reCAPTCHA API Key
  37   *          {@link https://www.google.com/recaptcha/admin}
  38   *    - Discussion group
  39   *          {@link http://groups.google.com/group/recaptcha}
  40   *
  41   * @package core
  42   * @copyright 2018 Jeff Webster
  43   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  44   */
  45  
  46  defined('MOODLE_INTERNAL') || die();
  47  
  48  /**
  49   * The reCAPTCHA URL's
  50   */
  51  define('RECAPTCHA_API_URL', 'https://www.recaptcha.net/recaptcha/api.js');
  52  define('RECAPTCHA_VERIFY_URL', 'https://www.recaptcha.net/recaptcha/api/siteverify');
  53  
  54  /**
  55   * Returns the language code the reCAPTCHA element should use.
  56   * Google reCAPTCHA uses different language codes than Moodle so we must convert.
  57   * https://developers.google.com/recaptcha/docs/language
  58   *
  59   * @param string $lang Language to use. If not provided, get current language.
  60   * @return string A language code
  61   */
  62  function recaptcha_lang($lang = null) {
  63  
  64      if (empty($lang)) {
  65          $lang = current_language();
  66      }
  67  
  68      $glang = $lang;
  69      switch ($glang) {
  70          case 'en':
  71              $glang = 'en-GB';
  72              break;
  73          case 'en_us':
  74              $glang = 'en';
  75              break;
  76          case 'zh_cn':
  77              $glang = 'zh-CN';
  78              break;
  79          case 'zh_tw':
  80              $glang = 'zh-TW';
  81              break;
  82          case 'fr_ca':
  83              $glang = 'fr-CA';
  84              break;
  85          case 'pt_br':
  86              $glang = 'pt-BR';
  87              break;
  88          case 'he':
  89              $glang = 'iw';
  90              break;
  91      }
  92      // For any language code that didn't change reduce down to the base language.
  93      if (($lang === $glang) and (strpos($lang, '_') !== false)) {
  94          list($glang, $trash) = explode('_', $lang, 2);
  95      }
  96      return $glang;
  97  }
  98  
  99  /**
 100   * Gets the challenge HTML
 101   * This is called from the browser, and the resulting reCAPTCHA HTML widget
 102   * is embedded within the HTML form it was called from.
 103   *
 104   * @param string $apiurl URL for reCAPTCHA API
 105   * @param string $pubkey The public key for reCAPTCHA
 106   * @param string $lang Language to use. If not provided, get current language.
 107   * @return string - The HTML to be embedded in the user's form.
 108   */
 109  function recaptcha_get_challenge_html($apiurl, $pubkey, $lang = null) {
 110      global $CFG, $PAGE;
 111  
 112      // To use reCAPTCHA you must have an API key.
 113      if ($pubkey === null || $pubkey === '') {
 114          return get_string('getrecaptchaapi', 'auth');
 115      }
 116  
 117      $jscode = "
 118          var recaptchacallback = function() {
 119              grecaptcha.render('recaptcha_element', {
 120                'sitekey' : '$pubkey'
 121              });
 122          }";
 123  
 124      $lang = recaptcha_lang($lang);
 125      $apicode = "\n<script type=\"text/javascript\" ";
 126      $apicode .= "src=\"$apiurl?onload=recaptchacallback&render=explicit&hl=$lang\" async defer>";
 127      $apicode .= "</script>\n";
 128  
 129      $return = html_writer::script($jscode, '');
 130      $return .= html_writer::div('', 'recaptcha_element', array('id' => 'recaptcha_element'));
 131      $return .= $apicode;
 132  
 133      return $return;
 134  }
 135  
 136  /**
 137   * Calls an HTTP POST function to verify if the user's response was correct
 138   *
 139   * @param string $verifyurl URL for reCAPTCHA verification
 140   * @param string $privkey The private key for reCAPTCHA
 141   * @param string $remoteip The user's IP
 142   * @param string $response The response from reCAPTCHA
 143   * @return ReCaptchaResponse
 144   */
 145  function recaptcha_check_response($verifyurl, $privkey, $remoteip, $response) {
 146      global $CFG;
 147      require_once($CFG->libdir.'/filelib.php');
 148  
 149      // Check response - isvalid boolean, error string.
 150      $checkresponse = array('isvalid' => false, 'error' => 'check-not-started');
 151  
 152      // To use reCAPTCHA you must have an API key.
 153      if ($privkey === null || $privkey === '') {
 154          $checkresponse['isvalid'] = false;
 155          $checkresponse['error'] = 'no-apikey';
 156          return $checkresponse;
 157      }
 158  
 159      // For security reasons, you must pass the remote ip to reCAPTCHA.
 160      if ($remoteip === null || $remoteip === '') {
 161          $checkresponse['isvalid'] = false;
 162          $checkresponse['error'] = 'no-remoteip';
 163          return $checkresponse;
 164      }
 165  
 166      // Discard spam submissions.
 167      if ($response === null || strlen($response) === 0) {
 168          $checkresponse['isvalid'] = false;
 169          $checkresponse['error'] = 'incorrect-captcha-sol';
 170          return $checkresponse;
 171      }
 172  
 173      $params = array('secret' => $privkey, 'remoteip' => $remoteip, 'response' => $response);
 174      $curl = new curl();
 175      $curlresponse = $curl->post($verifyurl, $params);
 176  
 177      if ($curl->get_errno() === 0) {
 178          $curldata = json_decode($curlresponse);
 179  
 180          if (isset($curldata->success) && $curldata->success === true) {
 181              $checkresponse['isvalid'] = true;
 182              $checkresponse['error'] = '';
 183          } else {
 184              $checkresponse['isvalid'] = false;
 185              $checkresponse['error'] = $curldata->{error-codes};
 186          }
 187      } else {
 188          $checkresponse['isvalid'] = false;
 189          $checkresponse['error'] = 'check-failed';
 190      }
 191      return $checkresponse;
 192  }
 193