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 /** 18 * Privacy Fetch Result Set. 19 * 20 * @package core_privacy 21 * @copyright 2018 Andrew Nicols <andrew@nicols.co.uk> 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 namespace core_privacy\local\request; 26 27 defined('MOODLE_INTERNAL') || die(); 28 29 /** 30 * Privacy Fetch Result Set. 31 * 32 * @copyright 2018 Andrew Nicols <andrew@nicols.co.uk> 33 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 34 */ 35 class contextlist extends contextlist_base { 36 37 /** 38 * Add a set of contexts from SQL. 39 * 40 * The SQL should only return a list of context IDs. 41 * 42 * @param string $sql The SQL which will fetch the list of * context IDs 43 * @param array $params The set of SQL parameters 44 * @return $this 45 */ 46 public function add_from_sql(string $sql, array $params) : contextlist { 47 global $DB; 48 49 $fields = \context_helper::get_preload_record_columns_sql('ctx'); 50 if ($fieldname = $this->guess_id_field_from_sql($sql)) { 51 if (is_numeric($fieldname)) { 52 $wrapper = " 53 SELECT {$fields} 54 FROM {context} ctx 55 WHERE ctx.id = :fieldvalue"; 56 $params = ['fieldvalue' => $fieldname]; 57 } else { 58 // Able to guess a field name. 59 $wrapper = " 60 SELECT {$fields} 61 FROM {context} ctx 62 JOIN ({$sql}) target ON ctx.id = target.{$fieldname}"; 63 } 64 } else { 65 // No field name available. Fall back on a potentially slower version. 66 $wrapper = " 67 SELECT {$fields} 68 FROM {context} ctx 69 WHERE ctx.id IN ({$sql})"; 70 } 71 $contexts = $DB->get_recordset_sql($wrapper, $params); 72 73 $contextids = []; 74 foreach ($contexts as $context) { 75 $contextids[] = $context->ctxid; 76 \context_helper::preload_from_record($context); 77 } 78 $contexts->close(); 79 80 $this->set_contextids(array_merge($this->get_contextids(), $contextids)); 81 82 return $this; 83 } 84 85 /** 86 * Adds the system context. 87 * 88 * @return $this 89 */ 90 public function add_system_context() : contextlist { 91 return $this->add_from_sql(SYSCONTEXTID, []); 92 } 93 94 /** 95 * Adds the user context for a given user. 96 * 97 * @param int $userid 98 * @return $this 99 */ 100 public function add_user_context(int $userid) : contextlist { 101 $sql = "SELECT DISTINCT ctx.id 102 FROM {context} ctx 103 WHERE ctx.contextlevel = :contextlevel 104 AND ctx.instanceid = :instanceid"; 105 return $this->add_from_sql($sql, ['contextlevel' => CONTEXT_USER, 'instanceid' => $userid]); 106 } 107 108 /** 109 * Adds the user contexts for given users. 110 * 111 * @param array $userids 112 * @return $this 113 */ 114 public function add_user_contexts(array $userids) : contextlist { 115 global $DB; 116 117 list($useridsql, $useridparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED); 118 $sql = "SELECT DISTINCT ctx.id 119 FROM {context} ctx 120 WHERE ctx.contextlevel = :contextlevel 121 AND ctx.instanceid $useridsql"; 122 return $this->add_from_sql($sql, ['contextlevel' => CONTEXT_USER] + $useridparams); 123 } 124 125 /** 126 * Sets the component for this contextlist. 127 * 128 * @param string $component the frankenstyle component name. 129 */ 130 public function set_component($component) { 131 parent::set_component($component); 132 } 133 134 /** 135 * Guess the name of the contextid field from the supplied SQL. 136 * 137 * @param string $sql The SQL to guess from 138 * @return string The field name or a numeric value representing the context id 139 */ 140 protected function guess_id_field_from_sql(string $sql) : string { 141 // We are not interested in any subquery/view/conditions for the purpose of this method, so 142 // let's reduce the query to the interesting parts by recursively cleaning all 143 // contents within parenthesis. If there are problems (null), we keep the text unmodified. 144 // So just top-level sql will remain after the reduction. 145 $recursiveregexp = '/\((([^()]*|(?R))*)\)/'; 146 $sql = (preg_replace($recursiveregexp, '', $sql) ?: $sql); 147 // Get the list of relevant words from the SQL Query. 148 // We explode the SQL by the space character, then trim any extra whitespace (e.g. newlines), before we filter 149 // empty value, and finally we re-index the array. 150 $sql = rtrim($sql, ';'); 151 $words = array_map('trim', preg_split('/\s+/', $sql)); 152 $words = array_filter($words, function($word) { 153 return $word !== ''; 154 }); 155 $words = array_values($words); 156 $uwords = array_map('strtoupper', $words); // Uppercase all them. 157 158 // If the query has boolean operators (UNION, it is the only one we support cross-db) 159 // then we cannot guarantee whats coming after the first query, it can be anything. 160 if (array_search('UNION', $uwords)) { 161 return ''; 162 } 163 164 if ($firstfrom = array_search('FROM', $uwords)) { 165 // Found a FROM keyword. 166 // Select the previous word. 167 $fieldname = $words[$firstfrom - 1]; 168 if (is_numeric($fieldname)) { 169 return $fieldname; 170 } 171 172 if ($hasdot = strpos($fieldname, '.')) { 173 // This field is against a table alias. Take off the alias. 174 $fieldname = substr($fieldname, $hasdot + 1); 175 } 176 177 return $fieldname; 178 179 } else if ((count($words) == 1) && (is_numeric($words[0]))) { 180 // Not a real SQL, just a single numerical value - such as one returned by {@link self::add_system_context()}. 181 return $words[0]; 182 183 } else if ((count($words) == 2) && (strtoupper($words[0]) === 'SELECT') && (is_numeric($words[1]))) { 184 // SQL returning a constant numerical value. 185 return $words[1]; 186 } 187 188 return ''; 189 } 190 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body