ApiResult.php 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. <?php
  2. /*
  3. * Created on Sep 4, 2006
  4. *
  5. * API for MediaWiki 1.8+
  6. *
  7. * Copyright (C) 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. * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  22. * http://www.gnu.org/copyleft/gpl.html
  23. */
  24. if (!defined('MEDIAWIKI')) {
  25. // Eclipse helper - will be ignored in production
  26. require_once ('ApiBase.php');
  27. }
  28. /**
  29. * This class represents the result of the API operations.
  30. * It simply wraps a nested array() structure, adding some functions to simplify array's modifications.
  31. * As various modules execute, they add different pieces of information to this result,
  32. * structuring it as it will be given to the client.
  33. *
  34. * Each subarray may either be a dictionary - key-value pairs with unique keys,
  35. * or lists, where the items are added using $data[] = $value notation.
  36. *
  37. * There are two special key values that change how XML output is generated:
  38. * '_element' This key sets the tag name for the rest of the elements in the current array.
  39. * It is only inserted if the formatter returned true for getNeedsRawData()
  40. * '*' This key has special meaning only to the XML formatter, and is outputed as is
  41. * for all others. In XML it becomes the content of the current element.
  42. *
  43. * @ingroup API
  44. */
  45. class ApiResult extends ApiBase {
  46. private $mData, $mIsRawMode, $mSize, $mCheckingSize;
  47. /**
  48. * Constructor
  49. * @param $main ApiMain object
  50. */
  51. public function __construct($main) {
  52. parent :: __construct($main, 'result');
  53. $this->mIsRawMode = false;
  54. $this->mCheckingSize = true;
  55. $this->reset();
  56. }
  57. /**
  58. * Clear the current result data.
  59. */
  60. public function reset() {
  61. $this->mData = array ();
  62. $this->mSize = 0;
  63. }
  64. /**
  65. * Call this function when special elements such as '_element'
  66. * are needed by the formatter, for example in XML printing.
  67. */
  68. public function setRawMode() {
  69. $this->mIsRawMode = true;
  70. }
  71. /**
  72. * Returns true whether the formatter requested raw data.
  73. * @return bool
  74. */
  75. public function getIsRawMode() {
  76. return $this->mIsRawMode;
  77. }
  78. /**
  79. * Get the result's internal data array (read-only)
  80. * @return array
  81. */
  82. public function getData() {
  83. return $this->mData;
  84. }
  85. /**
  86. * Get the 'real' size of a result item. This means the strlen() of the item,
  87. * or the sum of the strlen()s of the elements if the item is an array.
  88. * @param $value mixed
  89. * @return int
  90. */
  91. public static function size($value) {
  92. $s = 0;
  93. if(is_array($value))
  94. foreach($value as $v)
  95. $s += self::size($v);
  96. else if(!is_object($value))
  97. // Objects can't always be cast to string
  98. $s = strlen($value);
  99. return $s;
  100. }
  101. /**
  102. * Get the size of the result, i.e. the amount of bytes in it
  103. * @return int
  104. */
  105. public function getSize() {
  106. return $this->mSize;
  107. }
  108. /**
  109. * Disable size checking in addValue(). Don't use this unless you
  110. * REALLY know what you're doing. Values added while size checking
  111. * was disabled will not be counted (ever)
  112. */
  113. public function disableSizeCheck() {
  114. $this->mCheckingSize = false;
  115. }
  116. /**
  117. * Re-enable size checking in addValue()
  118. */
  119. public function enableSizeCheck() {
  120. $this->mCheckingSize = true;
  121. }
  122. /**
  123. * Add an output value to the array by name.
  124. * Verifies that value with the same name has not been added before.
  125. * @param $arr array to add $value to
  126. * @param $name string Index of $arr to add $value at
  127. * @param $value mixed
  128. */
  129. public static function setElement(& $arr, $name, $value) {
  130. if ($arr === null || $name === null || $value === null || !is_array($arr) || is_array($name))
  131. ApiBase :: dieDebug(__METHOD__, 'Bad parameter');
  132. if (!isset ($arr[$name])) {
  133. $arr[$name] = $value;
  134. }
  135. elseif (is_array($arr[$name]) && is_array($value)) {
  136. $merged = array_intersect_key($arr[$name], $value);
  137. if (!count($merged))
  138. $arr[$name] += $value;
  139. else
  140. ApiBase :: dieDebug(__METHOD__, "Attempting to merge element $name");
  141. } else
  142. ApiBase :: dieDebug(__METHOD__, "Attempting to add element $name=$value, existing value is {$arr[$name]}");
  143. }
  144. /**
  145. * Adds a content element to an array.
  146. * Use this function instead of hardcoding the '*' element.
  147. * @param $arr array to add the content element to
  148. * @param $subElemName string when present, content element is created
  149. * as a sub item of $arr. Use this parameter to create elements in
  150. * format <elem>text</elem> without attributes
  151. */
  152. public static function setContent(& $arr, $value, $subElemName = null) {
  153. if (is_array($value))
  154. ApiBase :: dieDebug(__METHOD__, 'Bad parameter');
  155. if (is_null($subElemName)) {
  156. ApiResult :: setElement($arr, '*', $value);
  157. } else {
  158. if (!isset ($arr[$subElemName]))
  159. $arr[$subElemName] = array ();
  160. ApiResult :: setElement($arr[$subElemName], '*', $value);
  161. }
  162. }
  163. /**
  164. * In case the array contains indexed values (in addition to named),
  165. * give all indexed values the given tag name. This function MUST be
  166. * called on every arrray that has numerical indexes.
  167. * @param $arr array
  168. * @param $tag string Tag name
  169. */
  170. public function setIndexedTagName(& $arr, $tag) {
  171. // In raw mode, add the '_element', otherwise just ignore
  172. if (!$this->getIsRawMode())
  173. return;
  174. if ($arr === null || $tag === null || !is_array($arr) || is_array($tag))
  175. ApiBase :: dieDebug(__METHOD__, 'Bad parameter');
  176. // Do not use setElement() as it is ok to call this more than once
  177. $arr['_element'] = $tag;
  178. }
  179. /**
  180. * Calls setIndexedTagName() on each sub-array of $arr
  181. * @param $arr array
  182. * @param $tag string Tag name
  183. */
  184. public function setIndexedTagName_recursive(&$arr, $tag)
  185. {
  186. if(!is_array($arr))
  187. return;
  188. foreach($arr as &$a)
  189. {
  190. if(!is_array($a))
  191. continue;
  192. $this->setIndexedTagName($a, $tag);
  193. $this->setIndexedTagName_recursive($a, $tag);
  194. }
  195. }
  196. /**
  197. * Calls setIndexedTagName() on an array already in the result.
  198. * Don't specify a path to a value that's not in the result, or
  199. * you'll get nasty errors.
  200. * @param $path array Path to the array, like addValue()'s $path
  201. * @param $tag string
  202. */
  203. public function setIndexedTagName_internal( $path, $tag ) {
  204. $data = & $this->mData;
  205. foreach((array)$path as $p) {
  206. if ( !isset( $data[$p] ) ) {
  207. $data[$p] = array();
  208. }
  209. $data = & $data[$p];
  210. }
  211. if(is_null($data))
  212. return;
  213. $this->setIndexedTagName($data, $tag);
  214. }
  215. /**
  216. * Add value to the output data at the given path.
  217. * Path is an indexed array, each element specifing the branch at which to add the new value
  218. * Setting $path to array('a','b','c') is equivalent to data['a']['b']['c'] = $value
  219. * If $name is empty, the $value is added as a next list element data[] = $value
  220. * @return bool True if $value fits in the result, false if not
  221. */
  222. public function addValue($path, $name, $value) {
  223. global $wgAPIMaxResultSize;
  224. $data = & $this->mData;
  225. if( $this->mCheckingSize ) {
  226. $newsize = $this->mSize + self::size($value);
  227. if($newsize > $wgAPIMaxResultSize)
  228. return false;
  229. $this->mSize = $newsize;
  230. }
  231. if (!is_null($path)) {
  232. if (is_array($path)) {
  233. foreach ($path as $p) {
  234. if (!isset ($data[$p]))
  235. $data[$p] = array ();
  236. $data = & $data[$p];
  237. }
  238. } else {
  239. if (!isset ($data[$path]))
  240. $data[$path] = array ();
  241. $data = & $data[$path];
  242. }
  243. }
  244. if (!$name)
  245. $data[] = $value; // Add list element
  246. else
  247. ApiResult :: setElement($data, $name, $value); // Add named element
  248. return true;
  249. }
  250. /**
  251. * Unset a value previously added to the result set.
  252. * Fails silently if the value isn't found.
  253. * For parameters, see addValue()
  254. * @param $path array
  255. * @param $name string
  256. */
  257. public function unsetValue($path, $name) {
  258. $data = & $this->mData;
  259. if(!is_null($path))
  260. foreach((array)$path as $p) {
  261. if(!isset($data[$p]))
  262. return;
  263. $data = & $data[$p];
  264. }
  265. $this->mSize -= self::size($data[$name]);
  266. unset($data[$name]);
  267. }
  268. /**
  269. * Ensure all values in this result are valid UTF-8.
  270. */
  271. public function cleanUpUTF8()
  272. {
  273. array_walk_recursive($this->mData, array('ApiResult', 'cleanUp_helper'));
  274. }
  275. /**
  276. * Callback function for cleanUpUTF8()
  277. */
  278. private static function cleanUp_helper(&$s)
  279. {
  280. if(!is_string($s))
  281. return;
  282. $s = UtfNormal::cleanUp($s);
  283. }
  284. public function execute() {
  285. ApiBase :: dieDebug(__METHOD__, 'execute() is not supported on Result object');
  286. }
  287. public function getVersion() {
  288. return __CLASS__ . ': $Id: ApiResult.php 47447 2009-02-18 12:41:28Z tstarling $';
  289. }
  290. }
  291. /* For compatibility with PHP versions < 5.1.0, define our own array_intersect_key function. */
  292. if (!function_exists('array_intersect_key')) {
  293. function array_intersect_key($isec, $keys) {
  294. $argc = func_num_args();
  295. if ($argc > 2) {
  296. for ($i = 1; $isec && $i < $argc; $i++) {
  297. $arr = func_get_arg($i);
  298. foreach (array_keys($isec) as $key) {
  299. if (!isset($arr[$key]))
  300. unset($isec[$key]);
  301. }
  302. }
  303. return $isec;
  304. } else {
  305. $res = array();
  306. foreach (array_keys($isec) as $key) {
  307. if (isset($keys[$key]))
  308. $res[$key] = $isec[$key];
  309. }
  310. return $res;
  311. }
  312. }
  313. }