r114389 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r114388‎ | r114389 | r114390 >
Date:18:35, 21 March 2012
Author:jeroendedauw
Status:deferred
Tags:
Comment:
some unfinished work on diff stuff and added in stuff that got reverted from core for now
Modified paths:
  • /trunk/extensions/EducationProgram/EducationProgram.php (modified) (history)
  • /trunk/extensions/EducationProgram/compat/CacheHelper.php (added) (history)
  • /trunk/extensions/EducationProgram/compat/CachedAction.php (added) (history)
  • /trunk/extensions/EducationProgram/compat/DBDataObject.php (added) (history)
  • /trunk/extensions/EducationProgram/compat/DBTable.php (added) (history)
  • /trunk/extensions/EducationProgram/compat/SpecialCachedPage.php (modified) (history)
  • /trunk/extensions/EducationProgram/includes/DBDataObject.php (deleted) (history)
  • /trunk/extensions/EducationProgram/includes/DBTable.php (deleted) (history)
  • /trunk/extensions/EducationProgram/includes/EPRevisionDiff.php (added) (history)
  • /trunk/extensions/EducationProgram/includes/EPRevisionedObject.php (modified) (history)
  • /trunk/extensions/EducationProgram/includes/EPUtils.php (modified) (history)

Diff [purge]

Index: trunk/extensions/EducationProgram/compat/DBTable.php
@@ -0,0 +1,658 @@
 2+<?php
 3+
 4+/**
 5+ * Abstract base class for representing a single database table.
 6+ *
 7+ * @since 1.20
 8+ *
 9+ * @file DBTable.php
 10+ *
 11+ * @licence GNU GPL v2 or later
 12+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
 13+ */
 14+abstract class DBTable {
 15+
 16+ /**
 17+ * Returns the name of the database table objects of this type are stored in.
 18+ *
 19+ * @since 1.20
 20+ *
 21+ * @return string
 22+ */
 23+ public abstract function getDBTable();
 24+
 25+ /**
 26+ * Returns the name of a DBDataObject deriving class that
 27+ * represents single rows in this table.
 28+ *
 29+ * @since 1.20
 30+ *
 31+ * @return string
 32+ */
 33+ public abstract function getDataObjectClass();
 34+
 35+ /**
 36+ * Gets the db field prefix.
 37+ *
 38+ * @since 1.20
 39+ *
 40+ * @return string
 41+ */
 42+ protected abstract function getFieldPrefix();
 43+
 44+ /**
 45+ * Returns an array with the fields and their types this object contains.
 46+ * This corresponds directly to the fields in the database, without prefix.
 47+ *
 48+ * field name => type
 49+ *
 50+ * Allowed types:
 51+ * * id
 52+ * * str
 53+ * * int
 54+ * * float
 55+ * * bool
 56+ * * array
 57+ *
 58+ * @since 1.20
 59+ *
 60+ * @return array
 61+ */
 62+ public abstract function getFieldTypes();
 63+
 64+ /**
 65+ * The database connection to use for read operations.
 66+ * Can be changed via @see setReadDb.
 67+ *
 68+ * @since 1.20
 69+ * @var integer DB_ enum
 70+ */
 71+ protected $readDb = DB_SLAVE;
 72+
 73+ /**
 74+ * Returns a list of default field values.
 75+ * field name => field value
 76+ *
 77+ * @since 1.20
 78+ *
 79+ * @return array
 80+ */
 81+ public function getDefaults() {
 82+ return array();
 83+ }
 84+
 85+ /**
 86+ * Returns a list of the summary fields.
 87+ * These are fields that cache computed values, such as the amount of linked objects of $type.
 88+ * This is relevant as one might not want to do actions such as log changes when these get updated.
 89+ *
 90+ * @since 1.20
 91+ *
 92+ * @return array
 93+ */
 94+ public function getSummaryFields() {
 95+ return array();
 96+ }
 97+
 98+ /**
 99+ * Selects the the specified fields of the records matching the provided
 100+ * conditions and returns them as DBDataObject. Field names get prefixed.
 101+ *
 102+ * @since 1.20
 103+ *
 104+ * @param array|string|null $fields
 105+ * @param array $conditions
 106+ * @param array $options
 107+ * @param string|null $functionName
 108+ *
 109+ * @return array of self
 110+ */
 111+ public function select( $fields = null, array $conditions = array(), array $options = array(), $functionName = null ) {
 112+ $result = $this->selectFields( $fields, $conditions, $options, false, $functionName );
 113+
 114+ $objects = array();
 115+
 116+ foreach ( $result as $record ) {
 117+ $objects[] = $this->newFromArray( $record );
 118+ }
 119+
 120+ return $objects;
 121+ }
 122+
 123+ /**
 124+ * Selects the the specified fields of the records matching the provided
 125+ * conditions and returns them as associative arrays.
 126+ * Provided field names get prefixed.
 127+ * Returned field names will not have a prefix.
 128+ *
 129+ * When $collapse is true:
 130+ * If one field is selected, each item in the result array will be this field.
 131+ * If two fields are selected, each item in the result array will have as key
 132+ * the first field and as value the second field.
 133+ * If more then two fields are selected, each item will be an associative array.
 134+ *
 135+ * @since 1.20
 136+ *
 137+ * @param array|string|null $fields
 138+ * @param array $conditions
 139+ * @param array $options
 140+ * @param boolean $collapse Set to false to always return each result row as associative array.
 141+ * @param string|null $functionName
 142+ *
 143+ * @return array of array
 144+ */
 145+ public function selectFields( $fields = null, array $conditions = array(), array $options = array(), $collapse = true, $functionName = null ) {
 146+ if ( is_null( $fields ) ) {
 147+ $fields = array_keys( $this->getFieldTypes() );
 148+ }
 149+ else {
 150+ $fields = (array)$fields;
 151+ }
 152+
 153+ $dbr = wfGetDB( $this->getReadDb() );
 154+ $result = $dbr->select(
 155+ $this->getDBTable(),
 156+ $this->getPrefixedFields( $fields ),
 157+ $this->getPrefixedValues( $conditions ),
 158+ is_null( $functionName ) ? __METHOD__ : $functionName,
 159+ $options
 160+ );
 161+
 162+ $objects = array();
 163+
 164+ foreach ( $result as $record ) {
 165+ $objects[] = $this->getFieldsFromDBResult( $record );
 166+ }
 167+
 168+ if ( $collapse ) {
 169+ if ( count( $fields ) === 1 ) {
 170+ $objects = array_map( 'array_shift', $objects );
 171+ }
 172+ elseif ( count( $fields ) === 2 ) {
 173+ $o = array();
 174+
 175+ foreach ( $objects as $object ) {
 176+ $o[array_shift( $object )] = array_shift( $object );
 177+ }
 178+
 179+ $objects = $o;
 180+ }
 181+ }
 182+
 183+ return $objects;
 184+ }
 185+
 186+ /**
 187+ * Selects the the specified fields of the first matching record.
 188+ * Field names get prefixed.
 189+ *
 190+ * @since 1.20
 191+ *
 192+ * @param array|string|null $fields
 193+ * @param array $conditions
 194+ * @param array $options
 195+ * @param string|null $functionName
 196+ *
 197+ * @return DBObject|bool False on failure
 198+ */
 199+ public function selectRow( $fields = null, array $conditions = array(), array $options = array(), $functionName = null ) {
 200+ $options['LIMIT'] = 1;
 201+
 202+ $objects = $this->select( $fields, $conditions, $options, $functionName );
 203+
 204+ return empty( $objects ) ? false : $objects[0];
 205+ }
 206+
 207+ /**
 208+ * Selects the the specified fields of the records matching the provided
 209+ * conditions. Field names do NOT get prefixed.
 210+ *
 211+ * @since 1.20
 212+ *
 213+ * @param array $fields
 214+ * @param array $conditions
 215+ * @param array $options
 216+ * @param string|null $functionName
 217+ *
 218+ * @return ResultWrapper
 219+ */
 220+ public function rawSelectRow( array $fields, array $conditions = array(), array $options = array(), $functionName = null ) {
 221+ $dbr = wfGetDB( $this->getReadDb() );
 222+
 223+ return $dbr->selectRow(
 224+ $this->getDBTable(),
 225+ $fields,
 226+ $conditions,
 227+ is_null( $functionName ) ? __METHOD__ : $functionName,
 228+ $options
 229+ );
 230+ }
 231+
 232+ /**
 233+ * Selects the the specified fields of the first record matching the provided
 234+ * conditions and returns it as an associative array, or false when nothing matches.
 235+ * This method makes use of selectFields and expects the same parameters and
 236+ * returns the same results (if there are any, if there are none, this method returns false).
 237+ * @see DBDataObject::selectFields
 238+ *
 239+ * @since 1.20
 240+ *
 241+ * @param array|string|null $fields
 242+ * @param array $conditions
 243+ * @param array $options
 244+ * @param boolean $collapse Set to false to always return each result row as associative array.
 245+ * @param string|null $functionName
 246+ *
 247+ * @return mixed|array|bool False on failure
 248+ */
 249+ public function selectFieldsRow( $fields = null, array $conditions = array(), array $options = array(), $collapse = true, $functionName = null ) {
 250+ $options['LIMIT'] = 1;
 251+
 252+ $objects = $this->selectFields( $fields, $conditions, $options, $collapse, $functionName );
 253+
 254+ return empty( $objects ) ? false : $objects[0];
 255+ }
 256+
 257+ /**
 258+ * Returns if there is at least one record matching the provided conditions.
 259+ * Condition field names get prefixed.
 260+ *
 261+ * @since 1.20
 262+ *
 263+ * @param array $conditions
 264+ *
 265+ * @return boolean
 266+ */
 267+ public function has( array $conditions = array() ) {
 268+ return $this->selectRow( array( 'id' ), $conditions ) !== false;
 269+ }
 270+
 271+ /**
 272+ * Returns the amount of matching records.
 273+ * Condition field names get prefixed.
 274+ *
 275+ * Note that this can be expensive on large tables.
 276+ * In such cases you might want to use DatabaseBase::estimateRowCount instead.
 277+ *
 278+ * @since 1.20
 279+ *
 280+ * @param array $conditions
 281+ * @param array $options
 282+ *
 283+ * @return integer
 284+ */
 285+ public function count( array $conditions = array(), array $options = array() ) {
 286+ $res = $this->rawSelectRow(
 287+ array( 'COUNT(*) AS rowcount' ),
 288+ $this->getPrefixedValues( $conditions ),
 289+ $options
 290+ );
 291+
 292+ return $res->rowcount;
 293+ }
 294+
 295+ /**
 296+ * Removes the object from the database.
 297+ *
 298+ * @since 1.20
 299+ *
 300+ * @param array $conditions
 301+ * @param string|null $functionName
 302+ *
 303+ * @return boolean Success indicator
 304+ */
 305+ public function delete( array $conditions, $functionName = null ) {
 306+ return wfGetDB( DB_MASTER )->delete(
 307+ $this->getDBTable(),
 308+ $this->getPrefixedValues( $conditions ),
 309+ $functionName
 310+ );
 311+ }
 312+
 313+ /**
 314+ * Get API parameters for the fields supported by this object.
 315+ *
 316+ * @since 1.20
 317+ *
 318+ * @param boolean $requireParams
 319+ * @param boolean $setDefaults
 320+ *
 321+ * @return array
 322+ */
 323+ public function getAPIParams( $requireParams = false, $setDefaults = false ) {
 324+ $typeMap = array(
 325+ 'id' => 'integer',
 326+ 'int' => 'integer',
 327+ 'float' => 'NULL',
 328+ 'str' => 'string',
 329+ 'bool' => 'integer',
 330+ 'array' => 'string',
 331+ 'blob' => 'string',
 332+ );
 333+
 334+ $params = array();
 335+ $defaults = $this->getDefaults();
 336+
 337+ foreach ( $this->getFieldTypes() as $field => $type ) {
 338+ if ( $field == 'id' ) {
 339+ continue;
 340+ }
 341+
 342+ $hasDefault = array_key_exists( $field, $defaults );
 343+
 344+ $params[$field] = array(
 345+ ApiBase::PARAM_TYPE => $typeMap[$type],
 346+ ApiBase::PARAM_REQUIRED => $requireParams && !$hasDefault
 347+ );
 348+
 349+ if ( $type == 'array' ) {
 350+ $params[$field][ApiBase::PARAM_ISMULTI] = true;
 351+ }
 352+
 353+ if ( $setDefaults && $hasDefault ) {
 354+ $default = is_array( $defaults[$field] ) ? implode( '|', $defaults[$field] ) : $defaults[$field];
 355+ $params[$field][ApiBase::PARAM_DFLT] = $default;
 356+ }
 357+ }
 358+
 359+ return $params;
 360+ }
 361+
 362+ /**
 363+ * Returns an array with the fields and their descriptions.
 364+ *
 365+ * field name => field description
 366+ *
 367+ * @since 1.20
 368+ *
 369+ * @return array
 370+ */
 371+ public function getFieldDescriptions() {
 372+ return array();
 373+ }
 374+
 375+ /**
 376+ * Get the database type used for read operations.
 377+ *
 378+ * @since 1.20
 379+ *
 380+ * @return integer DB_ enum
 381+ */
 382+ public function getReadDb() {
 383+ return $this->readDb;
 384+ }
 385+
 386+ /**
 387+ * Set the database type to use for read operations.
 388+ *
 389+ * @param integer $db
 390+ *
 391+ * @since 1.20
 392+ */
 393+ public function setReadDb( $db ) {
 394+ $this->readDb = $db;
 395+ }
 396+
 397+ /**
 398+ * Update the records matching the provided conditions by
 399+ * setting the fields that are keys in the $values param to
 400+ * their corresponding values.
 401+ *
 402+ * @since 1.20
 403+ *
 404+ * @param array $values
 405+ * @param array $conditions
 406+ *
 407+ * @return boolean Success indicator
 408+ */
 409+ public function update( array $values, array $conditions = array() ) {
 410+ $dbw = wfGetDB( DB_MASTER );
 411+
 412+ return $dbw->update(
 413+ $this->getDBTable(),
 414+ $this->getPrefixedValues( $values ),
 415+ $this->getPrefixedValues( $conditions ),
 416+ __METHOD__
 417+ );
 418+ }
 419+
 420+ /**
 421+ * Computes the values of the summary fields of the objects matching the provided conditions.
 422+ *
 423+ * @since 1.20
 424+ *
 425+ * @param array|string|null $summaryFields
 426+ * @param array $conditions
 427+ */
 428+ public function updateSummaryFields( $summaryFields = null, array $conditions = array() ) {
 429+ $this->setReadDb( DB_MASTER );
 430+
 431+ foreach ( $this->select( null, $conditions ) as /* DBDataObject */ $item ) {
 432+ $item->loadSummaryFields( $summaryFields );
 433+ $item->setSummaryMode( true );
 434+ $item->save();
 435+ }
 436+
 437+ $this->setReadDb( DB_SLAVE );
 438+ }
 439+
 440+ /**
 441+ * Takes in an associative array with field names as keys and
 442+ * their values as value. The field names are prefixed with the
 443+ * db field prefix.
 444+ *
 445+ * Field names can also be provided as an array with as first element a table name, such as
 446+ * $conditions = array(
 447+ * array( array( 'tablename', 'fieldname' ), $value ),
 448+ * );
 449+ *
 450+ * @since 1.20
 451+ *
 452+ * @param array $values
 453+ *
 454+ * @return array
 455+ */
 456+ public function getPrefixedValues( array $values ) {
 457+ $prefixedValues = array();
 458+
 459+ foreach ( $values as $field => $value ) {
 460+ if ( is_integer( $field ) ) {
 461+ if ( is_array( $value ) ) {
 462+ $field = $value[0];
 463+ $value = $value[1];
 464+ }
 465+ else {
 466+ $value = explode( ' ', $value, 2 );
 467+ $value[0] = $this->getPrefixedField( $value[0] );
 468+ $prefixedValues[] = implode( ' ', $value );
 469+ continue;
 470+ }
 471+ }
 472+
 473+ $prefixedValues[$this->getPrefixedField( $field )] = $value;
 474+ }
 475+
 476+ return $prefixedValues;
 477+ }
 478+
 479+ /**
 480+ * Takes in a field or array of fields and returns an
 481+ * array with their prefixed versions, ready for db usage.
 482+ *
 483+ * @since 1.20
 484+ *
 485+ * @param array|string $fields
 486+ *
 487+ * @return array
 488+ */
 489+ public function getPrefixedFields( array $fields ) {
 490+ foreach ( $fields as &$field ) {
 491+ $field = $this->getPrefixedField( $field );
 492+ }
 493+
 494+ return $fields;
 495+ }
 496+
 497+ /**
 498+ * Takes in a field and returns an it's prefixed version, ready for db usage.
 499+ *
 500+ * @since 1.20
 501+ *
 502+ * @param string|array $field
 503+ *
 504+ * @return string
 505+ */
 506+ public function getPrefixedField( $field ) {
 507+ return $this->getFieldPrefix() . $field;
 508+ }
 509+
 510+ /**
 511+ * Takes an array of field names with prefix and returns the unprefixed equivalent.
 512+ *
 513+ * @since 1.20
 514+ *
 515+ * @param array $fieldNames
 516+ *
 517+ * @return array
 518+ */
 519+ public function unprefixFieldNames( array $fieldNames ) {
 520+ return array_map( array( $this, 'unprefixFieldName' ), $fieldNames );
 521+ }
 522+
 523+ /**
 524+ * Takes a field name with prefix and returns the unprefixed equivalent.
 525+ *
 526+ * @since 1.20
 527+ *
 528+ * @param string $fieldName
 529+ *
 530+ * @return string
 531+ */
 532+ public function unprefixFieldName( $fieldName ) {
 533+ return substr( $fieldName, strlen( $this->getFieldPrefix() ) );
 534+ }
 535+
 536+ public function __construct() {
 537+
 538+ }
 539+
 540+ /**
 541+ * Get an instance of this class.
 542+ *
 543+ * @since 1.20
 544+ *
 545+ * @return DBtable
 546+ */
 547+ public static function singleton() {
 548+ static $instance;
 549+
 550+ if ( !isset( $instance ) ) {
 551+ $class = function_exists( 'get_called_class' ) ? get_called_class() : self::get_called_class();
 552+ $instance = new $class;
 553+ }
 554+
 555+ return $instance;
 556+ }
 557+
 558+ /**
 559+ * Compatibility fallback function so the singleton method works on PHP < 5.3.
 560+ * Code borrowed from http://www.php.net/manual/en/function.get-called-class.php#107445
 561+ *
 562+ * @since 1.20
 563+ *
 564+ * @return string
 565+ */
 566+ protected static function get_called_class() {
 567+ $bt = debug_backtrace();
 568+ $l = count($bt) - 1;
 569+ $matches = array();
 570+ while(empty($matches) && $l > -1){
 571+ $lines = file($bt[$l]['file']);
 572+ $callerLine = $lines[$bt[$l]['line']-1];
 573+ preg_match('/([a-zA-Z0-9\_]+)::'.$bt[$l--]['function'].'/',
 574+ $callerLine,
 575+ $matches);
 576+ }
 577+ if (!isset($matches[1])) $matches[1]=NULL; //for notices
 578+ if ($matches[1] == 'self') {
 579+ $line = $bt[$l]['line']-1;
 580+ while ($line > 0 && strpos($lines[$line], 'class') === false) {
 581+ $line--;
 582+ }
 583+ preg_match('/class[\s]+(.+?)[\s]+/si', $lines[$line], $matches);
 584+ }
 585+ return $matches[1];
 586+ }
 587+
 588+ /**
 589+ * Get an array with fields from a database result,
 590+ * that can be fed directly to the constructor or
 591+ * to setFields.
 592+ *
 593+ * @since 1.20
 594+ *
 595+ * @param stdClass $result
 596+ *
 597+ * @return array
 598+ */
 599+ public function getFieldsFromDBResult( stdClass $result ) {
 600+ $result = (array)$result;
 601+ return array_combine(
 602+ $this->unprefixFieldNames( array_keys( $result ) ),
 603+ array_values( $result )
 604+ );
 605+ }
 606+
 607+ /**
 608+ * Get a new instance of the class from a database result.
 609+ *
 610+ * @since 1.20
 611+ *
 612+ * @param stdClass $result
 613+ *
 614+ * @return DBDataObject
 615+ */
 616+ public function newFromDBResult( stdClass $result ) {
 617+ return $this->newFromArray( $this->getFieldsFromDBResult( $result ) );
 618+ }
 619+
 620+ /**
 621+ * Get a new instance of the class from an array.
 622+ *
 623+ * @since 1.20
 624+ *
 625+ * @param array $data
 626+ * @param boolean $loadDefaults
 627+ *
 628+ * @return DBDataObject
 629+ */
 630+ public function newFromArray( array $data, $loadDefaults = false ) {
 631+ $class = $this->getDataObjectClass();
 632+ return new $class( $this, $data, $loadDefaults );
 633+ }
 634+
 635+ /**
 636+ * Return the names of the fields.
 637+ *
 638+ * @since 1.20
 639+ *
 640+ * @return array
 641+ */
 642+ public function getFieldNames() {
 643+ return array_keys( $this->getFieldTypes() );
 644+ }
 645+
 646+ /**
 647+ * Gets if the object can take a certain field.
 648+ *
 649+ * @since 1.20
 650+ *
 651+ * @param string $name
 652+ *
 653+ * @return boolean
 654+ */
 655+ public function canHaveField( $name ) {
 656+ return array_key_exists( $name, $this->getFieldTypes() );
 657+ }
 658+
 659+}
