[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * 4 * 5 * Created on Sep 4, 2006 6 * 7 * Copyright © 2006 Yuri Astrakhan "<Firstname><Lastname>@gmail.com" 8 * 9 * This program is free software; you can redistribute it and/or modify 10 * it under the terms of the GNU General Public License as published by 11 * the Free Software Foundation; either version 2 of the License, or 12 * (at your option) any later version. 13 * 14 * This program is distributed in the hope that it will be useful, 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 * GNU General Public License for more details. 18 * 19 * You should have received a copy of the GNU General Public License along 20 * with this program; if not, write to the Free Software Foundation, Inc., 21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 22 * http://www.gnu.org/copyleft/gpl.html 23 * 24 * @file 25 */ 26 27 /** 28 * This class represents the result of the API operations. 29 * It simply wraps a nested array() structure, adding some functions to simplify 30 * array's modifications. As various modules execute, they add different pieces 31 * of information to this result, structuring it as it will be given to the client. 32 * 33 * Each subarray may either be a dictionary - key-value pairs with unique keys, 34 * or lists, where the items are added using $data[] = $value notation. 35 * 36 * There are three special key values that change how XML output is generated: 37 * '_element' This key sets the tag name for the rest of the elements in the current array. 38 * It is only inserted if the formatter returned true for getNeedsRawData() 39 * '_subelements' This key causes the specified elements to be returned as subelements rather than attributes. 40 * It is only inserted if the formatter returned true for getNeedsRawData() 41 * '*' This key has special meaning only to the XML formatter, and is outputted as is 42 * for all others. In XML it becomes the content of the current element. 43 * 44 * @ingroup API 45 */ 46 class ApiResult extends ApiBase { 47 48 /** 49 * override existing value in addValue() and setElement() 50 * @since 1.21 51 */ 52 const OVERRIDE = 1; 53 54 /** 55 * For addValue() and setElement(), if the value does not exist, add it as the first element. 56 * In case the new value has no name (numerical index), all indexes will be renumbered. 57 * @since 1.21 58 */ 59 const ADD_ON_TOP = 2; 60 61 /** 62 * For addValue() and setElement(), do not check size while adding a value 63 * Don't use this unless you REALLY know what you're doing. 64 * Values added while the size checking was disabled will never be counted 65 * @since 1.24 66 */ 67 const NO_SIZE_CHECK = 4; 68 69 private $mData, $mIsRawMode, $mSize, $mCheckingSize; 70 71 private $continueAllModules = array(); 72 private $continueGeneratedModules = array(); 73 private $continuationData = array(); 74 private $generatorContinuationData = array(); 75 private $generatorParams = array(); 76 private $generatorDone = false; 77 78 /** 79 * @param ApiMain $main 80 */ 81 public function __construct( ApiMain $main ) { 82 parent::__construct( $main, 'result' ); 83 $this->mIsRawMode = false; 84 $this->mCheckingSize = true; 85 $this->reset(); 86 } 87 88 /** 89 * Clear the current result data. 90 */ 91 public function reset() { 92 $this->mData = array(); 93 $this->mSize = 0; 94 } 95 96 /** 97 * Call this function when special elements such as '_element' 98 * are needed by the formatter, for example in XML printing. 99 * @since 1.23 $flag parameter added 100 * @param bool $flag Set the raw mode flag to this state 101 */ 102 public function setRawMode( $flag = true ) { 103 $this->mIsRawMode = $flag; 104 } 105 106 /** 107 * Returns true whether the formatter requested raw data. 108 * @return bool 109 */ 110 public function getIsRawMode() { 111 return $this->mIsRawMode; 112 } 113 114 /** 115 * Get the result's internal data array (read-only) 116 * @return array 117 */ 118 public function getData() { 119 return $this->mData; 120 } 121 122 /** 123 * Get the 'real' size of a result item. This means the strlen() of the item, 124 * or the sum of the strlen()s of the elements if the item is an array. 125 * @param mixed $value 126 * @return int 127 */ 128 public static function size( $value ) { 129 $s = 0; 130 if ( is_array( $value ) ) { 131 foreach ( $value as $v ) { 132 $s += self::size( $v ); 133 } 134 } elseif ( !is_object( $value ) ) { 135 // Objects can't always be cast to string 136 $s = strlen( $value ); 137 } 138 139 return $s; 140 } 141 142 /** 143 * Get the size of the result, i.e. the amount of bytes in it 144 * @return int 145 */ 146 public function getSize() { 147 return $this->mSize; 148 } 149 150 /** 151 * Disable size checking in addValue(). Don't use this unless you 152 * REALLY know what you're doing. Values added while size checking 153 * was disabled will not be counted (ever) 154 * @deprecated since 1.24, use ApiResult::NO_SIZE_CHECK 155 */ 156 public function disableSizeCheck() { 157 $this->mCheckingSize = false; 158 } 159 160 /** 161 * Re-enable size checking in addValue() 162 * @deprecated since 1.24, use ApiResult::NO_SIZE_CHECK 163 */ 164 public function enableSizeCheck() { 165 $this->mCheckingSize = true; 166 } 167 168 /** 169 * Add an output value to the array by name. 170 * Verifies that value with the same name has not been added before. 171 * @param array $arr To add $value to 172 * @param string $name Index of $arr to add $value at 173 * @param mixed $value 174 * @param int $flags Zero or more OR-ed flags like OVERRIDE | ADD_ON_TOP. 175 * This parameter used to be boolean, and the value of OVERRIDE=1 was 176 * specifically chosen so that it would be backwards compatible with the 177 * new method signature. 178 * 179 * @since 1.21 int $flags replaced boolean $override 180 */ 181 public static function setElement( &$arr, $name, $value, $flags = 0 ) { 182 if ( $arr === null || $name === null || $value === null 183 || !is_array( $arr ) || is_array( $name ) 184 ) { 185 ApiBase::dieDebug( __METHOD__, 'Bad parameter' ); 186 } 187 188 $exists = isset( $arr[$name] ); 189 if ( !$exists || ( $flags & ApiResult::OVERRIDE ) ) { 190 if ( !$exists && ( $flags & ApiResult::ADD_ON_TOP ) ) { 191 $arr = array( $name => $value ) + $arr; 192 } else { 193 $arr[$name] = $value; 194 } 195 } elseif ( is_array( $arr[$name] ) && is_array( $value ) ) { 196 $merged = array_intersect_key( $arr[$name], $value ); 197 if ( !count( $merged ) ) { 198 $arr[$name] += $value; 199 } else { 200 ApiBase::dieDebug( __METHOD__, "Attempting to merge element $name" ); 201 } 202 } else { 203 ApiBase::dieDebug( 204 __METHOD__, 205 "Attempting to add element $name=$value, existing value is {$arr[$name]}" 206 ); 207 } 208 } 209 210 /** 211 * Adds a content element to an array. 212 * Use this function instead of hardcoding the '*' element. 213 * @param array $arr To add the content element to 214 * @param mixed $value 215 * @param string $subElemName When present, content element is created 216 * as a sub item of $arr. Use this parameter to create elements in 217 * format "<elem>text</elem>" without attributes. 218 */ 219 public static function setContent( &$arr, $value, $subElemName = null ) { 220 if ( is_array( $value ) ) { 221 ApiBase::dieDebug( __METHOD__, 'Bad parameter' ); 222 } 223 if ( is_null( $subElemName ) ) { 224 ApiResult::setElement( $arr, '*', $value ); 225 } else { 226 if ( !isset( $arr[$subElemName] ) ) { 227 $arr[$subElemName] = array(); 228 } 229 ApiResult::setElement( $arr[$subElemName], '*', $value ); 230 } 231 } 232 233 /** 234 * Causes the elements with the specified names to be output as 235 * subelements rather than attributes. 236 * @param array $arr 237 * @param array|string $names The element name(s) to be output as subelements 238 */ 239 public function setSubelements( &$arr, $names ) { 240 // In raw mode, add the '_subelements', otherwise just ignore 241 if ( !$this->getIsRawMode() ) { 242 return; 243 } 244 if ( $arr === null || $names === null || !is_array( $arr ) ) { 245 ApiBase::dieDebug( __METHOD__, 'Bad parameter' ); 246 } 247 if ( !is_array( $names ) ) { 248 $names = array( $names ); 249 } 250 if ( !isset( $arr['_subelements'] ) ) { 251 $arr['_subelements'] = $names; 252 } else { 253 $arr['_subelements'] = array_merge( $arr['_subelements'], $names ); 254 } 255 } 256 257 /** 258 * In case the array contains indexed values (in addition to named), 259 * give all indexed values the given tag name. This function MUST be 260 * called on every array that has numerical indexes. 261 * @param array $arr 262 * @param string $tag Tag name 263 */ 264 public function setIndexedTagName( &$arr, $tag ) { 265 // In raw mode, add the '_element', otherwise just ignore 266 if ( !$this->getIsRawMode() ) { 267 return; 268 } 269 if ( $arr === null || $tag === null || !is_array( $arr ) || is_array( $tag ) ) { 270 ApiBase::dieDebug( __METHOD__, 'Bad parameter' ); 271 } 272 // Do not use setElement() as it is ok to call this more than once 273 $arr['_element'] = $tag; 274 } 275 276 /** 277 * Calls setIndexedTagName() on each sub-array of $arr 278 * @param array $arr 279 * @param string $tag Tag name 280 */ 281 public function setIndexedTagName_recursive( &$arr, $tag ) { 282 if ( !is_array( $arr ) ) { 283 return; 284 } 285 foreach ( $arr as &$a ) { 286 if ( !is_array( $a ) ) { 287 continue; 288 } 289 $this->setIndexedTagName( $a, $tag ); 290 $this->setIndexedTagName_recursive( $a, $tag ); 291 } 292 } 293 294 /** 295 * Calls setIndexedTagName() on an array already in the result. 296 * Don't specify a path to a value that's not in the result, or 297 * you'll get nasty errors. 298 * @param array $path Path to the array, like addValue()'s $path 299 * @param string $tag 300 */ 301 public function setIndexedTagName_internal( $path, $tag ) { 302 $data = &$this->mData; 303 foreach ( (array)$path as $p ) { 304 if ( !isset( $data[$p] ) ) { 305 $data[$p] = array(); 306 } 307 $data = &$data[$p]; 308 } 309 if ( is_null( $data ) ) { 310 return; 311 } 312 $this->setIndexedTagName( $data, $tag ); 313 } 314 315 /** 316 * Add value to the output data at the given path. 317 * Path can be an indexed array, each element specifying the branch at which to add the new 318 * value. Setting $path to array('a','b','c') is equivalent to data['a']['b']['c'] = $value. 319 * If $path is null, the value will be inserted at the data root. 320 * If $name is empty, the $value is added as a next list element data[] = $value. 321 * 322 * @param array|string|null $path 323 * @param string $name 324 * @param mixed $value 325 * @param int $flags Zero or more OR-ed flags like OVERRIDE | ADD_ON_TOP. 326 * This parameter used to be boolean, and the value of OVERRIDE=1 was specifically 327 * chosen so that it would be backwards compatible with the new method signature. 328 * @return bool True if $value fits in the result, false if not 329 * 330 * @since 1.21 int $flags replaced boolean $override 331 */ 332 public function addValue( $path, $name, $value, $flags = 0 ) { 333 $data = &$this->mData; 334 if ( $this->mCheckingSize && !( $flags & ApiResult::NO_SIZE_CHECK ) ) { 335 $newsize = $this->mSize + self::size( $value ); 336 $maxResultSize = $this->getConfig()->get( 'APIMaxResultSize' ); 337 if ( $newsize > $maxResultSize ) { 338 $this->setWarning( 339 "This result was truncated because it would otherwise be larger than the " . 340 "limit of {$maxResultSize} bytes" ); 341 342 return false; 343 } 344 $this->mSize = $newsize; 345 } 346 347 $addOnTop = $flags & ApiResult::ADD_ON_TOP; 348 if ( $path !== null ) { 349 foreach ( (array)$path as $p ) { 350 if ( !isset( $data[$p] ) ) { 351 if ( $addOnTop ) { 352 $data = array( $p => array() ) + $data; 353 $addOnTop = false; 354 } else { 355 $data[$p] = array(); 356 } 357 } 358 $data = &$data[$p]; 359 } 360 } 361 362 if ( !$name ) { 363 // Add list element 364 if ( $addOnTop ) { 365 // This element needs to be inserted in the beginning 366 // Numerical indexes will be renumbered 367 array_unshift( $data, $value ); 368 } else { 369 // Add new value at the end 370 $data[] = $value; 371 } 372 } else { 373 // Add named element 374 self::setElement( $data, $name, $value, $flags ); 375 } 376 377 return true; 378 } 379 380 /** 381 * Add a parsed limit=max to the result. 382 * 383 * @param string $moduleName 384 * @param int $limit 385 */ 386 public function setParsedLimit( $moduleName, $limit ) { 387 // Add value, allowing overwriting 388 $this->addValue( 'limits', $moduleName, $limit, ApiResult::OVERRIDE ); 389 } 390 391 /** 392 * Unset a value previously added to the result set. 393 * Fails silently if the value isn't found. 394 * For parameters, see addValue() 395 * @param array|null $path 396 * @param string $name 397 */ 398 public function unsetValue( $path, $name ) { 399 $data = &$this->mData; 400 if ( $path !== null ) { 401 foreach ( (array)$path as $p ) { 402 if ( !isset( $data[$p] ) ) { 403 return; 404 } 405 $data = &$data[$p]; 406 } 407 } 408 $this->mSize -= self::size( $data[$name] ); 409 unset( $data[$name] ); 410 } 411 412 /** 413 * Ensure all values in this result are valid UTF-8. 414 */ 415 public function cleanUpUTF8() { 416 array_walk_recursive( $this->mData, array( 'ApiResult', 'cleanUp_helper' ) ); 417 } 418 419 /** 420 * Callback function for cleanUpUTF8() 421 * 422 * @param string $s 423 */ 424 private static function cleanUp_helper( &$s ) { 425 if ( !is_string( $s ) ) { 426 return; 427 } 428 global $wgContLang; 429 $s = $wgContLang->normalize( $s ); 430 } 431 432 /** 433 * Converts a Status object to an array suitable for addValue 434 * @param Status $status 435 * @param string $errorType 436 * @return array 437 */ 438 public function convertStatusToArray( $status, $errorType = 'error' ) { 439 if ( $status->isGood() ) { 440 return array(); 441 } 442 443 $result = array(); 444 foreach ( $status->getErrorsByType( $errorType ) as $error ) { 445 $this->setIndexedTagName( $error['params'], 'param' ); 446 $result[] = $error; 447 } 448 $this->setIndexedTagName( $result, $errorType ); 449 450 return $result; 451 } 452 453 public function execute() { 454 ApiBase::dieDebug( __METHOD__, 'execute() is not supported on Result object' ); 455 } 456 457 /** 458 * Parse a 'continue' parameter and return status information. 459 * 460 * This must be balanced by a call to endContinuation(). 461 * 462 * @since 1.24 463 * @param string|null $continue The "continue" parameter, if any 464 * @param ApiBase[] $allModules Contains ApiBase instances that will be executed 465 * @param array $generatedModules Names of modules that depend on the generator 466 * @return array Two elements: a boolean indicating if the generator is done, 467 * and an array of modules to actually execute. 468 */ 469 public function beginContinuation( 470 $continue, array $allModules = array(), array $generatedModules = array() 471 ) { 472 $this->continueGeneratedModules = $generatedModules 473 ? array_combine( $generatedModules, $generatedModules ) 474 : array(); 475 $this->continuationData = array(); 476 $this->generatorContinuationData = array(); 477 $this->generatorParams = array(); 478 479 $skip = array(); 480 if ( is_string( $continue ) && $continue !== '' ) { 481 $continue = explode( '||', $continue ); 482 $this->dieContinueUsageIf( count( $continue ) !== 2 ); 483 $this->generatorDone = ( $continue[0] === '-' ); 484 if ( !$this->generatorDone ) { 485 $this->generatorParams = explode( '|', $continue[0] ); 486 } 487 $skip = explode( '|', $continue[1] ); 488 } 489 490 $this->continueAllModules = array(); 491 $runModules = array(); 492 foreach ( $allModules as $module ) { 493 $name = $module->getModuleName(); 494 if ( in_array( $name, $skip ) ) { 495 $this->continueAllModules[$name] = false; 496 // Prevent spurious "unused parameter" warnings 497 $module->extractRequestParams(); 498 } else { 499 $this->continueAllModules[$name] = true; 500 $runModules[] = $module; 501 } 502 } 503 504 return array( 505 $this->generatorDone, 506 $runModules, 507 ); 508 } 509 510 /** 511 * Set the continuation parameter for a module 512 * 513 * @since 1.24 514 * @param ApiBase $module 515 * @param string $paramName 516 * @param string|array $paramValue 517 */ 518 public function setContinueParam( ApiBase $module, $paramName, $paramValue ) { 519 $name = $module->getModuleName(); 520 if ( !isset( $this->continueAllModules[$name] ) ) { 521 throw new MWException( 522 "Module '$name' called ApiResult::setContinueParam but was not " . 523 'passed to ApiResult::beginContinuation' 524 ); 525 } 526 if ( !$this->continueAllModules[$name] ) { 527 throw new MWException( 528 "Module '$name' was not supposed to have been executed, but " . 529 'it was executed anyway' 530 ); 531 } 532 $paramName = $module->encodeParamName( $paramName ); 533 if ( is_array( $paramValue ) ) { 534 $paramValue = join( '|', $paramValue ); 535 } 536 $this->continuationData[$name][$paramName] = $paramValue; 537 } 538 539 /** 540 * Set the continuation parameter for the generator module 541 * 542 * @since 1.24 543 * @param ApiBase $module 544 * @param string $paramName 545 * @param string|array $paramValue 546 */ 547 public function setGeneratorContinueParam( ApiBase $module, $paramName, $paramValue ) { 548 $name = $module->getModuleName(); 549 $paramName = $module->encodeParamName( $paramName ); 550 if ( is_array( $paramValue ) ) { 551 $paramValue = join( '|', $paramValue ); 552 } 553 $this->generatorContinuationData[$name][$paramName] = $paramValue; 554 } 555 556 /** 557 * Close continuation, writing the data into the result 558 * 559 * @since 1.24 560 * @param string $style 'standard' for the new style since 1.21, 'raw' for 561 * the style used in 1.20 and earlier. 562 */ 563 public function endContinuation( $style = 'standard' ) { 564 if ( $style === 'raw' ) { 565 $key = 'query-continue'; 566 $data = array_merge_recursive( 567 $this->continuationData, $this->generatorContinuationData 568 ); 569 } else { 570 $key = 'continue'; 571 $data = array(); 572 573 $finishedModules = array_diff( 574 array_keys( $this->continueAllModules ), 575 array_keys( $this->continuationData ) 576 ); 577 578 // First, grab the non-generator-using continuation data 579 $continuationData = array_diff_key( 580 $this->continuationData, $this->continueGeneratedModules 581 ); 582 foreach ( $continuationData as $module => $kvp ) { 583 $data += $kvp; 584 } 585 586 // Next, handle the generator-using continuation data 587 $continuationData = array_intersect_key( 588 $this->continuationData, $this->continueGeneratedModules 589 ); 590 if ( $continuationData ) { 591 // Some modules are unfinished: include those params, and copy 592 // the generator params. 593 foreach ( $continuationData as $module => $kvp ) { 594 $data += $kvp; 595 } 596 $data += array_intersect_key( 597 $this->getMain()->getRequest()->getValues(), 598 array_flip( $this->generatorParams ) 599 ); 600 } elseif ( $this->generatorContinuationData ) { 601 // All the generator-using modules are complete, but the 602 // generator isn't. Continue the generator and restart the 603 // generator-using modules 604 $this->generatorParams = array(); 605 foreach ( $this->generatorContinuationData as $kvp ) { 606 $this->generatorParams = array_merge( 607 $this->generatorParams, array_keys( $kvp ) 608 ); 609 $data += $kvp; 610 } 611 $finishedModules = array_diff( 612 $finishedModules, $this->continueGeneratedModules 613 ); 614 } else { 615 // Generator and prop modules are all done. Mark it so. 616 $this->generatorDone = true; 617 } 618 619 // Set 'continue' if any continuation data is set or if the generator 620 // still needs to run 621 if ( $data || !$this->generatorDone ) { 622 $data['continue'] = 623 ( $this->generatorDone ? '-' : join( '|', $this->generatorParams ) ) . 624 '||' . join( '|', $finishedModules ); 625 } 626 } 627 if ( $data ) { 628 $this->addValue( null, $key, $data, ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK ); 629 } 630 } 631 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |