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 401] [Versions 400 and 402] [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  
  17  declare(strict_types=1);
  18  
  19  namespace core_reportbuilder\local\helpers;
  20  
  21  use coding_exception;
  22  use core_text;
  23  
  24  /**
  25   * Helper functions for DB manipulations
  26   *
  27   * @package     core_reportbuilder
  28   * @copyright   2019 Marina Glancy
  29   * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  30   */
  31  class database {
  32  
  33      /** @var string Prefix for generated aliases */
  34      private const GENERATE_ALIAS_PREFIX = 'rbalias';
  35  
  36      /** @var string Prefix for generated param names names */
  37      private const GENERATE_PARAM_PREFIX = 'rbparam';
  38  
  39      /**
  40       * Generates unique table/column alias that must be used in generated SQL
  41       *
  42       * @return string
  43       */
  44      public static function generate_alias(): string {
  45          static $aliascount = 0;
  46  
  47          return static::GENERATE_ALIAS_PREFIX . ($aliascount++);
  48      }
  49      /**
  50       * Generates unique parameter name that must be used in generated SQL
  51       *
  52       * @return string
  53       */
  54      public static function generate_param_name(): string {
  55          static $paramcount = 0;
  56  
  57          return static::GENERATE_PARAM_PREFIX . ($paramcount++);
  58      }
  59  
  60      /**
  61       * Validate that parameter names were generated using {@see generate_param_name}.
  62       *
  63       * @param array $params
  64       * @return bool
  65       * @throws coding_exception For invalid params.
  66       */
  67      public static function validate_params(array $params): bool {
  68          $nonmatchingkeys = array_filter($params, static function($key): bool {
  69              return !preg_match('/^' . static::GENERATE_PARAM_PREFIX . '[\d]+/', $key);
  70          }, ARRAY_FILTER_USE_KEY);
  71  
  72          if (!empty($nonmatchingkeys)) {
  73              throw new coding_exception('Invalid parameter names', implode(', ', array_keys($nonmatchingkeys)));
  74          }
  75  
  76          return true;
  77      }
  78  
  79      /**
  80       * Replace parameter names within given SQL expression, allowing caller to specify callback to handle their replacement
  81       * primarily to ensure uniqueness when the expression is to be used as part of a larger query
  82       *
  83       * @param string $sql
  84       * @param array $params
  85       * @param callable $callback Method that takes a single string parameter, and returns another string
  86       * @return string
  87       */
  88      public static function sql_replace_parameter_names(string $sql, array $params, callable $callback): string {
  89          foreach ($params as $param) {
  90  
  91              // Pattern to look for param within the SQL.
  92              $pattern = '/:(?<param>' . preg_quote($param) . ')\b/';
  93  
  94              $sql = preg_replace_callback($pattern, function(array $matches) use ($callback): string {
  95                  return ':' . $callback($matches['param']);
  96              }, $sql);
  97          }
  98  
  99          return $sql;
 100      }
 101  
 102      /**
 103       * Generate SQL expression for sorting group concatenated fields
 104       *
 105       * @param string $field The original field or SQL expression
 106       * @param string|null $sort A valid SQL ORDER BY to sort the concatenated fields, if omitted then $field will be used
 107       * @return string
 108       */
 109      public static function sql_group_concat_sort(string $field, string $sort = null): string {
 110          global $DB;
 111  
 112          // Fallback to sorting by the specified field, unless it contains parameters which would be duplicated.
 113          if ($sort === null && !preg_match('/[:?$]/', $field)) {
 114              $fieldsort = $field;
 115          } else {
 116              $fieldsort = $sort;
 117          }
 118  
 119          // Nothing to sort by.
 120          if ($fieldsort === null) {
 121              return '';
 122          }
 123  
 124          // If the sort specifies a direction, we need to handle that differently in Postgres.
 125          if ($DB->get_dbfamily() === 'postgres') {
 126              $fieldsortdirection = '';
 127  
 128              preg_match('/(?<direction>ASC|DESC)?$/i', $fieldsort, $matches);
 129              if (array_key_exists('direction', $matches)) {
 130                  $fieldsortdirection = $matches['direction'];
 131                  $fieldsort = core_text::substr($fieldsort, 0, -(core_text::strlen($fieldsortdirection)));
 132              }
 133  
 134              // Cast sort, stick the direction on the end.
 135              $fieldsort = "CAST({$fieldsort} AS VARCHAR) {$fieldsortdirection}";
 136          }
 137  
 138          return $fieldsort;
 139      }
 140  }