Property changes on: trunk/extensions/EducationProgram/compat/DBTable.php
___________________________________________________________________
Added: svn:mergeinfo
1660 Merged /branches/new-installer/phase3/includes/DBTable.php:r43664-66004
2661 Merged /branches/wmf-deployment/includes/DBTable.php:r53381
3662 Merged /branches/JSTesting/includes/DBTable.php:r100352-107913
4663 Merged /branches/REL1_15/phase3/includes/DBTable.php:r51646
5664 Merged /branches/wmf/1.18wmf1/includes/DBTable.php:r97508
6665 Merged /branches/sqlite/includes/DBTable.php:r58211-58321
Added: svn:eol-style
7666 + native
Index: trunk/extensions/EducationProgram/compat/CacheHelper.php
@@ -0,0 +1,339 @@
 2+<?php
 3+
 4+/**
 5+ * Interface for all classes implementing CacheHelper functionality.
 6+ *
 7+ * @since 1.20
 8+ *
 9+ * @licence GNU GPL v2 or later
 10+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
 11+ */
 12+interface ICacheHelper {
 13+
 14+ /**
 15+ * Sets if the cache should be enabled or not.
 16+ *
 17+ * @since 1.20
 18+ * @param boolean $cacheEnabled
 19+ */
 20+ function setCacheEnabled( $cacheEnabled );
 21+
 22+ /**
 23+ * Initializes the caching.
 24+ * Should be called before the first time anything is added via addCachedHTML.
 25+ *
 26+ * @since 1.20
 27+ *
 28+ * @param integer|null $cacheExpiry Sets the cache expirty, either ttl in seconds or unix timestamp.
 29+ * @param boolean|null $cacheEnabled Sets if the cache should be enabled or not.
 30+ */
 31+ function startCache( $cacheExpiry = null, $cacheEnabled = null );
 32+
 33+ /**
 34+ * Get a cached value if available or compute it if not and then cache it if possible.
 35+ * The provided $computeFunction is only called when the computation needs to happen
 36+ * and should return a result value. $args are arguments that will be passed to the
 37+ * compute function when called.
 38+ *
 39+ * @since 1.20
 40+ *
 41+ * @param {function} $computeFunction
 42+ * @param array|mixed $args
 43+ * @param string|null $key
 44+ *
 45+ * @return mixed
 46+ */
 47+ function getCachedValue( $computeFunction, $args = array(), $key = null );
 48+
 49+ /**
 50+ * Saves the HTML to the cache in case it got recomputed.
 51+ * Should be called after the last time anything is added via addCachedHTML.
 52+ *
 53+ * @since 1.20
 54+ */
 55+ function saveCache();
 56+
 57+ /**
 58+ * Sets the time to live for the cache, in seconds or a unix timestamp indicating the point of expiry..
 59+ *
 60+ * @since 1.20
 61+ *
 62+ * @param integer $cacheExpiry
 63+ */
 64+ function setExpirey( $cacheExpiry );
 65+
 66+}
 67+
 68+/**
 69+ * Helper class for caching various elements in a single cache entry.
 70+ *
 71+ * To get a cached value or compute it, use getCachedValue like this:
 72+ * $this->getCachedValue( $callback );
 73+ *
 74+ * To add HTML that should be cached, use addCachedHTML like this:
 75+ * $this->addCachedHTML( $callback );
 76+ *
 77+ * The callback function is only called when needed, so do all your expensive
 78+ * computations here. This function should returns the HTML to be cached.
 79+ * It should not add anything to the PageOutput object!
 80+ *
 81+ * Before the first addCachedHTML call, you should call $this->startCache();
 82+ * After adding the last HTML that should be cached, call $this->saveCache();
 83+ *
 84+ * @since 1.20
 85+ *
 86+ * @file CacheHelper.php
 87+ *
 88+ * @licence GNU GPL v2 or later
 89+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
 90+ */
 91+class CacheHelper implements ICacheHelper {
 92+
 93+ /**
 94+ * The time to live for the cache, in seconds or a unix timestamp indicating the point of expiry.
 95+ *
 96+ * @since 1.20
 97+ * @var integer
 98+ */
 99+ protected $cacheExpiry = 3600;
 100+
 101+ /**
 102+ * List of HTML chunks to be cached (if !hasCached) or that where cashed (of hasCached).
 103+ * If no cached already, then the newly computed chunks are added here,
 104+ * if it as cached already, chunks are removed from this list as they are needed.
 105+ *
 106+ * @since 1.20
 107+ * @var array
 108+ */
 109+ protected $cachedChunks;
 110+
 111+ /**
 112+ * Indicates if the to be cached content was already cached.
 113+ * Null if this information is not available yet.
 114+ *
 115+ * @since 1.20
 116+ * @var boolean|null
 117+ */
 118+ protected $hasCached = null;
 119+
 120+ /**
 121+ * If the cache is enabled or not.
 122+ *
 123+ * @since 1.20
 124+ * @var boolean
 125+ */
 126+ protected $cacheEnabled = true;
 127+
 128+ /**
 129+ * Function that gets called when initialization is done.
 130+ *
 131+ * @since 1.20
 132+ * @var function
 133+ */
 134+ protected $onInitHandler = false;
 135+
 136+ /**
 137+ * Sets if the cache should be enabled or not.
 138+ *
 139+ * @since 1.20
 140+ * @param boolean $cacheEnabled
 141+ */
 142+ public function setCacheEnabled( $cacheEnabled ) {
 143+ $this->cacheEnabled = $cacheEnabled;
 144+ }
 145+
 146+ /**
 147+ * Initializes the caching.
 148+ * Should be called before the first time anything is added via addCachedHTML.
 149+ *
 150+ * @since 1.20
 151+ *
 152+ * @param integer|null $cacheExpiry Sets the cache expirty, either ttl in seconds or unix timestamp.
 153+ * @param boolean|null $cacheEnabled Sets if the cache should be enabled or not.
 154+ */
 155+ public function startCache( $cacheExpiry = null, $cacheEnabled = null ) {
 156+ if ( is_null( $this->hasCached ) ) {
 157+ if ( !is_null( $cacheExpiry ) ) {
 158+ $this->cacheExpiry = $cacheExpiry;
 159+ }
 160+
 161+ if ( !is_null( $cacheEnabled ) ) {
 162+ $this->setCacheEnabled( $cacheEnabled );
 163+ }
 164+
 165+ $this->initCaching();
 166+ }
 167+ }
 168+
 169+ /**
 170+ * Returns a message that notifies the user he/she is looking at
 171+ * a cached version of the page, including a refresh link.
 172+ *
 173+ * @since 1.20
 174+ *
 175+ * @param IContextSource $context
 176+ *
 177+ * @return string
 178+ */
 179+ public function getCachedNotice( IContextSource $context ) {
 180+ $refreshArgs = $context->getRequest()->getQueryValues();
 181+ unset( $refreshArgs['title'] );
 182+ $refreshArgs['action'] = 'purge';
 183+
 184+ $subPage = $context->getTitle()->getFullText();
 185+ $subPage = explode( '/', $subPage, 2 );
 186+ $subPage = count( $subPage ) > 1 ? $subPage[1] : false;
 187+
 188+ $refreshLink = Linker::link(
 189+ $context->getTitle( $subPage ),
 190+ $context->msg( 'cachedspecial-refresh-now' )->escaped(),
 191+ array(),
 192+ $refreshArgs
 193+ );
 194+
 195+ if ( $this->cacheExpiry < 86400 * 3650 ) {
 196+ $message = $context->msg(
 197+ 'cachedspecial-viewing-cached-ttl',
 198+ $context->getLanguage()->formatDuration( $this->cacheExpiry )
 199+ )->escaped();
 200+ }
 201+ else {
 202+ $message = $context->msg(
 203+ 'cachedspecial-viewing-cached-ts'
 204+ )->escaped();
 205+ }
 206+
 207+ return $message . ' ' . $refreshLink;
 208+ }
 209+
 210+ /**
 211+ * Initializes the caching if not already done so.
 212+ * Should be called before any of the caching functionality is used.
 213+ *
 214+ * @since 1.20
 215+ */
 216+ protected function initCaching() {
 217+ if ( $this->cacheEnabled && is_null( $this->hasCached ) ) {
 218+ $cachedChunks = wfGetCache( CACHE_ANYTHING )->get( $this->getCacheKeyString() );
 219+
 220+ $this->hasCached = is_array( $cachedChunks );
 221+ $this->cachedChunks = $this->hasCached ? $cachedChunks : array();
 222+
 223+ if ( $this->onInitHandler !== false ) {
 224+ call_user_func( $this->onInitHandler, $this->hasCached );
 225+ }
 226+ }
 227+ }
 228+
 229+ /**
 230+ * Get a cached value if available or compute it if not and then cache it if possible.
 231+ * The provided $computeFunction is only called when the computation needs to happen
 232+ * and should return a result value. $args are arguments that will be passed to the
 233+ * compute function when called.
 234+ *
 235+ * @since 1.20
 236+ *
 237+ * @param {function} $computeFunction
 238+ * @param array|mixed $args
 239+ * @param string|null $key
 240+ *
 241+ * @return mixed
 242+ */
 243+ public function getCachedValue( $computeFunction, $args = array(), $key = null ) {
 244+ $this->initCaching();
 245+
 246+ if ( $this->cacheEnabled && $this->hasCached ) {
 247+ $value = null;
 248+
 249+ if ( is_null( $key ) ) {
 250+ $itemKey = array_keys( array_slice( $this->cachedChunks, 0, 1 ) );
 251+ $itemKey = array_shift( $itemKey );
 252+
 253+ if ( !is_integer( $itemKey ) ) {
 254+ wfWarn( "Attempted to get item with non-numeric key while the next item in the queue has a key ($itemKey) in " . __METHOD__ );
 255+ }
 256+ elseif ( is_null( $itemKey ) ) {
 257+ wfWarn( "Attempted to get an item while the queue is empty in " . __METHOD__ );
 258+ }
 259+ else {
 260+ $value = array_shift( $this->cachedChunks );
 261+ }
 262+ }
 263+ else {
 264+ if ( array_key_exists( $key, $this->cachedChunks ) ) {
 265+ $value = $this->cachedChunks[$key];
 266+ unset( $this->cachedChunks[$key] );
 267+ }
 268+ else {
 269+ wfWarn( "There is no item with key '$key' in this->cachedChunks in " . __METHOD__ );
 270+ }
 271+ }
 272+ }
 273+ else {
 274+ if ( !is_array( $args ) ) {
 275+ $args = array( $args );
 276+ }
 277+
 278+ $value = call_user_func_array( $computeFunction, $args );
 279+
 280+ if ( $this->cacheEnabled ) {
 281+ if ( is_null( $key ) ) {
 282+ $this->cachedChunks[] = $value;
 283+ }
 284+ else {
 285+ $this->cachedChunks[$key] = $value;
 286+ }
 287+ }
 288+ }
 289+
 290+ return $value;
 291+ }
 292+
 293+ /**
 294+ * Saves the HTML to the cache in case it got recomputed.
 295+ * Should be called after the last time anything is added via addCachedHTML.
 296+ *
 297+ * @since 1.20
 298+ */
 299+ public function saveCache() {
 300+ if ( $this->cacheEnabled && $this->hasCached === false && !empty( $this->cachedChunks ) ) {
 301+ wfGetCache( CACHE_ANYTHING )->set( $this->getCacheKeyString(), $this->cachedChunks, $this->cacheExpiry );
 302+ }
 303+ }
 304+
 305+ /**
 306+ * Sets the time to live for the cache, in seconds or a unix timestamp indicating the point of expiry..
 307+ *
 308+ * @since 1.20
 309+ *
 310+ * @param integer $cacheExpiry
 311+ */
 312+ public function setExpirey( $cacheExpiry ) {
 313+ $this->cacheExpiry = $cacheExpiry;
 314+ }
 315+
 316+ /**
 317+ * Returns the cache key to use to cache this page's HTML output.
 318+ * Is constructed from the special page name and language code.
 319+ *
 320+ * @since 1.20
 321+ *
 322+ * @return string
 323+ */
 324+ protected function getCacheKeyString() {
 325+ return call_user_func_array( 'wfMemcKey', $this->cacheKey );
 326+ }
 327+
 328+ public function setCacheKey( array $cacheKey ) {
 329+ $this->cacheKey = $cacheKey;
 330+ }
 331+
 332+ public function purge() {
 333+ $this->hasCached = false;
 334+ }
 335+
 336+ public function setOnInitializedHandler( $handlerFunction ) {
 337+ $this->onInitHandler = $handlerFunction;
 338+ }
 339+
 340+}
