Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.
   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  
  17  namespace core;
  18  
  19  use core\local\guzzle\cache_handler;
  20  use core\local\guzzle\cache_storage;
  21  use core\local\guzzle\check_request;
  22  use core\local\guzzle\redirect_middleware;
  23  use GuzzleHttp\Client;
  24  use GuzzleHttp\HandlerStack;
  25  use GuzzleHttp\RequestOptions;
  26  use Kevinrob\GuzzleCache\CacheMiddleware;
  27  use Kevinrob\GuzzleCache\Strategy\PrivateCacheStrategy;
  28  
  29  /**
  30   * Guzzle Integration for Moodle.
  31   *
  32   * @package   core
  33   * @copyright 2022 Andrew Lyons <andrew@nicols.co.uk>
  34   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  35   */
  36  class http_client extends Client {
  37  
  38      public function __construct(array $config = []) {
  39          $config = $this->get_options($config);
  40  
  41          parent::__construct($config);
  42      }
  43  
  44      /**
  45       * Get the custom options and handlers for guzzle integration in moodle.
  46       *
  47       * @param array $settings The settings or options from client.
  48       * @return array
  49       */
  50      protected function get_options(array $settings): array {
  51          if (empty($settings['handler'])) {
  52              // Configure the default handlers.
  53              $settings['handler'] = $this->get_handlers($settings);
  54          }
  55  
  56          // Request debugging {@link https://docs.guzzlephp.org/en/stable/request-options.html#debug}.
  57          if (!empty($settings[RequestOptions::DEBUG])) {
  58              // Accepts either a bool, or fopen resource.
  59              if (!is_resource($settings[RequestOptions::DEBUG])) {
  60                  $settings[RequestOptions::DEBUG] = !empty($settings['debug']);
  61              }
  62          }
  63  
  64          // Proxy.
  65          $proxy = $this->setup_proxy($settings);
  66          if (!empty($proxy)) {
  67              $settings[RequestOptions::PROXY] = $proxy;
  68          }
  69  
  70          // Add the default user-agent header.
  71          if (!isset($settings['headers'])) {
  72              $settings['headers'] = ['User-Agent' => \core_useragent::get_moodlebot_useragent()];
  73          } else if (is_array($settings['headers'])) {
  74              $headers = array_keys(array_change_key_case($settings['headers']));
  75              // Add the User-Agent header if one was not already set.
  76              if (!in_array('user-agent', $headers)) {
  77                  $settings['headers']['User-Agent'] = \core_useragent::get_moodlebot_useragent();
  78              }
  79          }
  80  
  81          return $settings;
  82      }
  83  
  84      /**
  85       * Get the handler stack according to the settings/options from client.
  86       *
  87       * @param array $settings The settings or options from client.
  88       * @return HandlerStack
  89       */
  90      protected function get_handlers(array $settings): HandlerStack {
  91          global $CFG;
  92          // If a mock handler is set, add to stack. Mainly used for tests.
  93          if (isset($settings['mock'])) {
  94              $stack = HandlerStack::create($settings['mock']);
  95          } else {
  96              $stack = HandlerStack::create();
  97          }
  98  
  99          // Ensure that the first piece of middleware checks the block list.
 100          $stack->unshift(check_request::setup($settings), 'moodle_check_initial_request');
 101  
 102          // Replace the standard redirect handler with our custom Moodle one.
 103          // This handler checks the block list.
 104          // It extends the standard 'allow_redirects' handler so supports the same options.
 105          $stack->after('allow_redirects', redirect_middleware::setup($settings), 'moodle_allow_redirect');
 106          $stack->remove('allow_redirects');
 107  
 108          // Use cache middleware if cache is enabled.
 109          if (!empty($settings['cache'])) {
 110              $module = 'misc';
 111              if (!empty($settings['module_cache'])) {
 112                  $module = $settings['module_cache'];
 113              }
 114  
 115              // Set TTL for the cache.
 116              if ($module === 'repository') {
 117                  if (empty($CFG->repositorycacheexpire)) {
 118                      $CFG->repositorycacheexpire = 120;
 119                  }
 120                  $ttl = $CFG->repositorycacheexpire;
 121              } else {
 122                  if (empty($CFG->curlcache)) {
 123                      $CFG->curlcache = 120;
 124                  }
 125                  $ttl = $CFG->curlcache;
 126              }
 127  
 128              $stack->push(new CacheMiddleware (new PrivateCacheStrategy (new cache_storage (new cache_handler($module), $ttl))),
 129                      'cache');
 130          }
 131  
 132          return $stack;
 133      }
 134  
 135      /**
 136       * Get the proxy configuration.
 137       *
 138       * @see {https://docs.guzzlephp.org/en/stable/request-options.html#proxy}
 139       * @param array $settings The incoming settings.
 140       * @return array The proxy settings
 141       */
 142      protected function setup_proxy(array $settings): ?array {
 143          global $CFG;
 144  
 145          if (empty($CFG->proxyhost)) {
 146              return null;
 147          }
 148  
 149          $proxy = $this->get_proxy($settings);
 150          $noproxy = [];
 151  
 152          if (!empty($CFG->proxybypass)) {
 153              $noproxy = array_map(function(string $hostname): string {
 154                  return trim($hostname);
 155              }, explode(',', $CFG->proxybypass));
 156          }
 157  
 158          return [
 159              'http' => $proxy,
 160              'https' => $proxy,
 161              'no' => $noproxy,
 162          ];
 163      }
 164  
 165      /**
 166       * Get the proxy server identified.
 167       *
 168       * @param array $settings The incoming settings.
 169       * @return string The URI for the Proxy Server
 170       */
 171      protected function get_proxy(array $settings): string {
 172          global $CFG;
 173          $proxyhost = $CFG->proxyhost;
 174          if (!empty($CFG->proxyport)) {
 175              $proxyhost = "{$CFG->proxyhost}:{$CFG->proxyport}";
 176          }
 177  
 178          $proxyauth = "";
 179          if (!empty($CFG->proxyuser) && !empty($CFG->proxypassword)) {
 180              $proxyauth = "{$CFG->proxyuser}{$CFG->proxypassword}";
 181          }
 182  
 183          $protocol = "http://";
 184          if (!empty($CFG->proxytype) && $CFG->proxytype === 'SOCKS5') {
 185              $protocol = "socks5://";
 186          }
 187  
 188          return "{$protocol}{$proxyauth}{$proxyhost}";
 189      }
 190  }