Follow-up for r39935: re-fix bug 14651 by making the first letter uppercase if needed...
[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 * @ingroup API
36 */
37 abstract class ApiQueryBase extends ApiBase {
38
39 private $mQueryModule, $mDb, $tables, $where, $fields, $options, $join_conds;
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 $this->join_conds = array ();
57 }
58
59 /**
60 * Add a set of tables to the internal array
61 * @param mixed $tables Table name or array of table names
62 * @param mixed $alias Table alias, or null for no alias. Cannot be used with multiple tables
63 */
64 protected function addTables($tables, $alias = null) {
65 if (is_array($tables)) {
66 if (!is_null($alias))
67 ApiBase :: dieDebug(__METHOD__, 'Multiple table aliases not supported');
68 $this->tables = array_merge($this->tables, $tables);
69 } else {
70 if (!is_null($alias))
71 $tables = $this->getAliasedName($tables, $alias);
72 $this->tables[] = $tables;
73 }
74 }
75
76 /**
77 * Get the SQL for a table name with alias
78 * @param string $table Table name
79 * @param string $alias Alias
80 * @return string SQL
81 */
82 protected function getAliasedName($table, $alias) {
83 return $this->getDB()->tableName($table) . ' ' . $alias;
84 }
85
86 /**
87 * Add a set of JOIN conditions to the internal array
88 *
89 * JOIN conditions are formatted as array( tablename => array(jointype, conditions)
90 * e.g. array('page' => array('LEFT JOIN', 'page_id=rev_page'))
91 * @param array $join_conds JOIN conditions
92 */
93 protected function addJoinConds($join_conds) {
94 if(!is_array($join_conds))
95 ApiBase::dieDebug(__METHOD__, 'Join conditions have to be arrays');
96 $this->join_conds = array_merge($this->join_conds, $join_conds);
97 }
98
99 /**
100 * Add a set of fields to select to the internal array
101 * @param mixed $value Field name or array of field names
102 */
103 protected function addFields($value) {
104 if (is_array($value))
105 $this->fields = array_merge($this->fields, $value);
106 else
107 $this->fields[] = $value;
108 }
109
110 /**
111 * Same as addFields(), but add the fields only if a condition is met
112 * @param mixed $value See addFields()
113 * @param bool $condition If false, do nothing
114 * @return bool $condition
115 */
116 protected function addFieldsIf($value, $condition) {
117 if ($condition) {
118 $this->addFields($value);
119 return true;
120 }
121 return false;
122 }
123
124 /**
125 * Add a set of WHERE clauses to the internal array.
126 * Clauses can be formatted as 'foo=bar' or array('foo' => 'bar'),
127 * the latter only works if the value is a constant (i.e. not another field)
128 *
129 * For example, array('foo=bar', 'baz' => 3, 'bla' => 'foo') translates
130 * to "foo=bar AND baz='3' AND bla='foo'"
131 * @param mixed $value String or array
132 */
133 protected function addWhere($value) {
134 if (is_array($value))
135 $this->where = array_merge($this->where, $value);
136 else
137 $this->where[] = $value;
138 }
139
140 /**
141 * Same as addWhere(), but add the WHERE clauses only if a condition is met
142 * @param mixed $value See addWhere()
143 * @param bool $condition If false, do nothing
144 * @return bool $condition
145 */
146 protected function addWhereIf($value, $condition) {
147 if ($condition) {
148 $this->addWhere($value);
149 return true;
150 }
151 return false;
152 }
153
154 /**
155 * Equivalent to addWhere(array($field => $value))
156 * @param string $field Field name
157 * @param string $value Value; ignored if nul;
158 */
159 protected function addWhereFld($field, $value) {
160 if (!is_null($value))
161 $this->where[$field] = $value;
162 }
163
164 /**
165 * Add a WHERE clause corresponding to a range, and an ORDER BY
166 * clause to sort in the right direction
167 * @param string $field Field name
168 * @param string $dir If 'newer', sort in ascending order, otherwise sort in descending order
169 * @param string $start Value to start the list at. If $dir == 'newer' this is the lower boundary, otherwise it's the upper boundary
170 * @param string $end Value to end the list at. If $dir == 'newer' this is the upper boundary, otherwise it's the lower boundary
171 */
172 protected function addWhereRange($field, $dir, $start, $end) {
173 $isDirNewer = ($dir === 'newer');
174 $after = ($isDirNewer ? '>=' : '<=');
175 $before = ($isDirNewer ? '<=' : '>=');
176 $db = $this->getDB();
177
178 if (!is_null($start))
179 $this->addWhere($field . $after . $db->addQuotes($start));
180
181 if (!is_null($end))
182 $this->addWhere($field . $before . $db->addQuotes($end));
183
184 $order = $field . ($isDirNewer ? '' : ' DESC');
185 if (!isset($this->options['ORDER BY']))
186 $this->addOption('ORDER BY', $order);
187 else
188 $this->addOption('ORDER BY', $this->options['ORDER BY'] . ', ' . $order);
189 }
190
191 /**
192 * Add an option such as LIMIT or USE INDEX
193 * @param string $name Option name
194 * @param string $value Option value
195 */
196 protected function addOption($name, $value = null) {
197 if (is_null($value))
198 $this->options[] = $name;
199 else
200 $this->options[$name] = $value;
201 }
202
203 /**
204 * Execute a SELECT query based on the values in the internal arrays
205 * @param string $method Function the query should be attributed to. You should usually use __METHOD__ here
206 * @return ResultWrapper
207 */
208 protected function select($method) {
209
210 // getDB has its own profileDBIn/Out calls
211 $db = $this->getDB();
212
213 $this->profileDBIn();
214 $res = $db->select($this->tables, $this->fields, $this->where, $method, $this->options, $this->join_conds);
215 $this->profileDBOut();
216
217 return $res;
218 }
219
220 /**
221 * Estimate the row count for the SELECT query that would be run if we
222 * called select() right now, and check if it's acceptable.
223 * @return bool true if acceptable, false otherwise
224 */
225 protected function checkRowCount() {
226 $db = $this->getDB();
227 $this->profileDBIn();
228 $rowcount = $db->estimateRowCount($this->tables, $this->fields, $this->where, __METHOD__, $this->options);
229 $this->profileDBOut();
230
231 global $wgAPIMaxDBRows;
232 if($rowcount > $wgAPIMaxDBRows)
233 return false;
234 return true;
235 }
236
237 /**
238 * Add information (title and namespace) about a Title object to a result array
239 * @param array $arr Result array à la ApiResult
240 * @param Title $title Title object
241 * @param string $prefix Module prefix
242 */
243 public static function addTitleInfo(&$arr, $title, $prefix='') {
244 $arr[$prefix . 'ns'] = intval($title->getNamespace());
245 $arr[$prefix . 'title'] = $title->getPrefixedText();
246 }
247
248 /**
249 * Override this method to request extra fields from the pageSet
250 * using $pageSet->requestField('fieldName')
251 * @param ApiPageSet $pageSet
252 */
253 public function requestExtraData($pageSet) {
254 }
255
256 /**
257 * Get the main Query module
258 * @return ApiQuery
259 */
260 public function getQuery() {
261 return $this->mQueryModule;
262 }
263
264 /**
265 * Add a sub-element under the page element with the given page ID
266 * @param int $pageId Page ID
267 * @param array $data Data array à la ApiResult
268 */
269 protected function addPageSubItems($pageId, $data) {
270 $result = $this->getResult();
271 $result->setIndexedTagName($data, $this->getModulePrefix());
272 $result->addValue(array ('query', 'pages', intval($pageId)),
273 $this->getModuleName(),
274 $data);
275 }
276
277 /**
278 * Set a query-continue value
279 * @param $paramName Parameter name
280 * @param $paramValue Parameter value
281 */
282 protected function setContinueEnumParameter($paramName, $paramValue) {
283
284 $paramName = $this->encodeParamName($paramName);
285 $msg = array( $paramName => $paramValue );
286 $this->getResult()->addValue('query-continue', $this->getModuleName(), $msg);
287 }
288
289 /**
290 * Get the Query database connection (readonly)
291 * @return Database
292 */
293 protected function getDB() {
294 if (is_null($this->mDb))
295 $this->mDb = $this->getQuery()->getDB();
296 return $this->mDb;
297 }
298
299 /**
300 * Selects the query database connection with the given name.
301 * If no such connection has been requested before, it will be created.
302 * Subsequent calls with the same $name will return the same connection
303 * as the first, regardless of $db or $groups new values.
304 * @param string $name Name to assign to the database connection
305 * @param int $db One of the DB_* constants
306 * @param array $groups Query groups
307 * @return Database
308 */
309 public function selectNamedDB($name, $db, $groups) {
310 $this->mDb = $this->getQuery()->getNamedDB($name, $db, $groups);
311 }
312
313 /**
314 * Get the PageSet object to work on
315 * @return ApiPageSet
316 */
317 protected function getPageSet() {
318 return $this->getQuery()->getPageSet();
319 }
320
321 /**
322 * Convert a title to a DB key
323 * @param string $title Page title with spaces
324 * @return string Page title with underscores
325 */
326 public function titleToKey($title) {
327 global $wgContLang, $wgCapitalLinks;
328 if($wgCaptialLinks)
329 $title = $wgContLang->ucfirst($title);
330 return str_replace(' ', '_', $title);
331 }
332
333 /**
334 * The inverse of titleToKey()
335 * @param string $key Page title with underscores
336 * @return string Page title with spaces
337 */
338 public function keyToTitle($key) {
339 return str_replace('_', ' ', $key);
340 }
341
342 /**
343 * Get version string for use in the API help output
344 * @return string
345 */
346 public static function getBaseVersion() {
347 return __CLASS__ . ': $Id$';
348 }
349 }
350
351 /**
352 * @ingroup API
353 */
354 abstract class ApiQueryGeneratorBase extends ApiQueryBase {
355
356 private $mIsGenerator;
357
358 public function __construct($query, $moduleName, $paramPrefix = '') {
359 parent :: __construct($query, $moduleName, $paramPrefix);
360 $this->mIsGenerator = false;
361 }
362
363 /**
364 * Switch this module to generator mode. By default, generator mode is
365 * switched off and the module acts like a normal query module.
366 */
367 public function setGeneratorMode() {
368 $this->mIsGenerator = true;
369 }
370
371 /**
372 * Overrides base class to prepend 'g' to every generator parameter
373 */
374 public function encodeParamName($paramName) {
375 if ($this->mIsGenerator)
376 return 'g' . parent :: encodeParamName($paramName);
377 else
378 return parent :: encodeParamName($paramName);
379 }
380
381 /**
382 * Execute this module as a generator
383 * @param $resultPageSet PageSet: All output should be appended to this object
384 */
385 public abstract function executeGenerator($resultPageSet);
386 }