Index: trunk/extensions/EducationProgram/compat/CachedAction.php
@@ -0,0 +1,170 @@
 2+<?php
 3+
 4+/**
 5+ * Abstract action class with scaffolding for caching HTML and other values
 6+ * in a single blob.
 7+ *
 8+ * Before using any of the cahing functionality, call startCache.
 9+ * After the last call to either getCachedValue or addCachedHTML, call saveCache.
 10+ *
 11+ * To get a cached value or compute it, use getCachedValue like this:
 12+ * $this->getCachedValue( $callback );
 13+ *
 14+ * To add HTML that should be cached, use addCachedHTML like this:
 15+ * $this->addCachedHTML( $callback );
 16+ *
 17+ * The callback function is only called when needed, so do all your expensive
 18+ * computations here. This function should returns the HTML to be cached.
 19+ * It should not add anything to the PageOutput object!
 20+ *
 21+ * @since 1.20
 22+ *
 23+ * @file CachedAction.php
 24+ * @ingroup Action
 25+ *
 26+ * @licence GNU GPL v2 or later
 27+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
 28+ */
 29+abstract class CachedAction extends FormlessAction implements ICacheHelper {
 30+
 31+ /**
 32+ * CacheHelper object to which we foreward the non-SpecialPage specific caching work.
 33+ * Initialized in startCache.
 34+ *
 35+ * @since 1.20
 36+ * @var CacheHelper
 37+ */
 38+ protected $cacheHelper;
 39+
 40+ /**
 41+ * If the cache is enabled or not.
 42+ *
 43+ * @since 1.20
 44+ * @var boolean
 45+ */
 46+ protected $cacheEnabled = true;
 47+
 48+ /**
 49+ * Sets if the cache should be enabled or not.
 50+ *
 51+ * @since 1.20
 52+ * @param boolean $cacheEnabled
 53+ */
 54+ public function setCacheEnabled( $cacheEnabled ) {
 55+ $this->cacheHelper->setCacheEnabled( $cacheEnabled );
 56+ }
 57+
 58+ /**
 59+ * Initializes the caching.
 60+ * Should be called before the first time anything is added via addCachedHTML.
 61+ *
 62+ * @since 1.20
 63+ *
 64+ * @param integer|null $cacheExpiry Sets the cache expirty, either ttl in seconds or unix timestamp.
 65+ * @param boolean|null $cacheEnabled Sets if the cache should be enabled or not.
 66+ */
 67+ public function startCache( $cacheExpiry = null, $cacheEnabled = null ) {
 68+ $this->cacheHelper = new CacheHelper();
 69+
 70+ $this->cacheHelper->setCacheEnabled( $this->cacheEnabled );
 71+ $this->cacheHelper->setOnInitializedHandler( array( $this, 'onCacheInitialized' ) );
 72+
 73+ $keyArgs = $this->getCacheKey();
 74+
 75+ if ( array_key_exists( 'action', $keyArgs ) && $keyArgs['action'] === 'purge' ) {
 76+ unset( $keyArgs['action'] );
 77+ }
 78+
 79+ $this->cacheHelper->setCacheKey( $keyArgs );
 80+
 81+ if ( $this->getRequest()->getText( 'action' ) === 'purge' ) {
 82+ $this->cacheHelper->purge();
 83+ }
 84+
 85+ $this->cacheHelper->startCache( $cacheExpiry, $cacheEnabled );
 86+ }
 87+
 88+ /**
 89+ * Get a cached value if available or compute it if not and then cache it if possible.
 90+ * The provided $computeFunction is only called when the computation needs to happen
 91+ * and should return a result value. $args are arguments that will be passed to the
 92+ * compute function when called.
 93+ *
 94+ * @since 1.20
 95+ *
 96+ * @param {function} $computeFunction
 97+ * @param array|mixed $args
 98+ * @param string|null $key
 99+ *
 100+ * @return mixed
 101+ */
 102+ public function getCachedValue( $computeFunction, $args = array(), $key = null ) {
 103+ return $this->cacheHelper->getCachedValue( $computeFunction, $args, $key );
 104+ }
 105+
 106+ /**
 107+ * Add some HTML to be cached.
 108+ * This is done by providing a callback function that should
 109+ * return the HTML to be added. It will only be called if the
 110+ * item is not in the cache yet or when the cache has been invalidated.
 111+ *
 112+ * @since 1.20
 113+ *
 114+ * @param {function} $computeFunction
 115+ * @param array $args
 116+ * @param string|null $key
 117+ */
 118+ public function addCachedHTML( $computeFunction, $args = array(), $key = null ) {
 119+ $this->getOutput()->addHTML( $this->cacheHelper->getCachedValue( $computeFunction, $args, $key ) );
 120+ }
 121+
 122+ /**
 123+ * Saves the HTML to the cache in case it got recomputed.
 124+ * Should be called after the last time anything is added via addCachedHTML.
 125+ *
 126+ * @since 1.20
 127+ */
 128+ public function saveCache() {
 129+ $this->cacheHelper->saveCache();
 130+ }
 131+
 132+ /**
 133+ * Sets the time to live for the cache, in seconds or a unix timestamp indicating the point of expiry..
 134+ *
 135+ * @since 1.20
 136+ *
 137+ * @param integer $cacheExpiry
 138+ */
 139+ public function setExpirey( $cacheExpiry ) {
 140+ $this->cacheHelper->setExpirey( $cacheExpiry );
 141+ }
 142+
 143+ /**
 144+ * Returns the variables used to constructed the cache key in an array.
 145+ *
 146+ * @since 1.20
 147+ *
 148+ * @return array
 149+ */
 150+ protected function getCacheKey() {
 151+ return array(
 152+ get_class( $this->page ),
 153+ $this->getName(),
 154+ $this->getLanguage()->getCode()
 155+ );
 156+ }
 157+
 158+ /**
 159+ * Gets called after the cache got initialized.
 160+ *
 161+ * @since 1.20
 162+ *
 163+ * @param boolean $hasCached
 164+ */
 165+ public function onCacheInitialized( $hasCached ) {
 166+ if ( $hasCached ) {
 167+ $this->getOutput()->setSubtitle( $this->cacheHelper->getCachedNotice( $this->getContext() ) );
 168+ }
 169+ }
 170+
 171+}
