AutorÃa | Ultima modificación | Ver Log |
<?php// This file is part of Moodle - http://moodle.org///// Moodle is free software: you can redistribute it and/or modify// it under the terms of the GNU General Public License as published by// the Free Software Foundation, either version 3 of the License, or// (at your option) any later version.//// Moodle is distributed in the hope that it will be useful,// but WITHOUT ANY WARRANTY; without even the implied warranty of// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the// GNU General Public License for more details.//// You should have received a copy of the GNU General Public License// along with Moodle. If not, see <http://www.gnu.org/licenses/>./*** Privacy Fetch Result Set.** @package core_privacy* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/namespace core_privacy\local\request;defined('MOODLE_INTERNAL') || die();/*** Privacy Fetch Result Set.** @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/class contextlist extends contextlist_base {/*** Add a set of contexts from SQL.** The SQL should only return a list of context IDs.** @param string $sql The SQL which will fetch the list of * context IDs* @param array $params The set of SQL parameters* @return $this*/public function add_from_sql(string $sql, array $params): contextlist {global $DB;$fields = \context_helper::get_preload_record_columns_sql('ctx');if ($fieldname = $this->guess_id_field_from_sql($sql)) {if (is_numeric($fieldname)) {$wrapper = "SELECT {$fields}FROM {context} ctxWHERE ctx.id = :fieldvalue";$params = ['fieldvalue' => $fieldname];} else {// Able to guess a field name.$wrapper = "SELECT {$fields}FROM {context} ctxJOIN ({$sql}) target ON ctx.id = target.{$fieldname}";}} else {// No field name available. Fall back on a potentially slower version.$wrapper = "SELECT {$fields}FROM {context} ctxWHERE ctx.id IN ({$sql})";}$contexts = $DB->get_recordset_sql($wrapper, $params);$contextids = [];foreach ($contexts as $context) {$contextids[] = $context->ctxid;\context_helper::preload_from_record($context);}$contexts->close();$this->set_contextids(array_merge($this->get_contextids(), $contextids));return $this;}/*** Adds the system context.** @return $this*/public function add_system_context(): contextlist {return $this->add_from_sql(SYSCONTEXTID, []);}/*** Adds the user context for a given user.** @param int $userid* @return $this*/public function add_user_context(int $userid): contextlist {$sql = "SELECT DISTINCT ctx.idFROM {context} ctxWHERE ctx.contextlevel = :contextlevelAND ctx.instanceid = :instanceid";return $this->add_from_sql($sql, ['contextlevel' => CONTEXT_USER, 'instanceid' => $userid]);}/*** Adds the user contexts for given users.** @param array $userids* @return $this*/public function add_user_contexts(array $userids): contextlist {global $DB;list($useridsql, $useridparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);$sql = "SELECT DISTINCT ctx.idFROM {context} ctxWHERE ctx.contextlevel = :contextlevelAND ctx.instanceid $useridsql";return $this->add_from_sql($sql, ['contextlevel' => CONTEXT_USER] + $useridparams);}/*** Sets the component for this contextlist.** @param string $component the frankenstyle component name.*/public function set_component($component) {parent::set_component($component);}/*** Guess the name of the contextid field from the supplied SQL.** @param string $sql The SQL to guess from* @return string The field name or a numeric value representing the context id*/protected function guess_id_field_from_sql(string $sql): string {// We are not interested in any subquery/view/conditions for the purpose of this method, so// let's reduce the query to the interesting parts by recursively cleaning all// contents within parenthesis. If there are problems (null), we keep the text unmodified.// So just top-level sql will remain after the reduction.$recursiveregexp = '/\((([^()]*|(?R))*)\)/';$sql = (preg_replace($recursiveregexp, '', $sql) ?: $sql);// Get the list of relevant words from the SQL Query.// We explode the SQL by the space character, then trim any extra whitespace (e.g. newlines), before we filter// empty value, and finally we re-index the array.$sql = rtrim($sql, ';');$words = array_map('trim', preg_split('/\s+/', $sql));$words = array_filter($words, function($word) {return $word !== '';});$words = array_values($words);$uwords = array_map('strtoupper', $words); // Uppercase all them.// If the query has boolean operators (UNION, it is the only one we support cross-db)// then we cannot guarantee whats coming after the first query, it can be anything.if (array_search('UNION', $uwords)) {return '';}if ($firstfrom = array_search('FROM', $uwords)) {// Found a FROM keyword.// Select the previous word.$fieldname = $words[$firstfrom - 1];if (is_numeric($fieldname)) {return $fieldname;}if ($hasdot = strpos($fieldname, '.')) {// This field is against a table alias. Take off the alias.$fieldname = substr($fieldname, $hasdot + 1);}return $fieldname;} else if ((count($words) == 1) && (is_numeric($words[0]))) {// Not a real SQL, just a single numerical value - such as one returned by {@link self::add_system_context()}.return $words[0];} else if ((count($words) == 2) && (strtoupper($words[0]) === 'SELECT') && (is_numeric($words[1]))) {// SQL returning a constant numerical value.return $words[1];}return '';}}