PageRenderTime 49ms CodeModel.GetById 8ms RepoModel.GetById 0ms app.codeStats 0ms

/extensions/DataTransclusion/DataTransclusionSource.php

https://github.com/ChuguluGames/mediawiki-svn
PHP | 378 lines | 217 code | 63 blank | 98 comment | 48 complexity | 1135deb07dc2471a66ef4efbf5fd637e MD5 | raw file
  1. <?php
  2. /**
  3. * DataTransclusion Source base class
  4. *
  5. * @file
  6. * @ingroup Extensions
  7. * @author Daniel Kinzler for Wikimedia Deutschland
  8. * @copyright © 2010 Wikimedia Deutschland (Author: Daniel Kinzler)
  9. * @licence GNU General Public Licence 2.0 or later
  10. */
  11. if ( !defined( 'MEDIAWIKI' ) ) {
  12. echo( "This file is an extension to the MediaWiki software and cannot be used standalone.\n" );
  13. die( 1 );
  14. }
  15. /**
  16. * Baseclass representing a source of data transclusion. All logic for addressing, fetching, decoding and filtering
  17. * data is encapsulated by a subclass of DataTransclusionSource. Instances of DataTransclusionSource are instantiated
  18. * by DataTransclusionHandler, and initialized by passing an associative array of options to the constructor. This array
  19. * is taken from the $wgDataTransclusionSources configuration variable.
  20. *
  21. * Below is a list of options for the $spec array, as handled by the DataTransclusionSource
  22. * base class (sublcasses may handle additional options):
  23. *
  24. * * $spec['name']: the source's name, used to specify it in wiki text.
  25. * Set automatically by DataTransclusionHandler. REQUIRED.
  26. * * $spec['keyFields']: list of fields that can be used as the key
  27. * for fetching a record. REQUIRED.
  28. * * $spec['optionNames']: names of option that can be specified in addition
  29. * to a key, to refine the output. Optional.
  30. * * $spec['fieldNames']: names of all fields present in each record.
  31. * Fields not listed here will not be available on the wiki,
  32. * even if they are returned by the data source. If not given, this defaults to
  33. * $spec['keyFields'] + array_keys( $spec['fieldInfo'] ).
  34. * * $spec['fieldInfo']: Assiciative array mapping logical field names to additional
  35. * information for using and interpreting these fields. Different data sources
  36. * may allow different hints for each field. The following hints are known per
  37. * default:
  38. * * $spec['fieldInfo'][$field]['type']: specifies the data types for the field:
  39. * 'int' for integers, 'float' or 'decimal' for decimals, or 'string' for
  40. * string fields. Serialization types 'json', 'wddx' and 'php' are also
  41. * supported. Defaults to 'string'.
  42. * * $spec['fieldInfo'][$field]['normalization']: normalization to be applied for
  43. * this field, when used as a query key. This may be a callable, or an object
  44. * that supports the function normalize(), or a regular expression for patterns
  45. * to be removed from the value.
  46. * * $spec['cacheDuration']: the number of seconds a result from this source
  47. * may be cached for. If not set, results are assumed to be cacheable
  48. * indefinitely. This setting determines the expiry time of the parser
  49. * cache entry for pages that show data from this source. If $spec['cache'],
  50. * i.e. if this DataTransclusionSource is wrapped by an instance of
  51. * CachingDataTransclusionSource, $spec['cacheDuration'] also determines
  52. * the expiry time of ObjectCache entries for records from this source.
  53. * * $spec['sourceInfo']: associative array of information about the data source
  54. * that should be made available on the wiki. This information will be
  55. * present in the record arrays as passed to the template.
  56. * This is intended to allow information about source, license, etc to be
  57. * shown on the wiki. Note that DataTransclusionSource implementations may
  58. * provide extra information in the source info on their own: This base
  59. * class forces $spec['sourceInfo']['source-name'] = $spec['name'].
  60. * * $spec['transformer']: a record transformer specification. This may be an
  61. * instance of RecordTransformer, or an associative array specifying a
  62. * record transformer which can then be created using
  63. * RecordTransformer::newRecordTransformer. In that case,
  64. * $spec['transformer']['class'] must be the class name of the desired
  65. * RecordTransformer implementation. Other entries in that array are
  66. * specific to the individual transformers.
  67. *
  68. * Options used by DataTransclusionHandler but ignored by DataTransclusionSource:
  69. * * $spec['class']: see documentation if $wgDataTransclusionSources in DataTransclusion.
  70. * * $spec['cache']: see documentation if $wgDataTransclusionSources in DataTransclusion.
  71. *
  72. * Lists may be given as arrays or strings with items separated by [,;|].
  73. */
  74. abstract class DataTransclusionSource {
  75. static function splitList( $s, $chars = ',;|' ) {
  76. if ( $s === null || $s === false ) {
  77. return $s;
  78. }
  79. if ( !is_string( $s ) ) {
  80. return $s;
  81. }
  82. $list = preg_split( '!\s*[' . $chars . ']\s*!', $s );
  83. return $list;
  84. }
  85. /**
  86. * Initializes the DataTransclusionSource from the given parameter array.
  87. * @param $spec associative array of options. See class-level documentation for details.
  88. */
  89. function __construct( $spec ) {
  90. $this->name = $spec[ 'name' ];
  91. wfDebugLog( 'DataTransclusion', "constructing " . get_class( $this ) . " \"{$this->name}\"\n" );
  92. $this->keyFields = self::splitList( $spec[ 'keyFields' ] );
  93. $this->optionNames = self::splitList( @$spec[ 'optionNames' ] );
  94. if ( isset( $spec[ 'fieldInfo' ] ) ) {
  95. $this->fieldInfo = $spec[ 'fieldInfo' ];
  96. } else {
  97. $this->fieldInfo = null;
  98. }
  99. if ( isset( $spec[ 'fieldNames' ] ) ) {
  100. $this->fieldNames = self::splitList( $spec[ 'fieldNames' ] );
  101. } elseif ( isset( $spec[ 'fieldInfo' ] ) ) {
  102. $this->fieldNames = array_keys( $spec[ 'fieldInfo' ] );
  103. } else {
  104. $this->fieldNames = $this->keyFields;
  105. if ( !empty( $this->fieldInfo ) ) {
  106. $this->fieldNames = array_merge( $this->fieldNames, array_keys( $this->fieldInfo ) );
  107. }
  108. $this->fieldNames = array_unique( $this->fieldNames );
  109. }
  110. if ( !empty( $spec[ 'cacheDuration' ] ) ) {
  111. $this->cacheDuration = (int)$spec[ 'cacheDuration' ];
  112. } else {
  113. $this->cacheDuration = null;
  114. }
  115. if ( !empty( $spec[ 'transformer' ] ) ) {
  116. if ( is_array( $spec[ 'transformer' ] ) ) {
  117. $this->transformer = RecordTransformer::newRecordTransformer( $spec[ 'transformer' ] );
  118. } else {
  119. $this->transformer = $spec[ 'transformer' ];
  120. }
  121. } else {
  122. $this->transformer = null;
  123. }
  124. $this->sourceInfo = array();
  125. if ( !empty( $spec[ 'sourceInfo' ] ) ) {
  126. foreach ( $spec[ 'sourceInfo' ] as $k => $v ) {
  127. $this->sourceInfo[ $k ] = $v;
  128. }
  129. }
  130. $this->sourceInfo[ 'source-name' ] = $this->name; // force this one
  131. }
  132. public function normalize( $key, $value, $norm = null ) {
  133. if ( $norm );
  134. elseif ( isset( $this->fieldInfo[ $key ]['normalization'] ) ) {
  135. $norm = trim( $this->fieldInfo[ $key ]['normalization'] );
  136. } else {
  137. return $value;
  138. }
  139. if ( is_object( $norm ) ) {
  140. return $norm->normalize( $value );
  141. } elseif ( is_callable( $norm ) || preg_match( '/^(\w[\w\d]*::)?(\w[\w\d]*)$/', $norm ) ) {
  142. return call_user_func( $norm, $value );
  143. } elseif ( is_array( $norm ) ) {
  144. return preg_replace( $norm[0], $norm[1], $value );
  145. } else {
  146. return preg_replace( $norm, '', $value );
  147. }
  148. }
  149. public function convert( $key, $value, $format = null ) {
  150. if ( $format );
  151. elseif ( isset( $this->fieldInfo[ $key ]['type'] ) ) {
  152. $format = strtolower( trim( $this->fieldInfo[ $key ]['type'] ) );
  153. } else {
  154. return (string)$value;
  155. }
  156. if ( $format == 'int' ) {
  157. return (int)$value;
  158. } elseif ( $format == 'decimal' || $format == 'float' ) {
  159. return (float)$value;
  160. } elseif ( $format == 'json' || $format == 'js' ) {
  161. return DataTransclusionSource::decodeJson( $value );
  162. } elseif ( $format == 'wddx' ) {
  163. return DataTransclusionSource::decodeWddx( $value );
  164. } elseif ( $format == 'xml' ) {
  165. return DataTransclusionSource::parseXml( $value ); #WARNING: returns DOM
  166. } elseif ( $format == 'php' || $format == 'pser' ) {
  167. return DataTransclusionSource::decodeSerialized( $value );
  168. } else {
  169. return (string)$value;
  170. }
  171. }
  172. public function getName() {
  173. return $this->name;
  174. }
  175. public function getSourceInfo() {
  176. return $this->sourceInfo;
  177. }
  178. public function getKeyFields() {
  179. return $this->keyFields;
  180. }
  181. public function getOptionNames() {
  182. return $this->optionNames;
  183. }
  184. public function getFieldNames() {
  185. return $this->fieldNames;
  186. }
  187. public function getCacheDuration() {
  188. return $this->cacheDuration;
  189. }
  190. public abstract function fetchRawRecord( $field, $value, $options = null );
  191. public function fetchRecord( $field, $value, $options = null ) {
  192. $value = $this->normalize( $field, $value );
  193. $value = $this->convert( $field, $value );
  194. $rec = $this->fetchRawRecord( $field, $value, $options );
  195. if ( $this->transformer ) {
  196. $rec = $this->transformer->transform( $rec );
  197. }
  198. return $rec;
  199. }
  200. public static function decodeSerialized( $raw ) {
  201. return unserialize( $raw );
  202. }
  203. public static function decodeJson( $raw ) {
  204. $raw = preg_replace( '/^\s*(var\s)?\w([\w\d]*)\s+=\s*|\s*;\s*$/sim', '', $raw);
  205. return FormatJson::decode( $raw, true );
  206. }
  207. public static function decodeWddx( $raw ) {
  208. return wddx_unserialize( $raw );
  209. }
  210. public static function parseXml( $raw ) {
  211. $dom = new DOMDocument();
  212. $dom->loadXML( $raw );
  213. #NOTE: returns a DOM, RecordTransformer must be aware!
  214. return $dom->documentElement;
  215. }
  216. }
  217. /**
  218. * Implementation of DataTransclusionSource that wraps another DataTransclusionSource and applies caching in an
  219. * ObjectCache. All methods delegate to the underlieing data source, fetchRecord adds logic for caching.
  220. */
  221. class CachingDataTransclusionSource extends DataTransclusionSource {
  222. /**
  223. * Initializes the CachingDataTransclusionSource
  224. *
  225. * @param $source a DataTransclusionSource instance for fetching data records.
  226. * @param $cache an ObjectCache instance
  227. * @param $duration number of seconds for which records may be cached
  228. */
  229. function __construct( $source, $cache, $duration ) {
  230. $this->source = $source;
  231. $this->cache = $cache;
  232. $this->duration = $duration;
  233. }
  234. public function getName() {
  235. return $this->source->getName();
  236. }
  237. public function getSourceInfo() {
  238. return $this->source->getSourceInfo();
  239. }
  240. public function getKeyFields() {
  241. return $this->source->getKeyFields();
  242. }
  243. public function getOptionNames() {
  244. return $this->source->getOptionNames();
  245. }
  246. public function getFieldNames() {
  247. return $this->source->getFieldNames();
  248. }
  249. public function getCacheDuration() {
  250. return $this->source->getCacheDuration();
  251. }
  252. public function fetchRawRecord( $field, $value, $options = null ) {
  253. throw new MWException( "not implemented" );
  254. }
  255. public function fetchRecord( $field, $value, $options = null ) {
  256. global $wgDBname, $wgUser;
  257. $k = "$field=$value";
  258. if ( $options ) {
  259. $k .= "&" . sha1( var_export( $options, false ) );
  260. }
  261. $cacheKey = "$wgDBname:DataTransclusion(" . $this->getName() . ":$k)";
  262. $rec = $this->cache->get( $cacheKey );
  263. if ( !$rec ) {
  264. wfDebugLog( 'DataTransclusion', "fetching fresh record for $field=$value\n" );
  265. $rec = $this->source->fetchRecord( $field, $value, $options );
  266. if ( $rec ) { // XXX: also cache negatives??
  267. $duration = $this->getCacheDuration();
  268. wfDebugLog( 'DataTransclusion', "caching record for $field=$value for $duration sec\n" );
  269. $this->cache->set( $cacheKey, $rec, $duration ) ;
  270. }
  271. } else {
  272. wfDebugLog( 'DataTransclusion', "using cached record for $field=$value\n" );
  273. }
  274. return $rec;
  275. }
  276. }
  277. /**
  278. * Implementations of DataTransclusionSource which simply fetches data from an array. This is
  279. * intended mainly for testing and debugging.
  280. */
  281. class FakeDataTransclusionSource extends DataTransclusionSource {
  282. /**
  283. * Initializes the CachingDataTransclusionSource
  284. *
  285. * @param $spec an associative array of options. See class-level
  286. * documentation of DataTransclusionSource for details.
  287. *
  288. * @param $data an array containing a list of records. Records from
  289. * this list can be accessed via fetchRecord() using the key fields specified
  290. * by $spec['keyFields']. If $data is not given, $spec['data'] must contain the data array.
  291. */
  292. function __construct( $spec, $data = null ) {
  293. DataTransclusionSource::__construct( $spec );
  294. if ( $data === null ) {
  295. $data = $spec[ 'data' ];
  296. }
  297. $this->lookup = array();
  298. foreach ( $data as $rec ) {
  299. $this->putRecord( $rec );
  300. }
  301. }
  302. public function putRecord( $record ) {
  303. $fields = $this->getKeyFields();
  304. foreach ( $fields as $f ) {
  305. $k = $record[ $f ];
  306. if ( !isset( $this->lookup[ $f ] ) ) {
  307. $this->lookup[ $f ] = array();
  308. }
  309. $this->lookup[ $f ][ $k ] = $record;
  310. }
  311. }
  312. public function fetchRawRecord( $field, $value, $options = null ) {
  313. return @$this->lookup[ $field ][ $value ];
  314. }
  315. }