MediaWiki  REL1_24
ApiResult.php
Go to the documentation of this file.
00001 <?php
00046 class ApiResult extends ApiBase {
00047 
00052     const OVERRIDE = 1;
00053 
00059     const ADD_ON_TOP = 2;
00060 
00067     const NO_SIZE_CHECK = 4;
00068 
00069     private $mData, $mIsRawMode, $mSize, $mCheckingSize;
00070 
00071     private $continueAllModules = array();
00072     private $continueGeneratedModules = array();
00073     private $continuationData = array();
00074     private $generatorContinuationData = array();
00075     private $generatorParams = array();
00076     private $generatorDone = false;
00077 
00081     public function __construct( ApiMain $main ) {
00082         parent::__construct( $main, 'result' );
00083         $this->mIsRawMode = false;
00084         $this->mCheckingSize = true;
00085         $this->reset();
00086     }
00087 
00091     public function reset() {
00092         $this->mData = array();
00093         $this->mSize = 0;
00094     }
00095 
00102     public function setRawMode( $flag = true ) {
00103         $this->mIsRawMode = $flag;
00104     }
00105 
00110     public function getIsRawMode() {
00111         return $this->mIsRawMode;
00112     }
00113 
00118     public function getData() {
00119         return $this->mData;
00120     }
00121 
00128     public static function size( $value ) {
00129         $s = 0;
00130         if ( is_array( $value ) ) {
00131             foreach ( $value as $v ) {
00132                 $s += self::size( $v );
00133             }
00134         } elseif ( !is_object( $value ) ) {
00135             // Objects can't always be cast to string
00136             $s = strlen( $value );
00137         }
00138 
00139         return $s;
00140     }
00141 
00146     public function getSize() {
00147         return $this->mSize;
00148     }
00149 
00156     public function disableSizeCheck() {
00157         $this->mCheckingSize = false;
00158     }
00159 
00164     public function enableSizeCheck() {
00165         $this->mCheckingSize = true;
00166     }
00167 
00181     public static function setElement( &$arr, $name, $value, $flags = 0 ) {
00182         if ( $arr === null || $name === null || $value === null
00183             || !is_array( $arr ) || is_array( $name )
00184         ) {
00185             ApiBase::dieDebug( __METHOD__, 'Bad parameter' );
00186         }
00187 
00188         $exists = isset( $arr[$name] );
00189         if ( !$exists || ( $flags & ApiResult::OVERRIDE ) ) {
00190             if ( !$exists && ( $flags & ApiResult::ADD_ON_TOP ) ) {
00191                 $arr = array( $name => $value ) + $arr;
00192             } else {
00193                 $arr[$name] = $value;
00194             }
00195         } elseif ( is_array( $arr[$name] ) && is_array( $value ) ) {
00196             $merged = array_intersect_key( $arr[$name], $value );
00197             if ( !count( $merged ) ) {
00198                 $arr[$name] += $value;
00199             } else {
00200                 ApiBase::dieDebug( __METHOD__, "Attempting to merge element $name" );
00201             }
00202         } else {
00203             ApiBase::dieDebug(
00204                 __METHOD__,
00205                 "Attempting to add element $name=$value, existing value is {$arr[$name]}"
00206             );
00207         }
00208     }
00209 
00219     public static function setContent( &$arr, $value, $subElemName = null ) {
00220         if ( is_array( $value ) ) {
00221             ApiBase::dieDebug( __METHOD__, 'Bad parameter' );
00222         }
00223         if ( is_null( $subElemName ) ) {
00224             ApiResult::setElement( $arr, '*', $value );
00225         } else {
00226             if ( !isset( $arr[$subElemName] ) ) {
00227                 $arr[$subElemName] = array();
00228             }
00229             ApiResult::setElement( $arr[$subElemName], '*', $value );
00230         }
00231     }
00232 
00239     public function setSubelements( &$arr, $names ) {
00240         // In raw mode, add the '_subelements', otherwise just ignore
00241         if ( !$this->getIsRawMode() ) {
00242             return;
00243         }
00244         if ( $arr === null || $names === null || !is_array( $arr ) ) {
00245             ApiBase::dieDebug( __METHOD__, 'Bad parameter' );
00246         }
00247         if ( !is_array( $names ) ) {
00248             $names = array( $names );
00249         }
00250         if ( !isset( $arr['_subelements'] ) ) {
00251             $arr['_subelements'] = $names;
00252         } else {
00253             $arr['_subelements'] = array_merge( $arr['_subelements'], $names );
00254         }
00255     }
00256 
00264     public function setIndexedTagName( &$arr, $tag ) {
00265         // In raw mode, add the '_element', otherwise just ignore
00266         if ( !$this->getIsRawMode() ) {
00267             return;
00268         }
00269         if ( $arr === null || $tag === null || !is_array( $arr ) || is_array( $tag ) ) {
00270             ApiBase::dieDebug( __METHOD__, 'Bad parameter' );
00271         }
00272         // Do not use setElement() as it is ok to call this more than once
00273         $arr['_element'] = $tag;
00274     }
00275 
00281     public function setIndexedTagName_recursive( &$arr, $tag ) {
00282         if ( !is_array( $arr ) ) {
00283             return;
00284         }
00285         foreach ( $arr as &$a ) {
00286             if ( !is_array( $a ) ) {
00287                 continue;
00288             }
00289             $this->setIndexedTagName( $a, $tag );
00290             $this->setIndexedTagName_recursive( $a, $tag );
00291         }
00292     }
00293 
00301     public function setIndexedTagName_internal( $path, $tag ) {
00302         $data = &$this->mData;
00303         foreach ( (array)$path as $p ) {
00304             if ( !isset( $data[$p] ) ) {
00305                 $data[$p] = array();
00306             }
00307             $data = &$data[$p];
00308         }
00309         if ( is_null( $data ) ) {
00310             return;
00311         }
00312         $this->setIndexedTagName( $data, $tag );
00313     }
00314 
00332     public function addValue( $path, $name, $value, $flags = 0 ) {
00333         $data = &$this->mData;
00334         if ( $this->mCheckingSize && !( $flags & ApiResult::NO_SIZE_CHECK ) ) {
00335             $newsize = $this->mSize + self::size( $value );
00336             $maxResultSize = $this->getConfig()->get( 'APIMaxResultSize' );
00337             if ( $newsize > $maxResultSize ) {
00338                 $this->setWarning(
00339                     "This result was truncated because it would otherwise be larger than the " .
00340                         "limit of {$maxResultSize} bytes" );
00341 
00342                 return false;
00343             }
00344             $this->mSize = $newsize;
00345         }
00346 
00347         $addOnTop = $flags & ApiResult::ADD_ON_TOP;
00348         if ( $path !== null ) {
00349             foreach ( (array)$path as $p ) {
00350                 if ( !isset( $data[$p] ) ) {
00351                     if ( $addOnTop ) {
00352                         $data = array( $p => array() ) + $data;
00353                         $addOnTop = false;
00354                     } else {
00355                         $data[$p] = array();
00356                     }
00357                 }
00358                 $data = &$data[$p];
00359             }
00360         }
00361 
00362         if ( !$name ) {
00363             // Add list element
00364             if ( $addOnTop ) {
00365                 // This element needs to be inserted in the beginning
00366                 // Numerical indexes will be renumbered
00367                 array_unshift( $data, $value );
00368             } else {
00369                 // Add new value at the end
00370                 $data[] = $value;
00371             }
00372         } else {
00373             // Add named element
00374             self::setElement( $data, $name, $value, $flags );
00375         }
00376 
00377         return true;
00378     }
00379 
00386     public function setParsedLimit( $moduleName, $limit ) {
00387         // Add value, allowing overwriting
00388         $this->addValue( 'limits', $moduleName, $limit, ApiResult::OVERRIDE );
00389     }
00390 
00398     public function unsetValue( $path, $name ) {
00399         $data = &$this->mData;
00400         if ( $path !== null ) {
00401             foreach ( (array)$path as $p ) {
00402                 if ( !isset( $data[$p] ) ) {
00403                     return;
00404                 }
00405                 $data = &$data[$p];
00406             }
00407         }
00408         $this->mSize -= self::size( $data[$name] );
00409         unset( $data[$name] );
00410     }
00411 
00415     public function cleanUpUTF8() {
00416         array_walk_recursive( $this->mData, array( 'ApiResult', 'cleanUp_helper' ) );
00417     }
00418 
00424     private static function cleanUp_helper( &$s ) {
00425         if ( !is_string( $s ) ) {
00426             return;
00427         }
00428         global $wgContLang;
00429         $s = $wgContLang->normalize( $s );
00430     }
00431 
00438     public function convertStatusToArray( $status, $errorType = 'error' ) {
00439         if ( $status->isGood() ) {
00440             return array();
00441         }
00442 
00443         $result = array();
00444         foreach ( $status->getErrorsByType( $errorType ) as $error ) {
00445             $this->setIndexedTagName( $error['params'], 'param' );
00446             $result[] = $error;
00447         }
00448         $this->setIndexedTagName( $result, $errorType );
00449 
00450         return $result;
00451     }
00452 
00453     public function execute() {
00454         ApiBase::dieDebug( __METHOD__, 'execute() is not supported on Result object' );
00455     }
00456 
00469     public function beginContinuation(
00470         $continue, array $allModules = array(), array $generatedModules = array()
00471     ) {
00472         $this->continueGeneratedModules = $generatedModules
00473             ? array_combine( $generatedModules, $generatedModules )
00474             : array();
00475         $this->continuationData = array();
00476         $this->generatorContinuationData = array();
00477         $this->generatorParams = array();
00478 
00479         $skip = array();
00480         if ( is_string( $continue ) && $continue !== '' ) {
00481             $continue = explode( '||', $continue );
00482             $this->dieContinueUsageIf( count( $continue ) !== 2 );
00483             $this->generatorDone = ( $continue[0] === '-' );
00484             if ( !$this->generatorDone ) {
00485                 $this->generatorParams = explode( '|', $continue[0] );
00486             }
00487             $skip = explode( '|', $continue[1] );
00488         }
00489 
00490         $this->continueAllModules = array();
00491         $runModules = array();
00492         foreach ( $allModules as $module ) {
00493             $name = $module->getModuleName();
00494             if ( in_array( $name, $skip ) ) {
00495                 $this->continueAllModules[$name] = false;
00496                 // Prevent spurious "unused parameter" warnings
00497                 $module->extractRequestParams();
00498             } else {
00499                 $this->continueAllModules[$name] = true;
00500                 $runModules[] = $module;
00501             }
00502         }
00503 
00504         return array(
00505             $this->generatorDone,
00506             $runModules,
00507         );
00508     }
00509 
00518     public function setContinueParam( ApiBase $module, $paramName, $paramValue ) {
00519         $name = $module->getModuleName();
00520         if ( !isset( $this->continueAllModules[$name] ) ) {
00521             throw new MWException(
00522                 "Module '$name' called ApiResult::setContinueParam but was not " .
00523                 'passed to ApiResult::beginContinuation'
00524             );
00525         }
00526         if ( !$this->continueAllModules[$name] ) {
00527             throw new MWException(
00528                 "Module '$name' was not supposed to have been executed, but " .
00529                 'it was executed anyway'
00530             );
00531         }
00532         $paramName = $module->encodeParamName( $paramName );
00533         if ( is_array( $paramValue ) ) {
00534             $paramValue = join( '|', $paramValue );
00535         }
00536         $this->continuationData[$name][$paramName] = $paramValue;
00537     }
00538 
00547     public function setGeneratorContinueParam( ApiBase $module, $paramName, $paramValue ) {
00548         $name = $module->getModuleName();
00549         $paramName = $module->encodeParamName( $paramName );
00550         if ( is_array( $paramValue ) ) {
00551             $paramValue = join( '|', $paramValue );
00552         }
00553         $this->generatorContinuationData[$name][$paramName] = $paramValue;
00554     }
00555 
00563     public function endContinuation( $style = 'standard' ) {
00564         if ( $style === 'raw' ) {
00565             $key = 'query-continue';
00566             $data = array_merge_recursive(
00567                 $this->continuationData, $this->generatorContinuationData
00568             );
00569         } else {
00570             $key = 'continue';
00571             $data = array();
00572 
00573             $finishedModules = array_diff(
00574                 array_keys( $this->continueAllModules ),
00575                 array_keys( $this->continuationData )
00576             );
00577 
00578             // First, grab the non-generator-using continuation data
00579             $continuationData = array_diff_key(
00580                 $this->continuationData, $this->continueGeneratedModules
00581             );
00582             foreach ( $continuationData as $module => $kvp ) {
00583                 $data += $kvp;
00584             }
00585 
00586             // Next, handle the generator-using continuation data
00587             $continuationData = array_intersect_key(
00588                 $this->continuationData, $this->continueGeneratedModules
00589             );
00590             if ( $continuationData ) {
00591                 // Some modules are unfinished: include those params, and copy
00592                 // the generator params.
00593                 foreach ( $continuationData as $module => $kvp ) {
00594                     $data += $kvp;
00595                 }
00596                 $data += array_intersect_key(
00597                     $this->getMain()->getRequest()->getValues(),
00598                     array_flip( $this->generatorParams )
00599                 );
00600             } elseif ( $this->generatorContinuationData ) {
00601                 // All the generator-using modules are complete, but the
00602                 // generator isn't. Continue the generator and restart the
00603                 // generator-using modules
00604                 $this->generatorParams = array();
00605                 foreach ( $this->generatorContinuationData as $kvp ) {
00606                     $this->generatorParams = array_merge(
00607                         $this->generatorParams, array_keys( $kvp )
00608                     );
00609                     $data += $kvp;
00610                 }
00611                 $finishedModules = array_diff(
00612                     $finishedModules, $this->continueGeneratedModules
00613                 );
00614             } else {
00615                 // Generator and prop modules are all done. Mark it so.
00616                 $this->generatorDone = true;
00617             }
00618 
00619             // Set 'continue' if any continuation data is set or if the generator
00620             // still needs to run
00621             if ( $data || !$this->generatorDone ) {
00622                 $data['continue'] =
00623                     ( $this->generatorDone ? '-' : join( '|', $this->generatorParams ) ) .
00624                     '||' . join( '|', $finishedModules );
00625             }
00626         }
00627         if ( $data ) {
00628             $this->addValue( null, $key, $data, ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK );
00629         }
00630     }
00631 }