Index: trunk/phase3/includes/api/ApiQuery.php |
— | — | @@ -96,13 +96,13 @@ |
97 | 97 | * #5 Execute all requested modules |
98 | 98 | */ |
99 | 99 | public function execute() { |
100 | | - $prop = $list = $meta = $generator = null; |
| 100 | + $prop = $list = $meta = $generator = $redirects = null; |
101 | 101 | extract($this->extractRequestParams()); |
102 | 102 | |
103 | 103 | // |
104 | 104 | // Create PageSet |
105 | 105 | // |
106 | | - $this->mPageSet = new ApiPageSet($this); |
| 106 | + $this->mPageSet = new ApiPageSet($this, $redirects); |
107 | 107 | |
108 | 108 | // Instantiate required modules |
109 | 109 | $modules = array (); |
— | — | @@ -126,7 +126,7 @@ |
127 | 127 | // If given, execute generator to substitute user supplied data with generated data. |
128 | 128 | // |
129 | 129 | if (isset ($generator)) |
130 | | - $this->executeGenerator($generator); |
| 130 | + $this->executeGeneratorModule($generator, $redirects); |
131 | 131 | |
132 | 132 | // |
133 | 133 | // Populate page information for the given pageSet |
— | — | @@ -212,7 +212,7 @@ |
213 | 213 | } |
214 | 214 | } |
215 | 215 | |
216 | | - protected function executeGenerator($generatorName) { |
| 216 | + protected function executeGeneratorModule($generatorName, $redirects) { |
217 | 217 | |
218 | 218 | // Find class that implements requested generator |
219 | 219 | if (isset ($this->mQueryListModules[$generatorName])) { |
— | — | @@ -226,7 +226,7 @@ |
227 | 227 | |
228 | 228 | // Use current pageset as the result, and create a new one just for the generator |
229 | 229 | $resultPageSet = $this->mPageSet; |
230 | | - $this->mPageSet = new ApiPageSet($this); |
| 230 | + $this->mPageSet = new ApiPageSet($this, $redirects); |
231 | 231 | |
232 | 232 | // Create and execute the generator |
233 | 233 | $generator = new $className ($this, $generatorName); |
— | — | @@ -242,6 +242,7 @@ |
243 | 243 | // populate resultPageSet with the generator output |
244 | 244 | $generator->profileIn(); |
245 | 245 | $generator->executeGenerator($resultPageSet); |
| 246 | + $resultPageSet->finishPageSetGeneration(); |
246 | 247 | $generator->profileOut(); |
247 | 248 | |
248 | 249 | // Swap the resulting pageset back in |
— | — | @@ -264,7 +265,8 @@ |
265 | 266 | ), |
266 | 267 | 'generator' => array ( |
267 | 268 | ApiBase :: PARAM_TYPE => $this->mAllowedGenerators |
268 | | - ) |
| 269 | + ), |
| 270 | + 'redirects' => false |
269 | 271 | ); |
270 | 272 | } |
271 | 273 | |
— | — | @@ -322,7 +324,8 @@ |
323 | 325 | 'prop' => 'Which properties to get for the titles/revisions/pageids', |
324 | 326 | 'list' => 'Which lists to get', |
325 | 327 | 'meta' => 'Which meta data to get about the site', |
326 | | - 'generator' => 'Use the output of a list as the input for other prop/list/meta items' |
| 328 | + 'generator' => 'Use the output of a list as the input for other prop/list/meta items', |
| 329 | + 'redirects' => 'Automatically resolve redirects' |
327 | 330 | ); |
328 | 331 | } |
329 | 332 | |
Index: trunk/phase3/includes/api/ApiQueryAllpages.php |
— | — | @@ -40,6 +40,9 @@ |
41 | 41 | } |
42 | 42 | |
43 | 43 | public function executeGenerator($resultPageSet) { |
| 44 | + if ($resultPageSet->isResolvingRedirects()) |
| 45 | + $this->dieUsage('Use "gapfilterredir=nonredirects" option instead of "redirects" when using allpages as a generator', 'params'); |
| 46 | + |
44 | 47 | $this->run($resultPageSet); |
45 | 48 | } |
46 | 49 | |
— | — | @@ -52,9 +55,11 @@ |
53 | 56 | $where = array ( |
54 | 57 | 'page_namespace' => $namespace |
55 | 58 | ); |
| 59 | + |
56 | 60 | if (isset ($from)) { |
57 | 61 | $where[] = 'page_title>=' . $db->addQuotes(ApiQueryBase :: titleToKey($from)); |
58 | 62 | } |
| 63 | + |
59 | 64 | if ($filterredir === 'redirects') { |
60 | 65 | $where['page_is_redirect'] = 1; |
61 | 66 | } |
— | — | @@ -62,12 +67,18 @@ |
63 | 68 | $where['page_is_redirect'] = 0; |
64 | 69 | } |
65 | 70 | |
| 71 | + if (is_null($resultPageSet)) { |
| 72 | + $fields = array ( |
| 73 | + 'page_id', |
| 74 | + 'page_namespace', |
| 75 | + 'page_title' |
| 76 | + ); |
| 77 | + } else { |
| 78 | + $fields = $resultPageSet->getPageTableFields(); |
| 79 | + } |
| 80 | + |
66 | 81 | $this->profileDBIn(); |
67 | | - $res = $db->select('page', array ( |
68 | | - 'page_id', |
69 | | - 'page_namespace', |
70 | | - 'page_title' |
71 | | - ), $where, __CLASS__ . '::' . __METHOD__, array ( |
| 82 | + $res = $db->select('page', $fields, $where, __CLASS__ . '::' . __METHOD__, array ( |
72 | 83 | 'USE INDEX' => 'name_title', |
73 | 84 | 'LIMIT' => $limit +1, |
74 | 85 | 'ORDER BY' => 'page_namespace, page_title' |
— | — | @@ -80,7 +91,8 @@ |
81 | 92 | if (++ $count > $limit) { |
82 | 93 | // We've reached the one extra which shows that there are additional pages to be had. Stop here... |
83 | 94 | $msg = array ( |
84 | | - 'continue' => $this->encodeParamName('from') . '='. ApiQueryBase :: keyToTitle($row->page_title)); |
| 95 | + 'continue' => $this->encodeParamName('from' |
| 96 | + ) . '=' . ApiQueryBase :: keyToTitle($row->page_title)); |
85 | 97 | $this->getResult()->addValue('query-status', 'allpages', $msg); |
86 | 98 | break; |
87 | 99 | } |
— | — | @@ -88,18 +100,12 @@ |
89 | 101 | $title = Title :: makeTitle($row->page_namespace, $row->page_title); |
90 | 102 | // skip any pages that user has no rights to read |
91 | 103 | if ($title->userCanRead()) { |
92 | | - $id = intval($row->page_id); |
93 | 104 | |
94 | 105 | if (is_null($resultPageSet)) { |
95 | | - $pagedata = array (); |
96 | | - $pagedata['id'] = $id; |
97 | | - if ($title->getNamespace() !== 0) |
98 | | - $pagedata['ns'] = $title->getNamespace(); |
99 | | - $pagedata['title'] = $title->getPrefixedText(); |
100 | | - |
101 | | - $data[$id] = $pagedata; |
| 106 | + $id = intval($row->page_id); |
| 107 | + $data[] = $id; // in generator mode, just assemble a list of page IDs. |
102 | 108 | } else { |
103 | | - $data[] = $id; // in generator mode, just assemble a list of page IDs. |
| 109 | + $resultPageSet->processDbRow($row); |
104 | 110 | } |
105 | 111 | } |
106 | 112 | } |
— | — | @@ -108,8 +114,6 @@ |
109 | 115 | if (is_null($resultPageSet)) { |
110 | 116 | ApiResult :: setIndexedTagName($data, 'p'); |
111 | 117 | $this->getResult()->addValue('query', 'allpages', $data); |
112 | | - } else { |
113 | | - $resultPageSet->executeForPageIDs($data); |
114 | 118 | } |
115 | 119 | } |
116 | 120 | |
— | — | @@ -161,9 +165,14 @@ |
162 | 166 | |
163 | 167 | protected function getExamples() { |
164 | 168 | return array ( |
165 | | - 'api.php?action=query&list=allpages', |
166 | | - 'api.php?action=query&list=allpages&apfrom=B&aplimit=5', |
167 | | - 'api.php?action=query&generator=allpages&gaplimit=4&prop=info (generator)' |
| 169 | + 'Simple Use', |
| 170 | + ' api.php?action=query&list=allpages', |
| 171 | + ' api.php?action=query&list=allpages&apfrom=B&aplimit=5', |
| 172 | + 'Using as Generator', |
| 173 | + ' Show info about 4 pages starting at the letter "T"', |
| 174 | + ' api.php?action=query&generator=allpages&gaplimit=4&gapfrom=T&prop=info', |
| 175 | + ' Show content of first 2 non-redirect pages begining at "Re"', |
| 176 | + ' api.php?action=query&generator=allpages&gaplimit=2&gapfilterredir=nonredirects&gapfrom=Re&prop=revisions&rvprop=content' |
168 | 177 | ); |
169 | 178 | } |
170 | 179 | |
Index: trunk/phase3/includes/api/ApiMain.php |
— | — | @@ -148,7 +148,8 @@ |
149 | 149 | header($errorCode, true, $httpRespCode); |
150 | 150 | |
151 | 151 | $data = array ( |
152 | | - 'code' => $errorCode |
| 152 | + 'code' => $errorCode, |
| 153 | + 'info' => $description |
153 | 154 | ); |
154 | 155 | ApiResult :: setContent($data, $this->makeHelpMsg()); |
155 | 156 | $this->mResult->addValue(null, 'error', $data); |
Index: trunk/phase3/includes/api/ApiPageSet.php |
— | — | @@ -33,10 +33,11 @@ |
34 | 34 | |
35 | 35 | private $mAllPages; // [ns][dbkey] => page_id or 0 when missing |
36 | 36 | private $mGoodTitles, $mMissingTitles, $mMissingPageIDs, $mRedirectTitles, $mNormalizedTitles; |
| 37 | + private $mResolveRedirects, $mPendingRedirectIDs; |
37 | 38 | |
38 | | - private $mRequestedFields; |
| 39 | + private $mRequestedPageFields; |
39 | 40 | |
40 | | - public function __construct($query) { |
| 41 | + public function __construct($query, $resolveRedirects = false) { |
41 | 42 | parent :: __construct($query, __CLASS__); |
42 | 43 | |
43 | 44 | $this->mAllPages = array (); |
— | — | @@ -46,18 +47,45 @@ |
47 | 48 | $this->mRedirectTitles = array (); |
48 | 49 | $this->mNormalizedTitles = array (); |
49 | 50 | |
50 | | - $this->mRequestedFields = array (); |
| 51 | + $this->mRequestedPageFields = array (); |
| 52 | + $this->mResolveRedirects = $resolveRedirects; |
| 53 | + if($resolveRedirects) |
| 54 | + $this->mPendingRedirectIDs = array(); |
51 | 55 | } |
52 | 56 | |
| 57 | + public function isResolvingRedirects() { |
| 58 | + return $this->mResolveRedirects; |
| 59 | + } |
| 60 | + |
53 | 61 | public function requestField($fieldName) { |
54 | | - $this->mRequestedFields[$fieldName] = null; |
| 62 | + $this->mRequestedPageFields[$fieldName] = null; |
55 | 63 | } |
56 | 64 | |
57 | 65 | public function getCustomField($fieldName) { |
58 | | - return $this->mRequestedFields[$fieldName]; |
| 66 | + return $this->mRequestedPageFields[$fieldName]; |
59 | 67 | } |
60 | 68 | |
61 | 69 | /** |
| 70 | + * Get fields that modules have requested from the page table |
| 71 | + */ |
| 72 | + public function getPageTableFields() { |
| 73 | + // Ensure we get minimum required fields |
| 74 | + $pageFlds = array ( |
| 75 | + 'page_id' => null, |
| 76 | + 'page_namespace' => null, |
| 77 | + 'page_title' => null |
| 78 | + ); |
| 79 | + |
| 80 | + // only store non-default fields |
| 81 | + $this->mRequestedPageFields = array_diff_key($this->mRequestedPageFields, $pageFlds); |
| 82 | + |
| 83 | + if ($this->mResolveRedirects) |
| 84 | + $pageFlds['page_is_redirect'] = null; |
| 85 | + |
| 86 | + return array_keys(array_merge($pageFlds, $this->mRequestedPageFields)); |
| 87 | + } |
| 88 | + |
| 89 | + /** |
62 | 90 | * Title objects that were found in the database. |
63 | 91 | * @return array page_id (int) => Title (obj) |
64 | 92 | */ |
— | — | @@ -66,6 +94,13 @@ |
67 | 95 | } |
68 | 96 | |
69 | 97 | /** |
| 98 | + * Returns the number of unique pages (not revisions) in the set. |
| 99 | + */ |
| 100 | + public function getGoodTitleCount() { |
| 101 | + return count($this->getGoodTitles()); |
| 102 | + } |
| 103 | + |
| 104 | + /** |
70 | 105 | * Title objects that were NOT found in the database. |
71 | 106 | * @return array of Title objects |
72 | 107 | */ |
— | — | @@ -99,13 +134,6 @@ |
100 | 135 | } |
101 | 136 | |
102 | 137 | /** |
103 | | - * Returns the number of unique pages (not revisions) in the set. |
104 | | - */ |
105 | | - public function getGoodTitleCount() { |
106 | | - return count($this->getGoodTitles()); |
107 | | - } |
108 | | - |
109 | | - /** |
110 | 138 | * Get the list of revision IDs (requested with revids= parameter) |
111 | 139 | */ |
112 | 140 | public function getRevisionIDs() { |
— | — | @@ -120,6 +148,100 @@ |
121 | 149 | } |
122 | 150 | |
123 | 151 | /** |
| 152 | + * Populate from the request parameters |
| 153 | + */ |
| 154 | + public function execute() { |
| 155 | + $this->profileIn(); |
| 156 | + $titles = $pageids = $revids = null; |
| 157 | + extract($this->extractRequestParams()); |
| 158 | + |
| 159 | + // Only one of the titles/pageids/revids is allowed at the same time |
| 160 | + $dataSource = null; |
| 161 | + if (isset ($titles)) |
| 162 | + $dataSource = 'titles'; |
| 163 | + if (isset ($pageids)) { |
| 164 | + if (isset ($dataSource)) |
| 165 | + $this->dieUsage("Cannot use 'pageids' at the same time as '$dataSource'", 'multisource'); |
| 166 | + $dataSource = 'pageids'; |
| 167 | + } |
| 168 | + if (isset ($revids)) { |
| 169 | + if (isset ($dataSource)) |
| 170 | + $this->dieUsage("Cannot use 'revids' at the same time as '$dataSource'", 'multisource'); |
| 171 | + $dataSource = 'revids'; |
| 172 | + } |
| 173 | + |
| 174 | + switch ($dataSource) { |
| 175 | + case 'titles' : |
| 176 | + $this->initFromTitles($titles); |
| 177 | + break; |
| 178 | + case 'pageids' : |
| 179 | + $this->initFromPageIds($pageids); |
| 180 | + break; |
| 181 | + case 'revids' : |
| 182 | + $this->initFromRevIDs($revids); |
| 183 | + break; |
| 184 | + default : |
| 185 | + // Do nothing - some queries do not need any of the data sources. |
| 186 | + break; |
| 187 | + } |
| 188 | + $this->profileOut(); |
| 189 | + } |
| 190 | + |
| 191 | + /** |
| 192 | + * Initialize PageSet from a list of Titles |
| 193 | + */ |
| 194 | + public function populateFromTitles($titles) { |
| 195 | + $this->profileIn(); |
| 196 | + $this->initFromTitles($titles); |
| 197 | + $this->profileOut(); |
| 198 | + } |
| 199 | + |
| 200 | + /** |
| 201 | + * Initialize PageSet from a list of Page IDs |
| 202 | + */ |
| 203 | + public function populateFromPageIDs($pageIDs) { |
| 204 | + $this->profileIn(); |
| 205 | + $pageIDs = array_map('intval', $pageIDs); // paranoia |
| 206 | + $this->initFromPageIds($pageIDs); |
| 207 | + $this->profileOut(); |
| 208 | + } |
| 209 | + |
| 210 | + /** |
| 211 | + * Initialize PageSet from a rowset returned from the database |
| 212 | + */ |
| 213 | + public function populateFromQueryResult($db, $queryResult) { |
| 214 | + $this->profileIn(); |
| 215 | + $this->initFromQueryResult($db, $queryResult); |
| 216 | + $this->profileOut(); |
| 217 | + } |
| 218 | + |
| 219 | + /** |
| 220 | + * Extract all requested fields from the row received from the database |
| 221 | + */ |
| 222 | + public function processDbRow($row) { |
| 223 | + $pageId = intval($row->page_id); |
| 224 | + |
| 225 | + // Store Title object in various data structures |
| 226 | + $title = Title :: makeTitle($row->page_namespace, $row->page_title); |
| 227 | + $this->mAllPages[$row->page_namespace][$row->page_title] = $pageId; |
| 228 | + |
| 229 | + if ($this->mResolveRedirects && $row->page_is_redirect == '1') { |
| 230 | + $this->mPendingRedirectIDs[$pageId] = $title; |
| 231 | + } else { |
| 232 | + $this->mGoodTitles[$pageId] = $title; |
| 233 | + } |
| 234 | + |
| 235 | + foreach ($this->mRequestedPageFields as $fieldName => & $fieldValues) |
| 236 | + $fieldValues[$pageId] = $row-> $fieldName; |
| 237 | + } |
| 238 | + |
| 239 | + public function finishPageSetGeneration() { |
| 240 | + $this->profileIn(); |
| 241 | + $this->resolvePendingRedirects(); |
| 242 | + $this->profileOut(); |
| 243 | + } |
| 244 | + |
| 245 | + /** |
124 | 246 | * This method populates internal variables with page information |
125 | 247 | * based on the given array of title strings. |
126 | 248 | * |
— | — | @@ -133,85 +255,79 @@ |
134 | 256 | * #5 Substitute the original LinkBatch object with the new list |
135 | 257 | * #6 Repeat from step #1 |
136 | 258 | */ |
137 | | - private function populatePages($titles, $pageids, $redirects) { |
138 | | - if (!is_null($titles) && !is_null($pageids)) |
139 | | - ApiBase :: dieDebug(__METHOD__, 'bad parameters'); |
140 | | - $processTitles = !is_null($titles); |
| 259 | + private function initFromTitles($titles) { |
| 260 | + $db = $this->getDB(); |
141 | 261 | |
142 | | - // Ensure we get minimum required fields |
143 | | - $pageFlds = array ( |
144 | | - 'page_id' => null, |
145 | | - 'page_namespace' => null, |
146 | | - 'page_title' => null |
147 | | - ); |
| 262 | + // Get validated and normalized title objects |
| 263 | + $linkBatch = $this->processTitlesStrArray($titles); |
| 264 | + $set = $linkBatch->constructSet('page', $db); |
148 | 265 | |
149 | | - // only store non-default fields |
150 | | - $this->mRequestedFields = array_diff_key($this->mRequestedFields, $pageFlds); |
| 266 | + // Get pageIDs data from the `page` table |
| 267 | + $this->profileDBIn(); |
| 268 | + $res = $db->select('page', $this->getPageTableFields(), $set, __METHOD__); |
| 269 | + $this->profileDBOut(); |
151 | 270 | |
152 | | - if ($redirects) |
153 | | - $pageFlds['page_is_redirect'] = null; |
| 271 | + // Hack: get the ns:titles stored in array(ns => array(titles)) format |
| 272 | + $this->initFromQueryResult($db, $res, $linkBatch->data, true); // process Titles |
154 | 273 | |
155 | | - $pageFlds = array_keys(array_merge($pageFlds, $this->mRequestedFields)); |
| 274 | + // Resolve any found redirects |
| 275 | + $this->resolvePendingRedirects(); |
| 276 | + } |
156 | 277 | |
| 278 | + private function initFromPageIds($pageids) { |
157 | 279 | $db = $this->getDB(); |
158 | 280 | |
159 | | - if ($processTitles) { |
| 281 | + $set = array ( |
| 282 | + 'page_id' => $pageids |
| 283 | + ); |
160 | 284 | |
161 | | - // Get validated and normalized title objects |
162 | | - $linkBatch = $this->processTitlesStrArray($titles); |
| 285 | + // Get pageIDs data from the `page` table |
| 286 | + $this->profileDBIn(); |
| 287 | + $res = $db->select('page', $this->getPageTableFields(), $set, __METHOD__); |
| 288 | + $this->profileDBOut(); |
| 289 | + |
| 290 | + $this->initFromQueryResult($db, $res, array_flip($pageids), false); // process PageIDs |
163 | 291 | |
164 | | - $set = $linkBatch->constructSet('page', $db); |
165 | | - } else { |
166 | | - $set = array ( |
167 | | - 'page_id' => $pageids |
168 | | - ); |
169 | | - } |
| 292 | + // Resolve any found redirects |
| 293 | + $this->resolvePendingRedirects(); |
| 294 | + } |
| 295 | + |
| 296 | + /** |
| 297 | + * Iterate through the result of the query on 'page' table, |
| 298 | + * and for each row create and store title object and save any extra fields requested. |
| 299 | + * @param $db Database |
| 300 | + * @param $res DB Query result |
| 301 | + * @param $remaining Array of either pageID or ns/title elements (optional). |
| 302 | + * If given, any missing items will go to $mMissingPageIDs and $mMissingTitles |
| 303 | + * @param $processTitles bool Must be provided together with $remaining. |
| 304 | + * If true, treat $remaining as an array of [ns][title] |
| 305 | + * If false, treat it as an array of [pageIDs] |
| 306 | + * @return Array of redirect IDs (only when resolving redirects) |
| 307 | + */ |
| 308 | + private function initFromQueryResult($db, $res, &$remaining = null, $processTitles = null) { |
| 309 | + if (!is_null($remaining) && is_null($processTitles)) |
| 310 | + $this->dieDebug('Missing $processTitles parameter when $remaining is provided'); |
| 311 | + |
| 312 | + while ($row = $db->fetchObject($res)) { |
170 | 313 | |
171 | | - // |
172 | | - // Repeat until all redirects have been resolved |
173 | | - // The infinite loop is prevented by keeping all known pages in $this->mAllPages |
174 | | - // |
175 | | - do { |
176 | | - if ($processTitles) { |
177 | | - // Hack: get the ns:titles stored in array(ns => array(titles)) format |
178 | | - $remaining = $linkBatch->data; |
179 | | - } else { |
180 | | - $remaining = array_flip($pageids); // turn pageids into keys |
181 | | - } |
| 314 | + $pageId = intval($row->page_id); |
182 | 315 | |
183 | | - $redirectIds = array (); |
184 | | - |
185 | | - // |
186 | | - // Get data about $linkBatch from `page` table |
187 | | - // |
188 | | - $this->profileDBIn(); |
189 | | - $res = $db->select('page', $pageFlds, $set, __METHOD__); |
190 | | - $this->profileDBOut(); |
191 | | - while ($row = $db->fetchObject($res)) { |
192 | | - |
193 | | - $pageId = intval($row->page_id); |
194 | | - |
| 316 | + // Remove found page from the list of remaining items |
| 317 | + if (isset($remaining)) { |
195 | 318 | if ($processTitles) |
196 | 319 | unset ($remaining[$row->page_namespace][$row->page_title]); |
197 | 320 | else |
198 | 321 | unset ($remaining[$pageId]); |
199 | | - |
200 | | - $title = Title :: makeTitle($row->page_namespace, $row->page_title); |
201 | | - $this->mAllPages[$row->page_namespace][$row->page_title] = $pageId; |
202 | | - |
203 | | - if ($redirects && $row->page_is_redirect == '1') { |
204 | | - $redirectIds[$pageId] = $title; |
205 | | - } else { |
206 | | - $this->mGoodTitles[$pageId] = $title; |
207 | | - } |
208 | | - |
209 | | - foreach ($this->mRequestedFields as $fieldName => & $fieldValues) { |
210 | | - $fieldValues[$pageId] = $row-> $fieldName; |
211 | | - } |
212 | 322 | } |
213 | | - $db->freeResult($res); |
214 | | - |
215 | | - if ($processTitles) { |
| 323 | + |
| 324 | + // Store any extra fields requested by modules |
| 325 | + $this->processDbRow($row); |
| 326 | + } |
| 327 | + $db->freeResult($res); |
| 328 | + |
| 329 | + if(isset($remaining)) { |
| 330 | + // Any items left in the $remaining list are added as missing |
| 331 | + if($processTitles) { |
216 | 332 | // The remaining titles in $remaining are non-existant pages |
217 | 333 | foreach ($remaining as $ns => $dbkeys) { |
218 | 334 | foreach ($dbkeys as $dbkey => $nothing) { |
— | — | @@ -219,29 +335,55 @@ |
220 | 336 | $this->mAllPages[$ns][$dbkey] = 0; |
221 | 337 | } |
222 | 338 | } |
223 | | - } else { |
224 | | - // The remaining pageids in $remaining do not exist |
225 | | - foreach ($remaining as $pageid => $ignore) { |
226 | | - $this->mMissingPageIDs[] = $pageid; |
227 | | - } |
228 | 339 | } |
| 340 | + else |
| 341 | + { |
| 342 | + // The remaining pageids do not exist |
| 343 | + if(empty($this->mMissingPageIDs)) |
| 344 | + $this->mMissingPageIDs = array_keys($remaining); |
| 345 | + else |
| 346 | + $this->mMissingPageIDs = array_merge($this->mMissingPageIDs, array_keys($remaining)); |
| 347 | + } |
| 348 | + } |
| 349 | + } |
229 | 350 | |
230 | | - if (!$redirects || empty ($redirectIds)) |
231 | | - break; |
| 351 | + private function initFromRevIDs($revids) { |
| 352 | + $this->dieUsage(__METHOD__ . ' is not implemented', 'notimplemented'); |
| 353 | + } |
232 | 354 | |
233 | | - // |
234 | | - // Resolve redirects by querying the pagelinks table, and repeat the process |
235 | | - // Create a new linkBatch object for the next pass |
236 | | - // |
237 | | - $linkBatch = $this->resolveRedirectList($redirectIds); |
| 355 | + private function resolvePendingRedirects() { |
238 | 356 | |
239 | | - // Redirects are always titles |
240 | | - $processTitles = true; |
| 357 | + if($this->mResolveRedirects) { |
| 358 | + $db = $this->getDB(); |
| 359 | + $pageFlds = $this->getPageTableFields(); |
| 360 | + |
| 361 | + // Repeat until all redirects have been resolved |
| 362 | + // The infinite loop is prevented by keeping all known pages in $this->mAllPages |
| 363 | + while (!empty ($this->mPendingRedirectIDs)) { |
| 364 | + |
| 365 | + // Resolve redirects by querying the pagelinks table, and repeat the process |
| 366 | + // Create a new linkBatch object for the next pass |
| 367 | + $linkBatch = $this->getRedirectTargets(); |
| 368 | + |
| 369 | + if ($linkBatch->isEmpty()) |
| 370 | + break; |
| 371 | + |
| 372 | + $set = $linkBatch->constructSet('page', $db); |
| 373 | + if(false === $set) |
| 374 | + break; |
| 375 | + |
| 376 | + // Get pageIDs data from the `page` table |
| 377 | + $this->profileDBIn(); |
| 378 | + $res = $db->select('page', $pageFlds, $set, __METHOD__); |
| 379 | + $this->profileDBOut(); |
| 380 | + |
| 381 | + // Hack: get the ns:titles stored in array(ns => array(titles)) format |
| 382 | + $this->initFromQueryResult($db, $res, $linkBatch->data, true); |
| 383 | + } |
241 | 384 | } |
242 | | - while (false !== ($set = $linkBatch->constructSet('page', $db))); |
243 | 385 | } |
244 | 386 | |
245 | | - private function resolveRedirectList($redirectIds) { |
| 387 | + private function getRedirectTargets() { |
246 | 388 | |
247 | 389 | $linkBatch = new LinkBatch(); |
248 | 390 | $db = $this->getDB(); |
— | — | @@ -253,7 +395,7 @@ |
254 | 396 | 'pl_namespace', |
255 | 397 | 'pl_title' |
256 | 398 | ), array ( |
257 | | - 'pl_from' => array_keys($redirectIds |
| 399 | + 'pl_from' => array_keys($this->mPendingRedirectIDs |
258 | 400 | )), __METHOD__); |
259 | 401 | $this->profileDBOut(); |
260 | 402 | |
— | — | @@ -265,11 +407,11 @@ |
266 | 408 | // ( http://bugzilla.wikipedia.org/show_bug.cgi?id=7304 ) |
267 | 409 | // A redirect page may have more than one link. |
268 | 410 | // This code will only use the first link returned. |
269 | | - if (isset ($redirectIds[$plfrom])) { // remove line when bug 7304 is fixed |
| 411 | + if (isset ($this->mPendingRedirectIDs[$plfrom])) { // remove line when bug 7304 is fixed |
270 | 412 | |
271 | | - $titleStrFrom = $redirectIds[$plfrom]->getPrefixedText(); |
| 413 | + $titleStrFrom = $this->mPendingRedirectIDs[$plfrom]->getPrefixedText(); |
272 | 414 | $titleStrTo = Title :: makeTitle($row->pl_namespace, $row->pl_title)->getPrefixedText(); |
273 | | - unset ($redirectIds[$plfrom]); // remove line when bug 7304 is fixed |
| 415 | + unset ($this->mPendingRedirectIDs[$plfrom]); // remove line when bug 7304 is fixed |
274 | 416 | |
275 | 417 | // Avoid an infinite loop by checking if we have already processed this target |
276 | 418 | if (!isset ($this->mAllPages[$row->pl_namespace][$row->pl_title])) { |
— | — | @@ -299,6 +441,10 @@ |
300 | 442 | } |
301 | 443 | $db->freeResult($res); |
302 | 444 | |
| 445 | + // All IDs must exist in the page table |
| 446 | + if (!empty($this->mPendingRedirectIDs[$plfrom])) |
| 447 | + $this->dieDebug('Invalid redirect IDs were found'); |
| 448 | + |
303 | 449 | return $linkBatch; |
304 | 450 | } |
305 | 451 | |
— | — | @@ -337,55 +483,6 @@ |
338 | 484 | return $linkBatch; |
339 | 485 | } |
340 | 486 | |
341 | | - private function populateRevIDs($revids) { |
342 | | - $this->dieUsage(__METHOD__ . ' is not implemented', 'notimplemented'); |
343 | | - } |
344 | | - |
345 | | - public function execute() { |
346 | | - $this->profileIn(); |
347 | | - $titles = $pageids = $revids = $redirects = null; |
348 | | - extract($this->extractRequestParams()); |
349 | | - |
350 | | - // Only one of the titles/pageids/revids is allowed at the same time |
351 | | - $dataSource = null; |
352 | | - if (isset ($titles)) |
353 | | - $dataSource = 'titles'; |
354 | | - if (isset ($pageids)) { |
355 | | - if (isset ($dataSource)) |
356 | | - $this->dieUsage("Cannot use 'pageids' at the same time as '$dataSource'", 'multisource'); |
357 | | - $dataSource = 'pageids'; |
358 | | - } |
359 | | - if (isset ($revids)) { |
360 | | - if (isset ($dataSource)) |
361 | | - $this->dieUsage("Cannot use 'revids' at the same time as '$dataSource'", 'multisource'); |
362 | | - $dataSource = 'revids'; |
363 | | - } |
364 | | - |
365 | | - switch ($dataSource) { |
366 | | - case 'titles' : |
367 | | - case 'pageids' : |
368 | | - $this->populatePages($titles, $pageids, $redirects); |
369 | | - break; |
370 | | - case 'revids' : |
371 | | - $this->populateRevIDs($revids); |
372 | | - break; |
373 | | - default : |
374 | | - // Do nothing - some queries do not need any of the data sources. |
375 | | - break; |
376 | | - } |
377 | | - $this->profileOut(); |
378 | | - } |
379 | | - |
380 | | - /** |
381 | | - * This method is used by generators to pass the list of pageIDs internaly |
382 | | - */ |
383 | | - public function executeForPageIDs($pageIDs) { |
384 | | - $this->profileIn(); |
385 | | - $pageIDs = array_map( 'intval', $pageIDs ); // paranoia |
386 | | - $this->populatePages(null, $pageIDs, $this->getParameter('redirects')); |
387 | | - $this->profileOut(); |
388 | | - } |
389 | | - |
390 | 487 | protected function getAllowedParams() { |
391 | 488 | return array ( |
392 | 489 | 'titles' => array ( |
— | — | @@ -398,8 +495,7 @@ |
399 | 496 | 'revids' => array ( |
400 | 497 | ApiBase :: PARAM_TYPE => 'integer', |
401 | 498 | ApiBase :: PARAM_ISMULTI => true |
402 | | - ), |
403 | | - 'redirects' => false |
| 499 | + ) |
404 | 500 | ); |
405 | 501 | } |
406 | 502 | |
— | — | @@ -407,8 +503,7 @@ |
408 | 504 | return array ( |
409 | 505 | 'titles' => 'A list of titles to work on', |
410 | 506 | 'pageids' => 'A list of page IDs to work on', |
411 | | - 'revids' => 'A list of revision IDs to work on', |
412 | | - 'redirects' => 'Automatically resolve redirects' |
| 507 | + 'revids' => 'A list of revision IDs to work on' |
413 | 508 | ); |
414 | 509 | } |
415 | 510 | |