Re-applying r34440 (documenting ApiQueryBase)
[lhc/web/wiklou.git] / includes / api / ApiQueryBase.php
1 <?php
2
3 /*
4 * Created on Sep 7, 2006
5 *
6 * API for MediaWiki 1.8+
7 *
8 * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License along
21 * with this program; if not, write to the Free Software Foundation, Inc.,
22 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 * http://www.gnu.org/copyleft/gpl.html
24 */
25
26 if (!defined('MEDIAWIKI')) {
27 // Eclipse helper - will be ignored in production
28 require_once ('ApiBase.php');
29 }
30
31 /**
32 * This is a base class for all Query modules.
33 * It provides some common functionality such as constructing various SQL queries.
34 *
35 * @addtogroup API
36 */
37 abstract class ApiQueryBase extends ApiBase {
38
39 private $mQueryModule, $mDb, $tables, $where, $fields, $options;
40
41 public function __construct($query, $moduleName, $paramPrefix = '') {
42 parent :: __construct($query->getMain(), $moduleName, $paramPrefix);
43 $this->mQueryModule = $query;
44 $this->mDb = null;
45 $this->resetQueryParams();
46 }
47
48 /**
49 * Blank the internal arrays with query parameters
50 */
51 protected function resetQueryParams() {
52 $this->tables = array ();
53 $this->where = array ();
54 $this->fields = array ();
55 $this->options = array ();
56 }
57
58 /**
59 * Add a set of tables to the internal array
60 * @param mixed $tables Table name or array of table names
61 * @param mixed $alias Table alias, or null for no alias. Cannot be used with multiple tables
62 */
63 protected function addTables($tables, $alias = null) {
64 if (is_array($tables)) {
65 if (!is_null($alias))
66 ApiBase :: dieDebug(__METHOD__, 'Multiple table aliases not supported');
67 $this->tables = array_merge($this->tables, $tables);
68 } else {
69 if (!is_null($alias))
70 $tables = $this->getDB()->tableName($tables) . ' ' . $alias;
71 $this->tables[] = $tables;
72 }
73 }
74
75 /**
76 * Add a set of fields to select to the internal array
77 * @param mixed $value Field name or array of field names
78 */
79 protected function addFields($value) {
80 if (is_array($value))
81 $this->fields = array_merge($this->fields, $value);
82 else
83 $this->fields[] = $value;
84 }
85
86 /**
87 * Same as addFields(), but add the fields only if a condition is met
88 * @param mixed $value See addFields()
89 * @param bool $condition If false, do nothing
90 * @return bool $condition
91 */
92 protected function addFieldsIf($value, $condition) {
93 if ($condition) {
94 $this->addFields($value);
95 return true;
96 }
97 return false;
98 }
99
100 /**
101 * Add a set of WHERE clauses to the internal array.
102 * Clauses can be formatted as 'foo=bar' or array('foo' => 'bar'),
103 * the latter only works if the value is a constant (i.e. not another field)
104 *
105 * For example, array('foo=bar', 'baz' => 3, 'bla' => 'foo') translates
106 * to "foo=bar AND baz='3' AND bla='foo'"
107 * @param mixed $value String or array
108 */
109 protected function addWhere($value) {
110 if (is_array($value))
111 $this->where = array_merge($this->where, $value);
112 else
113 $this->where[] = $value;
114 }
115
116 /**
117 * Same as addWhere(), but add the WHERE clauses only if a condition is met
118 * @param mixed $value See addWhere()
119 * @param bool $condition If false, do nothing
120 * @return bool $condition
121 */
122 protected function addWhereIf($value, $condition) {
123 if ($condition) {
124 $this->addWhere($value);
125 return true;
126 }
127 return false;
128 }
129
130 /**
131 * Equivalent to addWhere(array($field => $value))
132 * @param string $field Field name
133 * @param string $value Value; ignored if nul;
134 */
135 protected function addWhereFld($field, $value) {
136 if (!is_null($value))
137 $this->where[$field] = $value;
138 }
139
140 /**
141 * Add a WHERE clause corresponding to a range, and an ORDER BY
142 * clause to sort in the right direction
143 * @param string $field Field name
144 * @param string $dir If 'newer', sort in ascending order, otherwise sort in descending order
145 * @param string $start Value to start the list at. If $dir == 'newer' this is the lower boundary, otherwise it's the upper boundary
146 * @param string $end Value to end the list at. If $dir == 'newer' this is the upper boundary, otherwise it's the lower boundary
147 */
148 protected function addWhereRange($field, $dir, $start, $end) {
149 $isDirNewer = ($dir === 'newer');
150 $after = ($isDirNewer ? '>=' : '<=');
151 $before = ($isDirNewer ? '<=' : '>=');
152 $db = $this->getDB();
153
154 if (!is_null($start))
155 $this->addWhere($field . $after . $db->addQuotes($start));
156
157 if (!is_null($end))
158 $this->addWhere($field . $before . $db->addQuotes($end));
159
160 $order = $field . ($isDirNewer ? '' : ' DESC');
161 if (!isset($this->options['ORDER BY']))
162 $this->addOption('ORDER BY', $order);
163 else
164 $this->addOption('ORDER BY', $this->options['ORDER BY'] . ', ' . $order);
165 }
166
167 /**
168 * Add an option such as LIMIT or USE INDEX
169 * @param string $name Option name
170 * @param string $value Option value
171 */
172 protected function addOption($name, $value = null) {
173 if (is_null($value))
174 $this->options[] = $name;
175 else
176 $this->options[$name] = $value;
177 }
178
179 /**
180 * Execute a SELECT query based on the values in the internal arrays
181 * @param string $method Function the query should be attributed to. You should usually use __METHOD__ here
182 * @return ResultWrapper
183 */
184 protected function select($method) {
185
186 // getDB has its own profileDBIn/Out calls
187 $db = $this->getDB();
188
189 $this->profileDBIn();
190 $res = $db->select($this->tables, $this->fields, $this->where, $method, $this->options);
191 $this->profileDBOut();
192
193 return $res;
194 }
195
196 /**
197 * Estimate the row count for the SELECT query that would be run if we
198 * called select() right now, and check if it's acceptable.
199 * @return bool true if acceptable, false otherwise
200 */
201 protected function checkRowCount() {
202 $db = $this->getDB();
203 $this->profileDBIn();
204 $rowcount = $db->estimateRowCount($this->tables, $this->fields, $this->where, __METHOD__, $this->options);
205 $this->profileDBOut();
206
207 global $wgAPIMaxDBRows;
208 if($rowcount > $wgAPIMaxDBRows)
209 return false;
210 return true;
211 }
212
213 /**
214 * Add information (title and namespace) about a Title object to a result array
215 * @param array $arr Result array à la ApiResult
216 * @param Title $title Title object
217 * @param string $prefix Module prefix
218 */
219 public static function addTitleInfo(&$arr, $title, $prefix='') {
220 $arr[$prefix . 'ns'] = intval($title->getNamespace());
221 $arr[$prefix . 'title'] = $title->getPrefixedText();
222 }
223
224 /**
225 * Override this method to request extra fields from the pageSet
226 * using $pageSet->requestField('fieldName')
227 * @param ApiPageSet $pageSet
228 */
229 public function requestExtraData($pageSet) {
230 }
231
232 /**
233 * Get the main Query module
234 * @return ApiQuery
235 */
236 public function getQuery() {
237 return $this->mQueryModule;
238 }
239
240 /**
241 * Add a sub-element under the page element with the given page ID
242 * @param int $pageId Page ID
243 * @param array $data Data array à la ApiResult
244 */
245 protected function addPageSubItems($pageId, $data) {
246 $result = $this->getResult();
247 $result->setIndexedTagName($data, $this->getModulePrefix());
248 $result->addValue(array ('query', 'pages', intval($pageId)),
249 $this->getModuleName(),
250 $data);
251 }
252
253 /**
254 * Set a query-continue value
255 * @param $paramName Parameter name
256 * @param $paramValue Parameter value
257 */
258 protected function setContinueEnumParameter($paramName, $paramValue) {
259
260 $paramName = $this->encodeParamName($paramName);
261 $msg = array( $paramName => $paramValue );
262 $this->getResult()->addValue('query-continue', $this->getModuleName(), $msg);
263 }
264
265 /**
266 * Get the Query database connection (readonly)
267 * @return Database
268 */
269 protected function getDB() {
270 if (is_null($this->mDb))
271 $this->mDb = $this->getQuery()->getDB();
272 return $this->mDb;
273 }
274
275 /**
276 * Selects the query database connection with the given name.
277 * If no such connection has been requested before, it will be created.
278 * Subsequent calls with the same $name will return the same connection
279 * as the first, regardless of $db or $groups new values.
280 * @param string $name Name to assign to the database connection
281 * @param int $db One of the DB_* constants
282 * @param array $groups Query groups
283 * @return Database
284 */
285 public function selectNamedDB($name, $db, $groups) {
286 $this->mDb = $this->getQuery()->getNamedDB($name, $db, $groups);
287 }
288
289 /**
290 * Get the PageSet object to work on
291 * @return ApiPageSet
292 */
293 protected function getPageSet() {
294 return $this->getQuery()->getPageSet();
295 }
296
297 /**
298 * This is a very simplistic utility function
299 * to convert a non-namespaced title string to a db key.
300 * It will replace all ' ' with '_'
301 * @param string $title Page title with spaces
302 * @return string Page title with underscores
303 */
304 public static function titleToKey($title) {
305 return str_replace(' ', '_', $title);
306 }
307
308 /**
309 * The inverse of titleToKey()
310 * @param string $key Page title with underscores
311 * @return string Page title with spaces
312 */
313 public static function keyToTitle($key) {
314 return str_replace('_', ' ', $key);
315 }
316
317 /**
318 * Check whether the current user requested a certain token and
319 * is actually allowed to request it.
320 * @param array $tokenArr Array of tokens the user requested
321 * @param string $action Action to check for
322 * @return bool true if the user requested the token and is allowed to, false otherwise
323 */
324 public function getTokenFlag($tokenArr, $action) {
325 if ($this->getMain()->getRequest()->getVal('callback') !== null) {
326 // Don't do any session-specific data.
327 return false;
328 }
329 if (in_array($action, $tokenArr)) {
330 global $wgUser;
331 if ($wgUser->isAllowed($action))
332 return true;
333 else
334 $this->dieUsage("Action '$action' is not allowed for the current user", 'permissiondenied');
335 }
336 return false;
337 }
338
339 /**
340 * Get version string for use in the API help output
341 * @return string
342 */
343 public static function getBaseVersion() {
344 return __CLASS__ . ': $Id$';
345 }
346 }
347
348 /**
349 * @addtogroup API
350 */
351 abstract class ApiQueryGeneratorBase extends ApiQueryBase {
352
353 private $mIsGenerator;
354
355 public function __construct($query, $moduleName, $paramPrefix = '') {
356 parent :: __construct($query, $moduleName, $paramPrefix);
357 $this->mIsGenerator = false;
358 }
359
360 /**
361 * Switch this module to generator mode. By default, generator mode is
362 * switched off and the module acts like a normal query module.
363 */
364 public function setGeneratorMode() {
365 $this->mIsGenerator = true;
366 }
367
368 /**
369 * Overrides base class to prepend 'g' to every generator parameter
370 */
371 public function encodeParamName($paramName) {
372 if ($this->mIsGenerator)
373 return 'g' . parent :: encodeParamName($paramName);
374 else
375 return parent :: encodeParamName($paramName);
376 }
377
378 /**
379 * Execute this module as a generator
380 * @param $resultPageSet PageSet: All output should be appended to this object
381 */
382 public abstract function executeGenerator($resultPageSet);
383 }