Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
}