\ No newline at end of file
Index: trunk/extensions/EducationProgram/compat/SpecialCachedPage.php
@@ -1,255 +1,169 @@
22 <?php
33
44 /**
5 - * Abstract special page class with scaffolding for caching the HTML output.
6 - * This copy is kept for compatibility with MW < 1.20.
7 - * As of 1.20, this class can be found at includes/specials/SpecialCachedPage.php
 5+ * Abstract special page class with scaffolding for caching HTML and other values
 6+ * in a single blob.
87 *
9 - * TODO: uncomment when done w/ dev (double declaration makes PhpStorm mad :)
 8+ * Before using any of the cahing functionality, call startCache.
 9+ * After the last call to either getCachedValue or addCachedHTML, call saveCache.
1010 *
11 - * @since 0.1
 11+ * To get a cached value or compute it, use getCachedValue like this:
 12+ * $this->getCachedValue( $callback );
1213 *
 14+ * To add HTML that should be cached, use addCachedHTML like this:
 15+ * $this->addCachedHTML( $callback );
 16+ *
 17+ * The callback function is only called when needed, so do all your expensive
 18+ * computations here. This function should returns the HTML to be cached.
 19+ * It should not add anything to the PageOutput object!
 20+ *
 21+ * @since 1.20
 22+ *
1323 * @file SpecialCachedPage.php
14 - * @ingroup EducationProgram
 24+ * @ingroup SpecialPage
1525 *
16 - * @licence GNU GPL v3 or later
 26+ * @licence GNU GPL v2 or later
1727 * @author Jeroen De Dauw < jeroendedauw@gmail.com >
1828 */
19 -//abstract class SpecialCachedPage extends SpecialPage {
20 -//
21 -// /**
22 -// * The time to live for the cache, in seconds or a unix timestamp indicating the point of expiry.
23 -// *
24 -// * @since 0.1
25 -// * @var integer|null
26 -// */
27 -// protected $cacheExpiry = null;
28 -//
29 -// /**
30 -// * List of HTML chunks to be cached (if !hasCached) or that where cashed (of hasCached).
31 -// * If no cached already, then the newly computed chunks are added here,
32 -// * if it as cached already, chunks are removed from this list as they are needed.
33 -// *
34 -// * @since 0.1
35 -// * @var array
36 -// */
37 -// protected $cachedChunks;
38 -//
39 -// /**
40 -// * Indicates if the to be cached content was already cached.
41 -// * Null if this information is not available yet.
42 -// *
43 -// * @since 0.1
44 -// * @var boolean|null
45 -// */
46 -// protected $hasCached = null;
47 -//
48 -// /**
49 -// * Main method.
50 -// *
51 -// * @since 0.1
52 -// *
53 -// * @param string|null $subPage
54 -// */
55 -// public function execute( $subPage ) {
56 -// if ( $this->getRequest()->getText( 'action' ) === 'purge' ) {
57 -// $this->hasCached = false;
58 -// }
59 -//
60 -// if ( !is_null( $this->cacheExpiry ) ) {
61 -// $this->initCaching();
62 -//
63 -// if ( $this->hasCached === true ) {
64 -// $this->getOutput()->setSubtitle( $this->getCachedNotice( $subPage ) );
65 -// }
66 -// }
67 -// }
68 -//
69 -// /**
70 -// * Returns a message that notifies the user he/she is looking at
71 -// * a cached version of the page, including a refresh link.
72 -// *
73 -// * @since 0.1
74 -// *
75 -// * @param string|null $subPage
76 -// *
77 -// * @return string
78 -// */
79 -// protected function getCachedNotice( $subPage ) {
80 -// $refreshArgs = $this->getRequest()->getQueryValues();
81 -// unset( $refreshArgs['title'] );
82 -// $refreshArgs['action'] = 'purge';
83 -//
84 -// $refreshLink = Linker::link(
85 -// $this->getTitle( $subPage ),
86 -// $this->msg( 'cachedspecial-refresh-now' )->escaped(),
87 -// array(),
88 -// $refreshArgs
89 -// );
90 -//
91 -// if ( $this->cacheExpiry < 86400 * 3650 ) {
92 -// $message = $this->msg(
93 -// 'cachedspecial-viewing-cached-ttl',
94 -// $this->getDurationText( $this->cacheExpiry )
95 -// )->escaped();
96 -// }
97 -// else {
98 -// $message = $this->msg(
99 -// 'cachedspecial-viewing-cached-ts'
100 -// )->escaped();
101 -// }
102 -//
103 -// return $message . ' ' . $refreshLink;
104 -// }
105 -//
106 -// /**
107 -// * Returns a message with the time to live of the cache.
108 -// * Takes care of compatibility with MW < 1.20, in which Language::formatDuration was introduced.
109 -// *
110 -// * @since 0.1
111 -// *
112 -// * @param integer $seconds
113 -// * @param array $chosenIntervals
114 -// *
115 -// * @return string
116 -// */
117 -// protected function getDurationText( $seconds, array $chosenIntervals = array( 'years', 'days', 'hours', 'minutes', 'seconds' ) ) {
118 -// if ( method_exists( $this->getLanguage(), 'formatDuration' ) ) {
119 -// return $this->getLanguage()->formatDuration( $seconds, $chosenIntervals );
120 -// }
121 -// else {
122 -// $intervals = array(
123 -// 'years' => 31557600, // 86400 * 365.25
124 -// 'weeks' => 604800,
125 -// 'days' => 86400,
126 -// 'hours' => 3600,
127 -// 'minutes' => 60,
128 -// 'seconds' => 1,
129 -// );
130 -//
131 -// if ( !empty( $chosenIntervals ) ) {
132 -// $intervals = array_intersect_key( $intervals, array_flip( $chosenIntervals ) );
133 -// }
134 -//
135 -// $segments = array();
136 -//
137 -// foreach ( $intervals as $name => $length ) {
138 -// $value = floor( $seconds / $length );
139 -//
140 -// if ( $value > 0 || ( $name == 'seconds' && empty( $segments ) ) ) {
141 -// $seconds -= $value * $length;
142 -// $segments[] = $this->msg( 'duration-' . $name, array( $value ) )->escaped();
143 -// }
144 -// }
145 -//
146 -// return $this->getLanguage()->listToText( $segments );
147 -// }
148 -// }
149 -//
150 -// /**
151 -// * Initializes the caching if not already done so.
152 -// * Should be called before any of the caching functionality is used.
153 -// *
154 -// * @since 0.1
155 -// */
156 -// protected function initCaching() {
157 -// if ( is_null( $this->hasCached ) ) {
158 -// $cachedChunks = wfGetCache( CACHE_ANYTHING )->get( $this->getCacheKey() );
159 -//
160 -// $this->hasCached = is_array( $cachedChunks );
161 -// $this->cachedChunks = $this->hasCached ? $cachedChunks : array();
162 -// }
163 -// }
164 -//
165 -// /**
166 -// * Add some HTML to be cached.
167 -// * This is done by providing a callback function that should
168 -// * return the HTML to be added. It will only be called if the
169 -// * item is not in the cache yet or when the cache has been invalidated.
170 -// *
171 -// * @since 0.1
172 -// *
173 -// * @param {function} $callback
174 -// * @param array $args
175 -// * @param string|null $key
176 -// */
177 -// public function addCachedHTML( $callback, $args = array(), $key = null ) {
178 -// $this->initCaching();
179 -//
180 -// if ( $this->hasCached ) {
181 -// $html = '';
182 -//
183 -// if ( is_null( $key ) ) {
184 -// $itemKey = array_keys( array_slice( $this->cachedChunks, 0, 1 ) );
185 -// $itemKey = array_shift( $itemKey );
186 -//
187 -// if ( !is_integer( $itemKey ) ) {
188 -// wfWarn( "Attempted to get item with non-numeric key while the next item in the queue has a key ($itemKey) in " . __METHOD__ );
189 -// }
190 -// elseif ( is_null( $itemKey ) ) {
191 -// wfWarn( "Attempted to get an item while the queue is empty in " . __METHOD__ );
192 -// }
193 -// else {
194 -// $html = array_shift( $this->cachedChunks );
195 -// }
196 -// }
197 -// else {
198 -// if ( array_key_exists( $key, $this->cachedChunks ) ) {
199 -// $html = $this->cachedChunks[$key];
200 -// unset( $this->cachedChunks[$key] );
201 -// }
202 -// else {
203 -// wfWarn( "There is no item with key '$key' in this->cachedChunks in " . __METHOD__ );
204 -// }
205 -// }
206 -// }
207 -// else {
208 -// $html = call_user_func_array( $callback, $args );
209 -//
210 -// if ( is_null( $key ) ) {
211 -// $this->cachedChunks[] = $html;
212 -// }
213 -// else {
214 -// $this->cachedChunks[$key] = $html;
215 -// }
216 -// }
217 -//
218 -// $this->getOutput()->addHTML( $html );
219 -// }
220 -//
221 -// /**
222 -// * Saves the HTML to the cache in case it got recomputed.
223 -// * Should be called after the last time anything is added via addCachedHTML.
224 -// *
225 -// * @since 0.1
226 -// */
227 -// public function saveCache() {
228 -// if ( $this->hasCached === false && !empty( $this->cachedChunks ) ) {
229 -// wfGetCache( CACHE_ANYTHING )->set( $this->getCacheKey(), $this->cachedChunks, $this->cacheExpiry );
230 -// }
231 -// }
232 -//
233 -// /**
234 -// * Sets the time to live for the cache, in seconds or a unix timestamp indicating the point of expiry..
235 -// *
236 -// * @since 0.1
237 -// *
238 -// * @param integer $cacheExpiry
239 -// */
240 -// protected function setExpirey( $cacheExpiry ) {
241 -// $this->cacheExpiry = $cacheExpiry;
242 -// }
243 -//
244 -// /**
245 -// * Returns the cache key to use to cache this page's HTML output.
246 -// * Is constructed from the special page name and language code.
247 -// *
248 -// * @since 0.1
249 -// *
250 -// * @return string
251 -// */
252 -// protected function getCacheKey() {
253 -// return wfMemcKey( $this->mName, $this->getLanguage()->getCode() );
254 -// }
255 -//
256 -//}
 29+abstract class SpecialCachedPage extends SpecialPage implements ICacheHelper {
 30+
 31+ /**
 32+ * CacheHelper object to which we foreward the non-SpecialPage specific caching work.
 33+ * Initialized in startCache.
 34+ *
 35+ * @since 1.20
 36+ * @var CacheHelper
 37+ */
 38+ protected $cacheHelper;
 39+
 40+ /**
 41+ * If the cache is enabled or not.
 42+ *
 43+ * @since 1.20
 44+ * @var boolean
 45+ */
 46+ protected $cacheEnabled = true;
 47+
 48+ /**
 49+ * Sets if the cache should be enabled or not.
 50+ *
 51+ * @since 1.20
 52+ * @param boolean $cacheEnabled
 53+ */
 54+ public function setCacheEnabled( $cacheEnabled ) {
 55+ $this->cacheHelper->setCacheEnabled( $cacheEnabled );
 56+ }
 57+
 58+ /**
 59+ * Initializes the caching.
 60+ * Should be called before the first time anything is added via addCachedHTML.
 61+ *
 62+ * @since 1.20
 63+ *
 64+ * @param integer|null $cacheExpiry Sets the cache expirty, either ttl in seconds or unix timestamp.
 65+ * @param boolean|null $cacheEnabled Sets if the cache should be enabled or not.
 66+ */
 67+ public function startCache( $cacheExpiry = null, $cacheEnabled = null ) {
 68+ $this->cacheHelper = new CacheHelper();
 69+
 70+ $this->cacheHelper->setCacheEnabled( $this->cacheEnabled );
 71+ $this->cacheHelper->setOnInitializedHandler( array( $this, 'onCacheInitialized' ) );
 72+
 73+ $keyArgs = $this->getCacheKey();
 74+
 75+ if ( array_key_exists( 'action', $keyArgs ) && $keyArgs['action'] === 'purge' ) {
 76+ unset( $keyArgs['action'] );
 77+ }
 78+
 79+ $this->cacheHelper->setCacheKey( $keyArgs );
 80+
 81+ if ( $this->getRequest()->getText( 'action' ) === 'purge' ) {
 82+ $this->cacheHelper->purge();
 83+ }
 84+
 85+ $this->cacheHelper->startCache( $cacheExpiry, $cacheEnabled );
 86+ }
 87+
 88+ /**
 89+ * Get a cached value if available or compute it if not and then cache it if possible.
 90+ * The provided $computeFunction is only called when the computation needs to happen
 91+ * and should return a result value. $args are arguments that will be passed to the
 92+ * compute function when called.
 93+ *
 94+ * @since 1.20
 95+ *
 96+ * @param {function} $computeFunction
 97+ * @param array|mixed $args
 98+ * @param string|null $key
 99+ *
 100+ * @return mixed
 101+ */
 102+ public function getCachedValue( $computeFunction, $args = array(), $key = null ) {
 103+ return $this->cacheHelper->getCachedValue( $computeFunction, $args, $key );
 104+ }
 105+
 106+ /**
 107+ * Add some HTML to be cached.
 108+ * This is done by providing a callback function that should
 109+ * return the HTML to be added. It will only be called if the
 110+ * item is not in the cache yet or when the cache has been invalidated.
 111+ *
 112+ * @since 1.20
 113+ *
 114+ * @param {function} $computeFunction
 115+ * @param array $args
 116+ * @param string|null $key
 117+ */
 118+ public function addCachedHTML( $computeFunction, $args = array(), $key = null ) {
 119+ $this->getOutput()->addHTML( $this->cacheHelper->getCachedValue( $computeFunction, $args, $key ) );
 120+ }
 121+
 122+ /**
 123+ * Saves the HTML to the cache in case it got recomputed.
 124+ * Should be called after the last time anything is added via addCachedHTML.
 125+ *
 126+ * @since 1.20
 127+ */
 128+ public function saveCache() {
 129+ $this->cacheHelper->saveCache();
 130+ }
 131+
 132+ /**
 133+ * Sets the time to live for the cache, in seconds or a unix timestamp indicating the point of expiry..
 134+ *
 135+ * @since 1.20
 136+ *
 137+ * @param integer $cacheExpiry
 138+ */
 139+ public function setExpirey( $cacheExpiry ) {
 140+ $this->cacheHelper->setExpirey( $cacheExpiry );
 141+ }
 142+
 143+ /**
 144+ * Returns the variables used to constructed the cache key in an array.
 145+ *
 146+ * @since 1.20
 147+ *
 148+ * @return array
 149+ */
 150+ protected function getCacheKey() {
 151+ return array(
 152+ $this->mName,
 153+ $this->getLanguage()->getCode()
 154+ );
 155+ }
 156+
 157+ /**
 158+ * Gets called after the cache got initialized.
 159+ *
 160+ * @since 1.20
 161+ *
 162+ * @param boolean $hasCached
 163+ */
 164+ public function onCacheInitialized( $hasCached ) {
 165+ if ( $hasCached ) {
 166+ $this->getOutput()->setSubtitle( $this->cacheHelper->getCachedNotice( $this->getContext() ) );
 167+ }
 168+ }
 169+
 170+}
Index: trunk/extensions/EducationProgram/compat/DBDataObject.php
@@ -0,0 +1,667 @@
 2+<?php
 3+
 4+/**
 5+ * Abstract base class for representing objects that are stored in some DB table.
 6+ * This is basically an ORM-like wrapper around rows in database tables that
 7+ * aims to be both simple and very flexible. It is centered around an associative
 8+ * array of fields and various methods to do common interaction with the database.
 9+ *
 10+ * These methods must be implemented in deriving classes:
 11+ * * getDBTable
 12+ * * getFieldPrefix
 13+ * * getFieldTypes
 14+ *
 15+ * These methods are likely candidates for overriding:
 16+ * * getDefaults
 17+ * * remove
 18+ * * insert
 19+ * * saveExisting
 20+ * * loadSummaryFields
 21+ * * getSummaryFields
 22+ *
 23+ * Main instance methods:
 24+ * * getField(s)
 25+ * * setField(s)
 26+ * * save
 27+ * * remove
 28+ *
 29+ * Main static methods:
 30+ * * select
 31+ * * update
 32+ * * delete
 33+ * * count
 34+ * * has
 35+ * * selectRow
 36+ * * selectFields
 37+ * * selectFieldsRow
 38+ *
 39+ * @since 1.20
 40+ *
 41+ * @file DBDataObject.php
 42+ *
 43+ * @licence GNU GPL v2 or later
 44+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
 45+ */
 46+abstract class DBDataObject {
 47+
 48+ /**
 49+ * The fields of the object.
 50+ * field name (w/o prefix) => value
 51+ *
 52+ * @since 1.20
 53+ * @var array
 54+ */
 55+ protected $fields = array( 'id' => null );
 56+
 57+ /**
 58+ * @since 1.20
 59+ * @var DBTable
 60+ */
 61+ protected $table;
 62+
 63+ /**
 64+ * If the object should update summaries of linked items when changed.
 65+ * For example, update the course_count field in universities when a course in courses is deleted.
 66+ * Settings this to false can prevent needless updating work in situations
 67+ * such as deleting a university, which will then delete all it's courses.
 68+ *
 69+ * @since 1.20
 70+ * @var bool
 71+ */
 72+ protected $updateSummaries = true;
 73+
 74+ /**
 75+ * Indicates if the object is in summary mode.
 76+ * This mode indicates that only summary fields got updated,
 77+ * which allows for optimizations.
 78+ *
 79+ * @since 1.20
 80+ * @var bool
 81+ */
 82+ protected $inSummaryMode = false;
 83+
 84+ /**
 85+ * Constructor.
 86+ *
 87+ * @since 1.20
 88+ *
 89+ * @param DBTable $table
 90+ * @param array|null $fields
 91+ * @param boolean $loadDefaults
 92+ */
 93+ public function __construct( DBTable $table, $fields = null, $loadDefaults = false ) {
 94+ $this->table = $table;
 95+
 96+ if ( !is_array( $fields ) ) {
 97+ $fields = array();
 98+ }
 99+
 100+ if ( $loadDefaults ) {
 101+ $fields = array_merge( $this->table->getDefaults(), $fields );
 102+ }
 103+
 104+ $this->setFields( $fields );
 105+ }
 106+
 107+ /**
 108+ * Load the specified fields from the database.
 109+ *
 110+ * @since 1.20
 111+ *
 112+ * @param array|null $fields
 113+ * @param boolean $override
 114+ * @param boolean $skipLoaded
 115+ *
 116+ * @return bool Success indicator
 117+ */
 118+ public function loadFields( $fields = null, $override = true, $skipLoaded = false ) {
 119+ if ( is_null( $this->getId() ) ) {
 120+ return false;
 121+ }
 122+
 123+ if ( is_null( $fields ) ) {
 124+ $fields = array_keys( $this->table->getFieldTypes() );
 125+ }
 126+
 127+ if ( $skipLoaded ) {
 128+ $fields = array_diff( $fields, array_keys( $this->fields ) );
 129+ }
 130+
 131+ if ( !empty( $fields ) ) {
 132+ $result = $this->table->rawSelectRow(
 133+ $this->table->getPrefixedFields( $fields ),
 134+ array( $this->table->getPrefixedField( 'id' ) => $this->getId() ),
 135+ array( 'LIMIT' => 1 )
 136+ );
 137+
 138+ if ( $result !== false ) {
 139+ $this->setFields( $this->table->getFieldsFromDBResult( $result ), $override );
 140+ return true;
 141+ }
 142+
 143+ return false;
 144+ }
 145+
 146+ return true;
 147+ }
 148+
 149+ /**
 150+ * Gets the value of a field.
 151+ *
 152+ * @since 1.20
 153+ *
 154+ * @param string $name
 155+ * @param mixed $default
 156+ *
 157+ * @throws MWException
 158+ * @return mixed
 159+ */
 160+ public function getField( $name, $default = null ) {
 161+ if ( $this->hasField( $name ) ) {
 162+ return $this->fields[$name];
 163+ } elseif ( !is_null( $default ) ) {
 164+ return $default;
 165+ } else {
 166+ throw new MWException( 'Attempted to get not-set field ' . $name );
 167+ }
 168+ }
 169+
 170+ /**
 171+ * Gets the value of a field but first loads it if not done so already.
 172+ *
 173+ * @since 1.20
 174+ *
 175+ * @param string$name
 176+ *
 177+ * @return mixed
 178+ */
 179+ public function loadAndGetField( $name ) {
 180+ if ( !$this->hasField( $name ) ) {
 181+ $this->loadFields( array( $name ) );
 182+ }
 183+
 184+ return $this->getField( $name );
 185+ }
 186+
 187+ /**
 188+ * Remove a field.
 189+ *
 190+ * @since 1.20
 191+ *
 192+ * @param string $name
 193+ */
 194+ public function removeField( $name ) {
 195+ unset( $this->fields[$name] );
 196+ }
 197+
 198+ /**
 199+ * Returns the objects database id.
 200+ *
 201+ * @since 1.20
 202+ *
 203+ * @return integer|null
 204+ */
 205+ public function getId() {
 206+ return $this->getField( 'id' );
 207+ }
 208+
 209+ /**
 210+ * Sets the objects database id.
 211+ *
 212+ * @since 1.20
 213+ *
 214+ * @param integer|null $id
 215+ */
 216+ public function setId( $id ) {
 217+ return $this->setField( 'id', $id );
 218+ }
 219+
 220+ /**
 221+ * Gets if a certain field is set.
 222+ *
 223+ * @since 1.20
 224+ *
 225+ * @param string $name
 226+ *
 227+ * @return boolean
 228+ */
 229+ public function hasField( $name ) {
 230+ return array_key_exists( $name, $this->fields );
 231+ }
 232+
 233+ /**
 234+ * Gets if the id field is set.
 235+ *
 236+ * @since 1.20
 237+ *
 238+ * @return boolean
 239+ */
 240+ public function hasIdField() {
 241+ return $this->hasField( 'id' )
 242+ && !is_null( $this->getField( 'id' ) );
 243+ }
 244+
 245+ /**
 246+ * Sets multiple fields.
 247+ *
 248+ * @since 1.20
 249+ *
 250+ * @param array $fields The fields to set
 251+ * @param boolean $override Override already set fields with the provided values?
 252+ */
 253+ public function setFields( array $fields, $override = true ) {
 254+ foreach ( $fields as $name => $value ) {
 255+ if ( $override || !$this->hasField( $name ) ) {
 256+ $this->setField( $name, $value );
 257+ }
 258+ }
 259+ }
 260+
 261+ /**
 262+ * Gets the fields => values to write to the table.
 263+ *
 264+ * @since 1.20
 265+ *
 266+ * @return array
 267+ */
 268+ protected function getWriteValues() {
 269+ $values = array();
 270+
 271+ foreach ( $this->table->getFieldTypes() as $name => $type ) {
 272+ if ( array_key_exists( $name, $this->fields ) ) {
 273+ $value = $this->fields[$name];
 274+
 275+ switch ( $type ) {
 276+ case 'array':
 277+ $value = (array)$value;
 278+ case 'blob':
 279+ $value = serialize( $value );
 280+ }
 281+
 282+ $values[$this->table->getPrefixedField( $name )] = $value;
 283+ }
 284+ }
 285+
 286+ return $values;
 287+ }
 288+
 289+ /**
 290+ * Serializes the object to an associative array which
 291+ * can then easily be converted into JSON or similar.
 292+ *
 293+ * @since 1.20
 294+ *
 295+ * @param null|array $fields
 296+ * @param boolean $incNullId
 297+ *
 298+ * @return array
 299+ */
 300+ public function toArray( $fields = null, $incNullId = false ) {
 301+ $data = array();
 302+ $setFields = array();
 303+
 304+ if ( !is_array( $fields ) ) {
 305+ $setFields = $this->getSetFieldNames();
 306+ } else {
 307+ foreach ( $fields as $field ) {
 308+ if ( $this->hasField( $field ) ) {
 309+ $setFields[] = $field;
 310+ }
 311+ }
 312+ }
 313+
 314+ foreach ( $setFields as $field ) {
 315+ if ( $incNullId || $field != 'id' || $this->hasIdField() ) {
 316+ $data[$field] = $this->getField( $field );
 317+ }
 318+ }
 319+
 320+ return $data;
 321+ }
 322+
 323+ /**
 324+ * Load the default values, via getDefaults.
 325+ *
 326+ * @since 1.20
 327+ *
 328+ * @param boolean $override
 329+ */
 330+ public function loadDefaults( $override = true ) {
 331+ $this->setFields( $this->table->getDefaults(), $override );
 332+ }
 333+
 334+ /**
 335+ * Writes the answer to the database, either updating it
 336+ * when it already exists, or inserting it when it doesn't.
 337+ *
 338+ * @since 1.20
 339+ *
 340+ * @param string|null $functionName
 341+ *
 342+ * @return boolean Success indicator
 343+ */
 344+ public function save( $functionName = null ) {
 345+ if ( $this->hasIdField() ) {
 346+ return $this->saveExisting( $functionName );
 347+ } else {
 348+ return $this->insert( $functionName );
 349+ }
 350+ }
 351+
 352+ /**
 353+ * Updates the object in the database.
 354+ *
 355+ * @since 1.20
 356+ *
 357+ * @param string|null $functionName
 358+ *
 359+ * @return boolean Success indicator
 360+ */
 361+ protected function saveExisting( $functionName = null ) {
 362+ $dbw = wfGetDB( DB_MASTER );
 363+
 364+ $success = $dbw->update(
 365+ $this->table->getDBTable(),
 366+ $this->getWriteValues(),
 367+ $this->table->getPrefixedValues( $this->getUpdateConditions() ),
 368+ is_null( $functionName ) ? __METHOD__ : $functionName
 369+ );
 370+
 371+ return $success;
 372+ }
 373+
 374+ /**
 375+ * Returns the WHERE considtions needed to identify this object so
 376+ * it can be updated.
 377+ *
 378+ * @since 1.20
 379+ *
 380+ * @return array
 381+ */
 382+ protected function getUpdateConditions() {
 383+ return array( 'id' => $this->getId() );
 384+ }
 385+
 386+ /**
 387+ * Inserts the object into the database.
 388+ *
 389+ * @since 1.20
 390+ *
 391+ * @param string|null $functionName
 392+ * @param array|null $options
 393+ *
 394+ * @return boolean Success indicator
 395+ */
 396+ protected function insert( $functionName = null, array $options = null ) {
 397+ $dbw = wfGetDB( DB_MASTER );
 398+
 399+ $result = $dbw->insert(
 400+ $this->table->getDBTable(),
 401+ $this->getWriteValues(),
 402+ is_null( $functionName ) ? __METHOD__ : $functionName,
 403+ is_null( $options ) ? array( 'IGNORE' ) : $options
 404+ );
 405+
 406+ if ( $result ) {
 407+ $this->setField( 'id', $dbw->insertId() );
 408+ }
 409+
 410+ return $result;
 411+ }
 412+
 413+ /**
 414+ * Removes the object from the database.
 415+ *
 416+ * @since 1.20
 417+ *
 418+ * @return boolean Success indicator
 419+ */
 420+ public function remove() {
 421+ $this->beforeRemove();
 422+
 423+ $success = $this->table->delete( array( 'id' => $this->getId() ) );
 424+
 425+ if ( $success ) {
 426+ $this->onRemoved();
 427+ }
 428+
 429+ return $success;
 430+ }
 431+
 432+ /**
 433+ * Gets called before an object is removed from the database.
 434+ *
 435+ * @since 1.20
 436+ */
 437+ protected function beforeRemove() {
 438+ $this->loadFields( $this->getBeforeRemoveFields(), false, true );
 439+ }
 440+
 441+ /**
 442+ * Before removal of an object happens, @see beforeRemove gets called.
 443+ * This method loads the fields of which the names have been returned by this one (or all fields if null is returned).
 444+ * This allows for loading info needed after removal to get rid of linked data and the like.
 445+ *
 446+ * @since 1.20
 447+ *
 448+ * @return array|null
 449+ */
 450+ protected function getBeforeRemoveFields() {
 451+ return array();
 452+ }
 453+
 454+ /**
 455+ * Gets called after successfull removal.
 456+ * Can be overriden to get rid of linked data.
 457+ *
 458+ * @since 1.20
 459+ */
 460+ protected function onRemoved() {
 461+ $this->setField( 'id', null );
 462+ }
 463+
 464+ /**
 465+ * Return the names and values of the fields.
 466+ *
 467+ * @since 1.20
 468+ *
 469+ * @return array
 470+ */
 471+ public function getFields() {
 472+ return $this->fields;
 473+ }
 474+
 475+ /**
 476+ * Return the names of the fields.
 477+ *
 478+ * @since 1.20
 479+ *
 480+ * @return array
 481+ */
 482+ public function getSetFieldNames() {
 483+ return array_keys( $this->fields );
 484+ }
 485+
 486+ /**
 487+ * Sets the value of a field.
 488+ * Strings can be provided for other types,
 489+ * so this method can be called from unserialization handlers.
 490+ *
 491+ * @since 1.20
 492+ *
 493+ * @param string $name
 494+ * @param mixed $value
 495+ *
 496+ * @throws MWException
 497+ */
 498+ public function setField( $name, $value ) {
 499+ $fields = $this->table->getFieldTypes();
 500+
 501+ if ( array_key_exists( $name, $fields ) ) {
 502+ switch ( $fields[$name] ) {
 503+ case 'int':
 504+ $value = (int)$value;
 505+ break;
 506+ case 'float':
 507+ $value = (float)$value;
 508+ break;
 509+ case 'bool':
 510+ if ( is_string( $value ) ) {
 511+ $value = $value !== '0';
 512+ } elseif ( is_int( $value ) ) {
 513+ $value = $value !== 0;
 514+ }
 515+ break;
 516+ case 'array':
 517+ if ( is_string( $value ) ) {
 518+ $value = unserialize( $value );
 519+ }
 520+
 521+ if ( !is_array( $value ) ) {
 522+ $value = array();
 523+ }
 524+ break;
 525+ case 'blob':
 526+ if ( is_string( $value ) ) {
 527+ $value = unserialize( $value );
 528+ }
 529+ break;
 530+ case 'id':
 531+ if ( is_string( $value ) ) {
 532+ $value = (int)$value;
 533+ }
 534+ break;
 535+ }
 536+
 537+ $this->fields[$name] = $value;
 538+ } else {
 539+ throw new MWException( 'Attempted to set unknown field ' . $name );
 540+ }
 541+ }
 542+
 543+ /**
 544+ * Add an amount (can be negative) to the specified field (needs to be numeric).
 545+ *
 546+ * @since 1.20
 547+ *
 548+ * @param string $field
 549+ * @param integer $amount
 550+ *
 551+ * @return boolean Success indicator
 552+ */
 553+ public function addToField( $field, $amount ) {
 554+ if ( $amount == 0 ) {
 555+ return true;
 556+ }
 557+
 558+ if ( !$this->hasIdField() ) {
 559+ return false;
 560+ }
 561+
 562+ $absoluteAmount = abs( $amount );
 563+ $isNegative = $amount < 0;
 564+
 565+ $dbw = wfGetDB( DB_MASTER );
 566+
 567+ $fullField = $this->table->getPrefixedField( $field );
 568+
 569+ $success = $dbw->update(
 570+ $this->table->getDBTable(),
 571+ array( "$fullField=$fullField" . ( $isNegative ? '-' : '+' ) . $absoluteAmount ),
 572+ array( $this->table->getPrefixedField( 'id' ) => $this->getId() ),
 573+ __METHOD__
 574+ );
 575+
 576+ if ( $success && $this->hasField( $field ) ) {
 577+ $this->setField( $field, $this->getField( $field ) + $amount );
 578+ }
 579+
 580+ return $success;
 581+ }
 582+
 583+ /**
 584+ * Return the names of the fields.
 585+ *
 586+ * @since 1.20
 587+ *
 588+ * @return array
 589+ */
 590+ public function getFieldNames() {
 591+ return array_keys( $this->table->getFieldTypes() );
 592+ }
 593+
 594+ /**
 595+ * Computes and updates the values of the summary fields.
 596+ *
 597+ * @since 1.20
 598+ *
 599+ * @param array|string|null $summaryFields
 600+ */
 601+ public function loadSummaryFields( $summaryFields = null ) {
 602+
 603+ }
 604+
 605+ /**
 606+ * Sets the value for the @see $updateSummaries field.
 607+ *
 608+ * @since 1.20
 609+ *
 610+ * @param boolean $update
 611+ */
 612+ public function setUpdateSummaries( $update ) {
 613+ $this->updateSummaries = $update;
 614+ }
 615+
 616+ /**
 617+ * Sets the value for the @see $inSummaryMode field.
 618+ *
 619+ * @since 1.20
 620+ *
 621+ * @param boolean $summaryMode
 622+ */
 623+ public function setSummaryMode( $summaryMode ) {
 624+ $this->inSummaryMode = $summaryMode;
 625+ }
 626+
 627+ /**
 628+ * Return if any fields got changed.
 629+ *
 630+ * @since 1.20
 631+ *
 632+ * @param DBDataObject $object
 633+ * @param boolean|array $excludeSummaryFields
 634+ * When set to true, summary field changes are ignored.
 635+ * Can also be an array of fields to ignore.
 636+ *
 637+ * @return boolean
 638+ */
 639+ protected function fieldsChanged( DBDataObject $object, $excludeSummaryFields = false ) {
 640+ $exclusionFields = array();
 641+
 642+ if ( $excludeSummaryFields !== false ) {
 643+ $exclusionFields = is_array( $excludeSummaryFields ) ? $excludeSummaryFields : $this->table->getSummaryFields();
 644+ }
 645+
 646+ foreach ( $this->fields as $name => $value ) {
 647+ $excluded = $excludeSummaryFields && in_array( $name, $exclusionFields );
 648+
 649+ if ( !$excluded && $object->getField( $name ) !== $value ) {
 650+ return true;
 651+ }
 652+ }
 653+
 654+ return false;
 655+ }
 656+
 657+ /**
 658+ * Returns the table this DBDataObject is a row in.
 659+ *
 660+ * @since 1.20
 661+ *
 662+ * @return DBTable
 663+ */
 664+ public function getTable() {
 665+ return $this->table;
 666+ }
 667+
 668+}
Property changes on: trunk/extensions/EducationProgram/compat/DBDataObject.php
___________________________________________________________________
Added: svn:mergeinfo
1669 Merged /branches/JSTesting/includes/DBDataObject.php:r100352-107913
2670 Merged /branches/REL1_15/phase3/includes/DBDataObject.php:r51646
3671 Merged /branches/wmf/1.18wmf1/includes/DBDataObject.php:r97508
4672 Merged /branches/sqlite/includes/DBDataObject.php:r58211-58321
5673 Merged /branches/new-installer/phase3/includes/DBDataObject.php:r43664-66004
6674 Merged /branches/wmf-deployment/includes/DBDataObject.php:r53381
Added: svn:eol-style
7675 + native
Index: trunk/extensions/EducationProgram/EducationProgram.php
@@ -82,9 +82,6 @@
8383 $wgAutoloadClasses['ApiEnlist'] = dirname( __FILE__ ) . '/api/ApiEnlist.php';
8484 $wgAutoloadClasses['ApiRefreshEducation'] = dirname( __FILE__ ) . '/api/ApiRefreshEducation.php';
8585
86 -$wgAutoloadClasses['DBDataObject'] = dirname( __FILE__ ) . '/includes/DBDataObject.php';
87 -$wgAutoloadClasses['DBTable'] = dirname( __FILE__ ) . '/includes/DBTable.php';
88 -
8986 $wgAutoloadClasses['EPCourse'] = dirname( __FILE__ ) . '/includes/EPCourse.php';
9087 $wgAutoloadClasses['EPCoursePager'] = dirname( __FILE__ ) . '/includes/EPCoursePager.php';
9188 $wgAutoloadClasses['EPInstructor'] = dirname( __FILE__ ) . '/includes/EPInstructor.php';
@@ -147,6 +144,11 @@
148145
149146 // Compat classes
150147 foreach ( array(
 148+ 'DBDataObject' => 'DBDataObject.php',
 149+ 'DBTable' => 'DBTable.php',
 150+ 'CacheHelper' => 'CacheHelper.php',
 151+ 'ICacheHelper' => 'CacheHelper.php',
 152+ 'CachedAction' => 'CachedAction.php',
151153 'SpecialCachedPage' => 'SpecialCachedPage.php' // MW < 1.20
152154 ) as $className => $fileName ) {
153155
Index: trunk/extensions/EducationProgram/includes/DBTable.php
@@ -1,658 +0,0 @@
2 -<?php
3 -
4 -/**
5 - * Abstract base class for representing a single database table.
6 - *
7 - * @since 1.20
8 - *
9 - * @file DBTable.php
10 - *
11 - * @licence GNU GPL v2 or later
12 - * @author Jeroen De Dauw < jeroendedauw@gmail.com >
13 - */
14 -abstract class DBTable {
15 -
16 - /**
17 - * Returns the name of the database table objects of this type are stored in.
18 - *
19 - * @since 1.20
20 - *
21 - * @return string
22 - */
23 - public abstract function getDBTable();
24 -
25 - /**
26 - * Returns the name of a DBDataObject deriving class that
27 - * represents single rows in this table.
28 - *
29 - * @since 1.20
30 - *
31 - * @return string
32 - */
33 - public abstract function getDataObjectClass();
34 -
35 - /**
36 - * Gets the db field prefix.
37 - *
38 - * @since 1.20
39 - *
40 - * @return string
41 - */
42 - protected abstract function getFieldPrefix();
43 -
44 - /**
45 - * Returns an array with the fields and their types this object contains.
46 - * This corresponds directly to the fields in the database, without prefix.
47 - *
48 - * field name => type
49 - *
50 - * Allowed types:
51 - * * id
52 - * * str
53 - * * int
54 - * * float
55 - * * bool
56 - * * array
57 - *
58 - * @since 1.20
59 - *
60 - * @return array
61 - */
62 - public abstract function getFieldTypes();
63 -
64 - /**
65 - * The database connection to use for read operations.
66 - * Can be changed via @see setReadDb.
67 - *
68 - * @since 1.20
69 - * @var integer DB_ enum
70 - */
71 - protected $readDb = DB_SLAVE;
72 -
73 - /**
74 - * Returns a list of default field values.
75 - * field name => field value
76 - *
77 - * @since 1.20
78 - *
79 - * @return array
80 - */
81 - public function getDefaults() {
82 - return array();
83 - }
84 -
85 - /**
86 - * Returns a list of the summary fields.
87 - * These are fields that cache computed values, such as the amount of linked objects of $type.
88 - * This is relevant as one might not want to do actions such as log changes when these get updated.
89 - *
90 - * @since 1.20
91 - *
92 - * @return array
93 - */
94 - public function getSummaryFields() {
95 - return array();
96 - }
97 -
98 - /**
99 - * Selects the the specified fields of the records matching the provided
100 - * conditions and returns them as DBDataObject. Field names get prefixed.
101 - *
102 - * @since 1.20
103 - *
104 - * @param array|string|null $fields
105 - * @param array $conditions
106 - * @param array $options
107 - * @param string|null $functionName
108 - *
109 - * @return array of self
110 - */
111 - public function select( $fields = null, array $conditions = array(), array $options = array(), $functionName = null ) {
112 - $result = $this->selectFields( $fields, $conditions, $options, false, $functionName );
113 -
114 - $objects = array();
115 -
116 - foreach ( $result as $record ) {
117 - $objects[] = $this->newFromArray( $record );
118 - }
119 -
120 - return $objects;
121 - }
122 -
123 - /**
124 - * Selects the the specified fields of the records matching the provided
125 - * conditions and returns them as associative arrays.
126 - * Provided field names get prefixed.
127 - * Returned field names will not have a prefix.
128 - *
129 - * When $collapse is true:
130 - * If one field is selected, each item in the result array will be this field.
131 - * If two fields are selected, each item in the result array will have as key
132 - * the first field and as value the second field.
133 - * If more then two fields are selected, each item will be an associative array.
134 - *
135 - * @since 1.20
136 - *
137 - * @param array|string|null $fields
138 - * @param array $conditions
139 - * @param array $options
140 - * @param boolean $collapse Set to false to always return each result row as associative array.
141 - * @param string|null $functionName
142 - *
143 - * @return array of array
144 - */
145 - public function selectFields( $fields = null, array $conditions = array(), array $options = array(), $collapse = true, $functionName = null ) {
146 - if ( is_null( $fields ) ) {
147 - $fields = array_keys( $this->getFieldTypes() );
148 - }
149 - else {
150 - $fields = (array)$fields;
151 - }
152 -
153 - $dbr = wfGetDB( $this->getReadDb() );
154 - $result = $dbr->select(
155 - $this->getDBTable(),
156 - $this->getPrefixedFields( $fields ),
157 - $this->getPrefixedValues( $conditions ),
158 - is_null( $functionName ) ? __METHOD__ : $functionName,
159 - $options
160 - );
161 -
162 - $objects = array();
163 -
164 - foreach ( $result as $record ) {
165 - $objects[] = $this->getFieldsFromDBResult( $record );
166 - }
167 -
168 - if ( $collapse ) {
169 - if ( count( $fields ) === 1 ) {
170 - $objects = array_map( 'array_shift', $objects );
171 - }
172 - elseif ( count( $fields ) === 2 ) {
173 - $o = array();
174 -
175 - foreach ( $objects as $object ) {
176 - $o[array_shift( $object )] = array_shift( $object );
177 - }
178 -
179 - $objects = $o;
180 - }
181 - }
182 -
183 - return $objects;
184 - }
185 -
186 - /**
187 - * Selects the the specified fields of the first matching record.
188 - * Field names get prefixed.
189 - *
190 - * @since 1.20
191 - *
192 - * @param array|string|null $fields
193 - * @param array $conditions
194 - * @param array $options
195 - * @param string|null $functionName
196 - *
197 - * @return DBObject|bool False on failure
198 - */
199 - public function selectRow( $fields = null, array $conditions = array(), array $options = array(), $functionName = null ) {
200 - $options['LIMIT'] = 1;
201 -
202 - $objects = $this->select( $fields, $conditions, $options, $functionName );
203 -
204 - return empty( $objects ) ? false : $objects[0];
205 - }
206 -
207 - /**
208 - * Selects the the specified fields of the records matching the provided
209 - * conditions. Field names do NOT get prefixed.
210 - *
211 - * @since 1.20
212 - *
213 - * @param array $fields
214 - * @param array $conditions
215 - * @param array $options
216 - * @param string|null $functionName
217 - *
218 - * @return ResultWrapper
219 - */
220 - public function rawSelectRow( array $fields, array $conditions = array(), array $options = array(), $functionName = null ) {
221 - $dbr = wfGetDB( $this->getReadDb() );
222 -
223 - return $dbr->selectRow(
224 - $this->getDBTable(),
225 - $fields,
226 - $conditions,
227 - is_null( $functionName ) ? __METHOD__ : $functionName,
228 - $options
229 - );
230 - }
231 -
232 - /**
233 - * Selects the the specified fields of the first record matching the provided
234 - * conditions and returns it as an associative array, or false when nothing matches.
235 - * This method makes use of selectFields and expects the same parameters and
236 - * returns the same results (if there are any, if there are none, this method returns false).
237 - * @see DBDataObject::selectFields
238 - *
239 - * @since 1.20
240 - *
241 - * @param array|string|null $fields
242 - * @param array $conditions
243 - * @param array $options
244 - * @param boolean $collapse Set to false to always return each result row as associative array.
245 - * @param string|null $functionName
246 - *
247 - * @return mixed|array|bool False on failure
248 - */
249 - public function selectFieldsRow( $fields = null, array $conditions = array(), array $options = array(), $collapse = true, $functionName = null ) {
250 - $options['LIMIT'] = 1;
251 -
252 - $objects = $this->selectFields( $fields, $conditions, $options, $collapse, $functionName );
253 -
254 - return empty( $objects ) ? false : $objects[0];
255 - }
256 -
257 - /**
258 - * Returns if there is at least one record matching the provided conditions.
259 - * Condition field names get prefixed.
260 - *
261 - * @since 1.20
262 - *
263 - * @param array $conditions
264 - *
265 - * @return boolean
266 - */
267 - public function has( array $conditions = array() ) {
268 - return $this->selectRow( array( 'id' ), $conditions ) !== false;
269 - }
270 -
271 - /**
272 - * Returns the amount of matching records.
273 - * Condition field names get prefixed.
274 - *
275 - * Note that this can be expensive on large tables.
276 - * In such cases you might want to use DatabaseBase::estimateRowCount instead.
277 - *
278 - * @since 1.20
279 - *
280 - * @param array $conditions
281 - * @param array $options
282 - *
283 - * @return integer
284 - */
285 - public function count( array $conditions = array(), array $options = array() ) {
286 - $res = $this->rawSelectRow(
287 - array( 'COUNT(*) AS rowcount' ),
288 - $this->getPrefixedValues( $conditions ),
289 - $options
290 - );
291 -
292 - return $res->rowcount;
293 - }
294 -
295 - /**
296 - * Removes the object from the database.
297 - *
298 - * @since 1.20
299 - *
300 - * @param array $conditions
301 - * @param string|null $functionName
302 - *
303 - * @return boolean Success indicator
304 - */
305 - public function delete( array $conditions, $functionName = null ) {
306 - return wfGetDB( DB_MASTER )->delete(
307 - $this->getDBTable(),
308 - $this->getPrefixedValues( $conditions ),
309 - $functionName
310 - );
311 - }
312 -
313 - /**
314 - * Get API parameters for the fields supported by this object.
315 - *
316 - * @since 1.20
317 - *
318 - * @param boolean $requireParams
319 - * @param boolean $setDefaults
320 - *
321 - * @return array
322 - */
323 - public function getAPIParams( $requireParams = false, $setDefaults = false ) {
324 - $typeMap = array(
325 - 'id' => 'integer',
326 - 'int' => 'integer',
327 - 'float' => 'NULL',
328 - 'str' => 'string',
329 - 'bool' => 'integer',
330 - 'array' => 'string',
331 - 'blob' => 'string',
332 - );
333 -
334 - $params = array();
335 - $defaults = $this->getDefaults();
336 -
337 - foreach ( $this->getFieldTypes() as $field => $type ) {
338 - if ( $field == 'id' ) {
339 - continue;
340 - }
341 -
342 - $hasDefault = array_key_exists( $field, $defaults );
343 -
344 - $params[$field] = array(
345 - ApiBase::PARAM_TYPE => $typeMap[$type],
346 - ApiBase::PARAM_REQUIRED => $requireParams && !$hasDefault
347 - );
348 -
349 - if ( $type == 'array' ) {
350 - $params[$field][ApiBase::PARAM_ISMULTI] = true;
351 - }
352 -
353 - if ( $setDefaults && $hasDefault ) {
354 - $default = is_array( $defaults[$field] ) ? implode( '|', $defaults[$field] ) : $defaults[$field];
355 - $params[$field][ApiBase::PARAM_DFLT] = $default;
356 - }
357 - }
358 -
359 - return $params;
360 - }
361 -
362 - /**
363 - * Returns an array with the fields and their descriptions.
364 - *
365 - * field name => field description
366 - *
367 - * @since 1.20
368 - *
369 - * @return array
370 - */
371 - public function getFieldDescriptions() {
372 - return array();
373 - }
374 -
375 - /**
376 - * Get the database type used for read operations.
377 - *
378 - * @since 1.20
379 - *
380 - * @return integer DB_ enum
381 - */
382 - public function getReadDb() {
383 - return $this->readDb;
384 - }
385 -
386 - /**
387 - * Set the database type to use for read operations.
388 - *
389 - * @param integer $db
390 - *
391 - * @since 1.20
392 - */
393 - public function setReadDb( $db ) {
394 - $this->readDb = $db;
395 - }
396 -
397 - /**
398 - * Update the records matching the provided conditions by
399 - * setting the fields that are keys in the $values param to
400 - * their corresponding values.
401 - *
402 - * @since 1.20
403 - *
404 - * @param array $values
405 - * @param array $conditions
406 - *
407 - * @return boolean Success indicator
408 - */
409 - public function update( array $values, array $conditions = array() ) {
410 - $dbw = wfGetDB( DB_MASTER );
411 -
412 - return $dbw->update(
413 - $this->getDBTable(),
414 - $this->getPrefixedValues( $values ),
415 - $this->getPrefixedValues( $conditions ),
416 - __METHOD__
417 - );
418 - }
419 -
420 - /**
421 - * Computes the values of the summary fields of the objects matching the provided conditions.
422 - *
423 - * @since 1.20
424 - *
425 - * @param array|string|null $summaryFields
426 - * @param array $conditions
427 - */
428 - public function updateSummaryFields( $summaryFields = null, array $conditions = array() ) {
429 - $this->setReadDb( DB_MASTER );
430 -
431 - foreach ( $this->select( null, $conditions ) as /* DBDataObject */ $item ) {
432 - $item->loadSummaryFields( $summaryFields );
433 - $item->setSummaryMode( true );
434 - $item->save();
435 - }
436 -
437 - $this->setReadDb( DB_SLAVE );
438 - }
439 -
440 - /**
441 - * Takes in an associative array with field names as keys and
442 - * their values as value. The field names are prefixed with the
443 - * db field prefix.
444 - *
445 - * Field names can also be provided as an array with as first element a table name, such as
446 - * $conditions = array(
447 - * array( array( 'tablename', 'fieldname' ), $value ),
448 - * );
449 - *
450 - * @since 1.20
451 - *
452 - * @param array $values
453 - *
454 - * @return array
455 - */
456 - public function getPrefixedValues( array $values ) {
457 - $prefixedValues = array();
458 -
459 - foreach ( $values as $field => $value ) {
460 - if ( is_integer( $field ) ) {
461 - if ( is_array( $value ) ) {
462 - $field = $value[0];
463 - $value = $value[1];
464 - }
465 - else {
466 - $value = explode( ' ', $value, 2 );
467 - $value[0] = $this->getPrefixedField( $value[0] );
468 - $prefixedValues[] = implode( ' ', $value );
469 - continue;
470 - }
471 - }
472 -
473 - $prefixedValues[$this->getPrefixedField( $field )] = $value;
474 - }
475 -
476 - return $prefixedValues;
477 - }
478 -
479 - /**
480 - * Takes in a field or array of fields and returns an
481 - * array with their prefixed versions, ready for db usage.
482 - *
483 - * @since 1.20
484 - *
485 - * @param array|string $fields
486 - *
487 - * @return array
488 - */
489 - public function getPrefixedFields( array $fields ) {
490 - foreach ( $fields as &$field ) {
491 - $field = $this->getPrefixedField( $field );
492 - }
493 -
494 - return $fields;
495 - }
496 -
497 - /**
498 - * Takes in a field and returns an it's prefixed version, ready for db usage.
499 - *
500 - * @since 1.20
501 - *
502 - * @param string|array $field
503 - *
504 - * @return string
505 - */
506 - public function getPrefixedField( $field ) {
507 - return $this->getFieldPrefix() . $field;
508 - }
509 -
510 - /**
511 - * Takes an array of field names with prefix and returns the unprefixed equivalent.
512 - *
513 - * @since 1.20
514 - *
515 - * @param array $fieldNames
516 - *
517 - * @return array
518 - */
519 - public function unprefixFieldNames( array $fieldNames ) {
520 - return array_map( array( $this, 'unprefixFieldName' ), $fieldNames );
521 - }
522 -
523 - /**
524 - * Takes a field name with prefix and returns the unprefixed equivalent.
525 - *
526 - * @since 1.20
527 - *
528 - * @param string $fieldName
529 - *
530 - * @return string
531 - */
532 - public function unprefixFieldName( $fieldName ) {
533 - return substr( $fieldName, strlen( $this->getFieldPrefix() ) );
534 - }
535 -
536 - public function __construct() {
537 -
538 - }
539 -
540 - /**
541 - * Get an instance of this class.
542 - *
543 - * @since 1.20
544 - *
545 - * @return DBtable
546 - */
547 - public static function singleton() {
548 - static $instance;
549 -
550 - if ( !isset( $instance ) ) {
551 - $class = function_exists( 'get_called_class' ) ? get_called_class() : self::get_called_class();
552 - $instance = new $class;
553 - }
554 -
555 - return $instance;
556 - }
557 -
558 - /**
559 - * Compatibility fallback function so the singleton method works on PHP < 5.3.
560 - * Code borrowed from http://www.php.net/manual/en/function.get-called-class.php#107445
561 - *
562 - * @since 1.20
563 - *
564 - * @return string
565 - */
566 - protected static function get_called_class() {
567 - $bt = debug_backtrace();
568 - $l = count($bt) - 1;
569 - $matches = array();
570 - while(empty($matches) && $l > -1){
571 - $lines = file($bt[$l]['file']);
572 - $callerLine = $lines[$bt[$l]['line']-1];
573 - preg_match('/([a-zA-Z0-9\_]+)::'.$bt[$l--]['function'].'/',
574 - $callerLine,
575 - $matches);
576 - }
577 - if (!isset($matches[1])) $matches[1]=NULL; //for notices
578 - if ($matches[1] == 'self') {
579 - $line = $bt[$l]['line']-1;
580 - while ($line > 0 && strpos($lines[$line], 'class') === false) {
581 - $line--;
582 - }
583 - preg_match('/class[\s]+(.+?)[\s]+/si', $lines[$line], $matches);
584 - }
585 - return $matches[1];
586 - }
587 -
588 - /**
589 - * Get an array with fields from a database result,
590 - * that can be fed directly to the constructor or
591 - * to setFields.
592 - *
593 - * @since 1.20
594 - *
595 - * @param stdClass $result
596 - *
597 - * @return array
598 - */
599 - public function getFieldsFromDBResult( stdClass $result ) {
600 - $result = (array)$result;
601 - return array_combine(
602 - $this->unprefixFieldNames( array_keys( $result ) ),
603 - array_values( $result )
604 - );
605 - }
606 -
607 - /**
608 - * Get a new instance of the class from a database result.
609 - *
610 - * @since 1.20
611 - *
612 - * @param stdClass $result
613 - *
614 - * @return DBDataObject
615 - */
616 - public function newFromDBResult( stdClass $result ) {
617 - return $this->newFromArray( $this->getFieldsFromDBResult( $result ) );
618 - }
619 -
620 - /**
621 - * Get a new instance of the class from an array.
622 - *
623 - * @since 1.20
624 - *
625 - * @param array $data
626 - * @param boolean $loadDefaults
627 - *
628 - * @return DBDataObject
629 - */
630 - public function newFromArray( array $data, $loadDefaults = false ) {
631 - $class = $this->getDataObjectClass();
632 - return new $class( $this, $data, $loadDefaults );
633 - }
634 -
635 - /**
636 - * Return the names of the fields.
637 - *
638 - * @since 1.20
639 - *
640 - * @return array
641 - */
642 - public function getFieldNames() {
643 - return array_keys( $this->getFieldTypes() );
644 - }
645 -
646 - /**
647 - * Gets if the object can take a certain field.
648 - *
649 - * @since 1.20
650 - *
651 - * @param string $name
652 - *
653 - * @return boolean
654 - */
655 - public function canHaveField( $name ) {
656 - return array_key_exists( $name, $this->getFieldTypes() );
657 - }
658 -
659 -}
Index: trunk/extensions/EducationProgram/includes/DBDataObject.php
@@ -1,667 +0,0 @@
2 -<?php
3 -
4 -/**
5 - * Abstract base class for representing objects that are stored in some DB table.
6 - * This is basically an ORM-like wrapper around rows in database tables that
7 - * aims to be both simple and very flexible. It is centered around an associative
8 - * array of fields and various methods to do common interaction with the database.
9 - *
10 - * These methods must be implemented in deriving classes:
11 - * * getDBTable
12 - * * getFieldPrefix
13 - * * getFieldTypes
14 - *
15 - * These methods are likely candidates for overriding:
16 - * * getDefaults
17 - * * remove
18 - * * insert
19 - * * saveExisting
20 - * * loadSummaryFields
21 - * * getSummaryFields
22 - *
23 - * Main instance methods:
24 - * * getField(s)
25 - * * setField(s)
26 - * * save
27 - * * remove
28 - *
29 - * Main static methods:
30 - * * select
31 - * * update
32 - * * delete
33 - * * count
34 - * * has
35 - * * selectRow
36 - * * selectFields
37 - * * selectFieldsRow
38 - *
39 - * @since 1.20
40 - *
41 - * @file DBDataObject.php
42 - *
43 - * @licence GNU GPL v2 or later
44 - * @author Jeroen De Dauw < jeroendedauw@gmail.com >
45 - */
46 -abstract class DBDataObject {
47 -
48 - /**
49 - * The fields of the object.
50 - * field name (w/o prefix) => value
51 - *
52 - * @since 1.20
53 - * @var array
54 - */
55 - protected $fields = array( 'id' => null );
56 -
57 - /**
58 - * @since 1.20
59 - * @var DBTable
60 - */
61 - protected $table;
62 -
63 - /**
64 - * If the object should update summaries of linked items when changed.
65 - * For example, update the course_count field in universities when a course in courses is deleted.
66 - * Settings this to false can prevent needless updating work in situations
67 - * such as deleting a university, which will then delete all it's courses.
68 - *
69 - * @since 1.20
70 - * @var bool
71 - */
72 - protected $updateSummaries = true;
73 -
74 - /**
75 - * Indicates if the object is in summary mode.
76 - * This mode indicates that only summary fields got updated,
77 - * which allows for optimizations.
78 - *
79 - * @since 1.20
80 - * @var bool
81 - */
82 - protected $inSummaryMode = false;
83 -
84 - /**
85 - * Constructor.
86 - *
87 - * @since 1.20
88 - *
89 - * @param DBTable $table
90 - * @param array|null $fields
91 - * @param boolean $loadDefaults
92 - */
93 - public function __construct( DBTable $table, $fields = null, $loadDefaults = false ) {
94 - $this->table = $table;
95 -
96 - if ( !is_array( $fields ) ) {
97 - $fields = array();
98 - }
99 -
100 - if ( $loadDefaults ) {
101 - $fields = array_merge( $this->table->getDefaults(), $fields );
102 - }
103 -
104 - $this->setFields( $fields );
105 - }
106 -
107 - /**
108 - * Load the specified fields from the database.
109 - *
110 - * @since 1.20
111 - *
112 - * @param array|null $fields
113 - * @param boolean $override
114 - * @param boolean $skipLoaded
115 - *
116 - * @return bool Success indicator
117 - */
118 - public function loadFields( $fields = null, $override = true, $skipLoaded = false ) {
119 - if ( is_null( $this->getId() ) ) {
120 - return false;
121 - }
122 -
123 - if ( is_null( $fields ) ) {
124 - $fields = array_keys( $this->table->getFieldTypes() );
125 - }
126 -
127 - if ( $skipLoaded ) {
128 - $fields = array_diff( $fields, array_keys( $this->fields ) );
129 - }
130 -
131 - if ( !empty( $fields ) ) {
132 - $result = $this->table->rawSelectRow(
133 - $this->table->getPrefixedFields( $fields ),
134 - array( $this->table->getPrefixedField( 'id' ) => $this->getId() ),
135 - array( 'LIMIT' => 1 )
136 - );
137 -
138 - if ( $result !== false ) {
139 - $this->setFields( $this->table->getFieldsFromDBResult( $result ), $override );
140 - return true;
141 - }
142 -
143 - return false;
144 - }
145 -
146 - return true;
147 - }
148 -
149 - /**
150 - * Gets the value of a field.
151 - *
152 - * @since 1.20
153 - *
154 - * @param string $name
155 - * @param mixed $default
156 - *
157 - * @throws MWException
158 - * @return mixed
159 - */
160 - public function getField( $name, $default = null ) {
161 - if ( $this->hasField( $name ) ) {
162 - return $this->fields[$name];
163 - } elseif ( !is_null( $default ) ) {
164 - return $default;
165 - } else {
166 - throw new MWException( 'Attempted to get not-set field ' . $name );
167 - }
168 - }
169 -
170 - /**
171 - * Gets the value of a field but first loads it if not done so already.
172 - *
173 - * @since 1.20
174 - *
175 - * @param string$name
176 - *
177 - * @return mixed
178 - */
179 - public function loadAndGetField( $name ) {
180 - if ( !$this->hasField( $name ) ) {
181 - $this->loadFields( array( $name ) );
182 - }
183 -
184 - return $this->getField( $name );
185 - }
186 -
187 - /**
188 - * Remove a field.
189 - *
190 - * @since 1.20
191 - *
192 - * @param string $name
193 - */
194 - public function removeField( $name ) {
195 - unset( $this->fields[$name] );
196 - }
197 -
198 - /**
199 - * Returns the objects database id.
200 - *
201 - * @since 1.20
202 - *
203 - * @return integer|null
204 - */
205 - public function getId() {
206 - return $this->getField( 'id' );
207 - }
208 -
209 - /**
210 - * Sets the objects database id.
211 - *
212 - * @since 1.20
213 - *
214 - * @param integer|null $id
215 - */
216 - public function setId( $id ) {
217 - return $this->setField( 'id', $id );
218 - }
219 -
220 - /**
221 - * Gets if a certain field is set.
222 - *
223 - * @since 1.20
224 - *
225 - * @param string $name
226 - *
227 - * @return boolean
228 - */
229 - public function hasField( $name ) {
230 - return array_key_exists( $name, $this->fields );
231 - }
232 -
233 - /**
234 - * Gets if the id field is set.
235 - *
236 - * @since 1.20
237 - *
238 - * @return boolean
239 - */
240 - public function hasIdField() {
241 - return $this->hasField( 'id' )
242 - && !is_null( $this->getField( 'id' ) );
243 - }
244 -
245 - /**
246 - * Sets multiple fields.
247 - *
248 - * @since 1.20
249 - *
250 - * @param array $fields The fields to set
251 - * @param boolean $override Override already set fields with the provided values?
252 - */
253 - public function setFields( array $fields, $override = true ) {
254 - foreach ( $fields as $name => $value ) {
255 - if ( $override || !$this->hasField( $name ) ) {
256 - $this->setField( $name, $value );
257 - }
258 - }
259 - }
260 -
261 - /**
262 - * Gets the fields => values to write to the table.
263 - *
264 - * @since 1.20
265 - *
266 - * @return array
267 - */
268 - protected function getWriteValues() {
269 - $values = array();
270 -
271 - foreach ( $this->table->getFieldTypes() as $name => $type ) {
272 - if ( array_key_exists( $name, $this->fields ) ) {
273 - $value = $this->fields[$name];
274 -
275 - switch ( $type ) {
276 - case 'array':
277 - $value = (array)$value;
278 - case 'blob':
279 - $value = serialize( $value );
280 - }
281 -
282 - $values[$this->table->getPrefixedField( $name )] = $value;
283 - }
284 - }
285 -
286 - return $values;
287 - }
288 -
289 - /**
290 - * Serializes the object to an associative array which
291 - * can then easily be converted into JSON or similar.
292 - *
293 - * @since 1.20
294 - *
295 - * @param null|array $fields
296 - * @param boolean $incNullId
297 - *
298 - * @return array
299 - */
300 - public function toArray( $fields = null, $incNullId = false ) {
301 - $data = array();
302 - $setFields = array();
303 -
304 - if ( !is_array( $fields ) ) {
305 - $setFields = $this->getSetFieldNames();
306 - } else {
307 - foreach ( $fields as $field ) {
308 - if ( $this->hasField( $field ) ) {
309 - $setFields[] = $field;
310 - }
311 - }
312 - }
313 -
314 - foreach ( $setFields as $field ) {
315 - if ( $incNullId || $field != 'id' || $this->hasIdField() ) {
316 - $data[$field] = $this->getField( $field );
317 - }
318 - }
319 -
320 - return $data;
321 - }
322 -
323 - /**
324 - * Load the default values, via getDefaults.
325 - *
326 - * @since 1.20
327 - *
328 - * @param boolean $override
329 - */
330 - public function loadDefaults( $override = true ) {
331 - $this->setFields( $this->table->getDefaults(), $override );
332 - }
333 -
334 - /**
335 - * Writes the answer to the database, either updating it
336 - * when it already exists, or inserting it when it doesn't.
337 - *
338 - * @since 1.20
339 - *
340 - * @param string|null $functionName
341 - *
342 - * @return boolean Success indicator
343 - */
344 - public function save( $functionName = null ) {
345 - if ( $this->hasIdField() ) {
346 - return $this->saveExisting( $functionName );
347 - } else {
348 - return $this->insert( $functionName );
349 - }
350 - }
351 -
352 - /**
353 - * Updates the object in the database.
354 - *
355 - * @since 1.20
356 - *
357 - * @param string|null $functionName
358 - *
359 - * @return boolean Success indicator
360 - */
361 - protected function saveExisting( $functionName = null ) {
362 - $dbw = wfGetDB( DB_MASTER );
363 -
364 - $success = $dbw->update(
365 - $this->table->getDBTable(),
366 - $this->getWriteValues(),
367 - $this->table->getPrefixedValues( $this->getUpdateConditions() ),
368 - is_null( $functionName ) ? __METHOD__ : $functionName
369 - );
370 -
371 - return $success;
372 - }
373 -
374 - /**
375 - * Returns the WHERE considtions needed to identify this object so
376 - * it can be updated.
377 - *
378 - * @since 1.20
379 - *
380 - * @return array
381 - */
382 - protected function getUpdateConditions() {
383 - return array( 'id' => $this->getId() );
384 - }
385 -
386 - /**
387 - * Inserts the object into the database.
388 - *
389 - * @since 1.20
390 - *
391 - * @param string|null $functionName
392 - * @param array|null $options
393 - *
394 - * @return boolean Success indicator
395 - */
396 - protected function insert( $functionName = null, array $options = null ) {
397 - $dbw = wfGetDB( DB_MASTER );
398 -
399 - $result = $dbw->insert(
400 - $this->table->getDBTable(),
401 - $this->getWriteValues(),
402 - is_null( $functionName ) ? __METHOD__ : $functionName,
403 - is_null( $options ) ? array( 'IGNORE' ) : $options
404 - );
405 -
406 - if ( $result ) {
407 - $this->setField( 'id', $dbw->insertId() );
408 - }
409 -
410 - return $result;
411 - }
412 -
413 - /**
414 - * Removes the object from the database.
415 - *
416 - * @since 1.20
417 - *
418 - * @return boolean Success indicator
419 - */
420 - public function remove() {
421 - $this->beforeRemove();
422 -
423 - $success = $this->table->delete( array( 'id' => $this->getId() ) );
424 -
425 - if ( $success ) {
426 - $this->onRemoved();
427 - }
428 -
429 - return $success;
430 - }
431 -
432 - /**
433 - * Gets called before an object is removed from the database.
434 - *
435 - * @since 1.20
436 - */
437 - protected function beforeRemove() {
438 - $this->loadFields( $this->getBeforeRemoveFields(), false, true );
439 - }
440 -
441 - /**
442 - * Before removal of an object happens, @see beforeRemove gets called.
443 - * This method loads the fields of which the names have been returned by this one (or all fields if null is returned).
444 - * This allows for loading info needed after removal to get rid of linked data and the like.
445 - *
446 - * @since 1.20
447 - *
448 - * @return array|null
449 - */
450 - protected function getBeforeRemoveFields() {
451 - return array();
452 - }
453 -
454 - /**
455 - * Gets called after successfull removal.
456 - * Can be overriden to get rid of linked data.
457 - *
458 - * @since 1.20
459 - */
460 - protected function onRemoved() {
461 - $this->setField( 'id', null );
462 - }
463 -
464 - /**
465 - * Return the names and values of the fields.
466 - *
467 - * @since 1.20
468 - *
469 - * @return array
470 - */
471 - public function getFields() {
472 - return $this->fields;
473 - }
474 -
475 - /**
476 - * Return the names of the fields.
477 - *
478 - * @since 1.20
479 - *
480 - * @return array
481 - */
482 - public function getSetFieldNames() {
483 - return array_keys( $this->fields );
484 - }
485 -
486 - /**
487 - * Sets the value of a field.
488 - * Strings can be provided for other types,
489 - * so this method can be called from unserialization handlers.
490 - *
491 - * @since 1.20
492 - *
493 - * @param string $name
494 - * @param mixed $value
495 - *
496 - * @throws MWException
497 - */
498 - public function setField( $name, $value ) {
499 - $fields = $this->table->getFieldTypes();
500 -
501 - if ( array_key_exists( $name, $fields ) ) {
502 - switch ( $fields[$name] ) {
503 - case 'int':
504 - $value = (int)$value;
505 - break;
506 - case 'float':
507 - $value = (float)$value;
508 - break;
509 - case 'bool':
510 - if ( is_string( $value ) ) {
511 - $value = $value !== '0';
512 - } elseif ( is_int( $value ) ) {
513 - $value = $value !== 0;
514 - }
515 - break;
516 - case 'array':
517 - if ( is_string( $value ) ) {
518 - $value = unserialize( $value );
519 - }
520 -
521 - if ( !is_array( $value ) ) {
522 - $value = array();
523 - }
524 - break;
525 - case 'blob':
526 - if ( is_string( $value ) ) {
527 - $value = unserialize( $value );
528 - }
529 - break;
530 - case 'id':
531 - if ( is_string( $value ) ) {
532 - $value = (int)$value;
533 - }
534 - break;
535 - }
536 -
537 - $this->fields[$name] = $value;
538 - } else {
539 - throw new MWException( 'Attempted to set unknown field ' . $name );
540 - }
541 - }
542 -
543 - /**
544 - * Add an amount (can be negative) to the specified field (needs to be numeric).
545 - *
546 - * @since 1.20
547 - *
548 - * @param string $field
549 - * @param integer $amount
550 - *
551 - * @return boolean Success indicator
552 - */
553 - public function addToField( $field, $amount ) {
554 - if ( $amount == 0 ) {
555 - return true;
556 - }
557 -
558 - if ( !$this->hasIdField() ) {
559 - return false;
560 - }
561 -
562 - $absoluteAmount = abs( $amount );
563 - $isNegative = $amount < 0;
564 -
565 - $dbw = wfGetDB( DB_MASTER );
566 -
567 - $fullField = $this->table->getPrefixedField( $field );
568 -
569 - $success = $dbw->update(
570 - $this->table->getDBTable(),
571 - array( "$fullField=$fullField" . ( $isNegative ? '-' : '+' ) . $absoluteAmount ),
572 - array( $this->table->getPrefixedField( 'id' ) => $this->getId() ),
573 - __METHOD__
574 - );
575 -
576 - if ( $success && $this->hasField( $field ) ) {
577 - $this->setField( $field, $this->getField( $field ) + $amount );
578 - }
579 -
580 - return $success;
581 - }
582 -
583 - /**
584 - * Return the names of the fields.
585 - *
586 - * @since 1.20
587 - *
588 - * @return array
589 - */
590 - public function getFieldNames() {
591 - return array_keys( $this->table->getFieldTypes() );
592 - }
593 -
594 - /**
595 - * Computes and updates the values of the summary fields.
596 - *
597 - * @since 1.20
598 - *
599 - * @param array|string|null $summaryFields
600 - */
601 - public function loadSummaryFields( $summaryFields = null ) {
602 -
603 - }
604 -
605 - /**
606 - * Sets the value for the @see $updateSummaries field.
607 - *
608 - * @since 1.20
609 - *
610 - * @param boolean $update
611 - */
612 - public function setUpdateSummaries( $update ) {
613 - $this->updateSummaries = $update;
614 - }
615 -
616 - /**
617 - * Sets the value for the @see $inSummaryMode field.
618 - *
619 - * @since 1.20
620 - *
621 - * @param boolean $summaryMode
622 - */
623 - public function setSummaryMode( $summaryMode ) {
624 - $this->inSummaryMode = $summaryMode;
625 - }
626 -
627 - /**
628 - * Return if any fields got changed.
629 - *
630 - * @since 1.20
631 - *
632 - * @param DBDataObject $object
633 - * @param boolean|array $excludeSummaryFields
634 - * When set to true, summary field changes are ignored.
635 - * Can also be an array of fields to ignore.
636 - *
637 - * @return boolean
638 - */
639 - protected function fieldsChanged( DBDataObject $object, $excludeSummaryFields = false ) {
640 - $exclusionFields = array();
641 -
642 - if ( $excludeSummaryFields !== false ) {
643 - $exclusionFields = is_array( $excludeSummaryFields ) ? $excludeSummaryFields : $this->table->getSummaryFields();
644 - }
645 -
646 - foreach ( $this->fields as $name => $value ) {
647 - $excluded = $excludeSummaryFields && in_array( $name, $exclusionFields );
648 -
649 - if ( !$excluded && $object->getField( $name ) !== $value ) {
650 - return true;
651 - }
652 - }
653 -
654 - return false;
655 - }
656 -
657 - /**
658 - * Returns the table this DBDataObject is a row in.
659 - *
660 - * @since 1.20
661 - *
662 - * @return DBTable
663 - */
664 - public function getTable() {
665 - return $this->table;
666 - }
667 -
668 -}
Index: trunk/extensions/EducationProgram/includes/EPUtils.php
@@ -270,9 +270,57 @@
271271 return $article->fetchContent();
272272 }
273273
274 - public static function formatDuration( $seconds, $units = array( 'days', 'hours', 'minutes', 'seconds' ) ) {
275 - // TODO: compat
276 - return $GLOBALS['wgLang']->formatDuration( $seconds, $units );
 274+ /**
 275+ * Takes a number of seconds and turns it into a text using values such as hours and minutes.
 276+ * Compatibility method. As of MW 1.20, this can be found at Language::formatDuration
 277+ *
 278+ * @since 0.1
 279+ *
 280+ * @param integer $seconds The amount of seconds.
 281+ * @param array $chosenIntervals The intervals to enable.
 282+ *
 283+ * @return string
 284+ */
 285+ public static function formatDuration( $seconds, array $chosenIntervals = array() ) {
 286+ global $wgLang;
 287+
 288+ if ( method_exists( $wgLang, 'formatDuration' ) ) {
 289+ return $wgLang->formatDuration( $seconds, $chosenIntervals );
 290+ }
 291+
 292+ $intervals = array(
 293+ 'millennia' => 1000 * 31557600,
 294+ 'centuries' => 100 * 31557600,
 295+ 'decades' => 10 * 31557600,
 296+ 'years' => 31557600, // 86400 * 365.25
 297+ 'weeks' => 604800,
 298+ 'days' => 86400,
 299+ 'hours' => 3600,
 300+ 'minutes' => 60,
 301+ 'seconds' => 1,
 302+ );
 303+
 304+ if ( empty( $chosenIntervals ) ) {
 305+ $chosenIntervals = array( 'millennia', 'centuries', 'decades', 'years', 'days', 'hours', 'minutes', 'seconds' );
 306+ }
 307+
 308+ $intervals = array_intersect_key( $intervals, array_flip( $chosenIntervals ) );
 309+ $sortedNames = array_keys( $intervals );
 310+ $smallestInterval = array_pop( $sortedNames );
 311+
 312+ $segments = array();
 313+
 314+ foreach ( $intervals as $name => $length ) {
 315+ $value = floor( $seconds / $length );
 316+
 317+ if ( $value > 0 || ( $name == $smallestInterval && empty( $segments ) ) ) {
 318+ $seconds -= $value * $length;
 319+ $message = new Message( 'duration-' . $name, array( $value ) );
 320+ $segments[] = $message->inLanguage( $wgLang )->escaped();
 321+ }
 322+ }
 323+
 324+ return $wgLang->listToText( $segments );
277325 }
278326
279327 }
Index: trunk/extensions/EducationProgram/includes/EPRevisionDiff.php
@@ -0,0 +1,71 @@
 2+<?php
 3+
 4+/**
 5+ * Utility for visualizing diffs between two revisions.
 6+ *
 7+ * @since 0.1
 8+ *
 9+ * @file EPRevisionDiff.php
 10+ * @ingroup EducationProgram
 11+ *
 12+ * @licence GNU GPL v3 or later
 13+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
 14+ */
 15+class EPRevisionDiff extends ContextSource {
 16+
 17+ protected $context;
 18+
 19+ protected $changedFields = array();
 20+
 21+ public static function newFromUndoRevision( IContextSource $context, EPRevision $revison, array $fields = null ) {
 22+ $changedFields = array();
 23+
 24+ $oldObject = $revison->getPreviousRevision()->getObject();
 25+
 26+ if ( $oldObject !== false ) {
 27+ $newObject = $revison->getObject();
 28+
 29+ $fields = is_null( $fields ) ? $newObject->getFieldNames() : $fields;
 30+
 31+ foreach ( $fields as $fieldName ) {
 32+ if ( $this->getField( $fieldName ) === $newObject->getField( $fieldName ) ) {
 33+ $changedFields[$fieldName] = array( );
 34+ $this->restoreField( $fieldName, $oldObject->getField( $fieldName ) );
 35+ }
 36+ }
 37+ }
 38+
 39+ return new self( $context, $changedFields );
 40+ }
 41+
 42+ public function __construct( IContextSource $context, array $changedFields ) {
 43+ $this->setContext( $context );
 44+ $this->changedFields = $changedFields;
 45+ }
 46+
 47+ public function display() {
 48+ $out = $this->getOutput();
 49+
 50+ $out->addHTML( '<table><tr>' );
 51+
 52+ $out->addElement( 'th', array(), '' );
 53+ $out->addElement( 'th', array(), $this->msg()->plain( 'ep-diff-old' ) );
 54+ $out->addElement( 'th', array(), $this->msg()->plain( 'ep-diff-new' ) );
 55+
 56+ $out->addHTML( '</tr>' );
 57+
 58+ foreach ( $this->changedFields as $field => $values ) {
 59+ list( $old, $new ) = $values;
 60+
 61+ $out->addHtml( '<tr>' );
 62+
 63+ $out->addElement( '<th>', array(), $field );
 64+ $out->addElement( '<td>', array(), $old );
 65+ $out->addElement( '<td>', array(), $new );
 66+
 67+ $out->addHtml( '</tr>' );
 68+ }
 69+ }
 70+
 71+
 72+}
\ No newline at end of file
Index: trunk/extensions/EducationProgram/includes/EPRevisionedObject.php
@@ -396,6 +396,33 @@
397397 }
398398
399399 /**
 400+ *
 401+ * @since 0.1
 402+ *
 403+ * @param EPRevision $revison
 404+ * @param array|null $fields
 405+ *
 406+ * @return EPRevisionDiff
 407+ */
 408+ public function getUndoDiff( EPRevision $revison, array $fields = null ) {
 409+ $oldObject = $revison->getPreviousRevision()->getObject();
 410+
 411+ if ( $oldObject === false ) {
 412+ return false;
 413+ }
 414+
 415+ $newObject = $revison->getObject();
 416+
 417+ $fields = is_null( $fields ) ? $newObject->getFieldNames() : $fields;
 418+
 419+ foreach ( $fields as $fieldName ) {
 420+ if ( $this->getField( $fieldName ) === $newObject->getField( $fieldName ) ) {
 421+ $this->restoreField( $fieldName, $oldObject->getField( $fieldName ) );
 422+ }
 423+ }
 424+ }
 425+
 426+ /**
400427 * Set a field to the value of the corresponding field in the provided object.
401428 *
402429 * @since 0.1

Status & tagging log