Reedy has uploaded a new change for review.
https://gerrit.wikimedia.org/r/246718
Change subject: Move ORM classes to EducationProgram extension
......................................................................
Move ORM classes to EducationProgram extension
Change-Id: Id223222aff7d375687bbf0126f3514d248660bab
---
M EducationProgram.php
A includes/ORMIterator.php
A includes/ORMResult.php
M includes/RoleObject.php
M includes/pagers/CoursePager.php
M includes/rows/EPRevision.php
A includes/rows/IORMRow.php
A includes/rows/ORMRow.php
M includes/rows/RevisionedObject.php
M includes/tables/CAs.php
M includes/tables/Events.php
A includes/tables/IORMTable.php
M includes/tables/Instructors.php
M includes/tables/OAs.php
A includes/tables/ORMTable.php
M includes/tables/PageTable.php
M includes/tables/Revisions.php
M includes/tables/Students.php
A tests/phpunit/db/ORMRowTest.php
A tests/phpunit/db/ORMTableTest.php
A tests/phpunit/db/TestORMRowTest.php
M tests/phpunit/rows/CourseTest.php
M tests/phpunit/rows/OrgTest.php
M tests/phpunit/rows/PageObjectTest.php
24 files changed, 3,288 insertions(+), 19 deletions(-)
git pull
ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/EducationProgram
refs/changes/18/246718/1
diff --git a/EducationProgram.php b/EducationProgram.php
index 058c2bc..93291d2 100644
--- a/EducationProgram.php
+++ b/EducationProgram.php
@@ -134,7 +134,9 @@
$wgAutoloadClasses['EducationProgram\EPArticle']
= $dir . '/includes/rows/EPArticle.php';
$wgAutoloadClasses['EducationProgram\EPRevision']
= $dir . '/includes/rows/EPRevision.php';
$wgAutoloadClasses['EducationProgram\Instructor']
= $dir . '/includes/rows/Instructor.php';
+$wgAutoloadClasses['EducationProgram\IORMRow']
= $dir . '/includes/rows/IORMRow.php';
$wgAutoloadClasses['EducationProgram\OA']
= $dir . '/includes/rows/OA.php';
+$wgAutoloadClasses['EducationProgram\ORMRow']
= $dir . '/includes/rows/ORMRow.php';
$wgAutoloadClasses['EducationProgram\Org']
= $dir . '/includes/rows/Org.php';
$wgAutoloadClasses['EducationProgram\PageObject']
= $dir . '/includes/rows/PageObject.php';
$wgAutoloadClasses['EducationProgram\RevisionedObject']
= $dir . '/includes/rows/RevisionedObject.php';
@@ -165,7 +167,9 @@
$wgAutoloadClasses['EducationProgram\Courses']
= $dir . '/includes/tables/Courses.php';
$wgAutoloadClasses['EducationProgram\Events']
= $dir . '/includes/tables/Events.php';
$wgAutoloadClasses['EducationProgram\Instructors']
= $dir . '/includes/tables/Instructors.php';
+$wgAutoloadClasses['EducationProgram\IORMTable']
= $dir . '/includes/rows/IORMTable.php';
$wgAutoloadClasses['EducationProgram\OAs']
= $dir . '/includes/tables/OAs.php';
+$wgAutoloadClasses['EducationProgram\ORMTable']
= $dir . '/includes/rows/ORMTable.php';
$wgAutoloadClasses['EducationProgram\Orgs']
= $dir . '/includes/tables/Orgs.php';
$wgAutoloadClasses['EducationProgram\PageTable']
= $dir . '/includes/tables/PageTable.php';
$wgAutoloadClasses['EducationProgram\Revisions']
= $dir . '/includes/tables/Revisions.php';
@@ -210,6 +214,8 @@
$wgAutoloadClasses['EducationProgram\UPCUserCourseFinder']
= $dir . '/includes/UPCUserCourseFinder.php';
$wgAutoloadClasses['EducationProgram\UserCourseFinder']
= $dir . '/includes/UserCourseFinder.php';
$wgAutoloadClasses['EducationProgram\UserRolesMessage']
= $dir . '/includes/UserRolesMessage.php';
+$wgAutoloadClasses['EducationProgram\ORMIterator']
= $dir . '/includes/ORMIterator.php';
+$wgAutoloadClasses['EducationProgram\ORMResult']
= $dir . '/includes/ORMResult.php';
$wgAutoloadClasses['EducationProgram\OrgDeletionHelper']
= $dir . '/includes/OrgDeletionHelper.php';
$wgAutoloadClasses['EducationProgram\OrgDelCheck']
= $dir . '/includes/OrgDeletionHelper.php';
$wgAutoloadClasses['EducationProgram\Utils']
= $dir . '/includes/Utils.php';
diff --git a/includes/ORMIterator.php b/includes/ORMIterator.php
new file mode 100644
index 0000000..b30caa3
--- /dev/null
+++ b/includes/ORMIterator.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace EducationProgram;
+
+/**
+ * Interface for Iterators containing IORMRows.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @since 1.20
+ *
+ * @file
+ * @ingroup ORM
+ *
+ * @license GNU GPL v2 or later
+ * @author Jeroen De Dauw < [email protected] >
+ */
+interface ORMIterator extends \Iterator {
+}
diff --git a/includes/ORMResult.php b/includes/ORMResult.php
new file mode 100644
index 0000000..0cb3a5f
--- /dev/null
+++ b/includes/ORMResult.php
@@ -0,0 +1,124 @@
+<?php
+
+namespace EducationProgram;
+
+/**
+ * ORMIterator that takes a ResultWrapper object returned from
+ * a select operation returning IORMRow objects (ie IORMTable::select).
+ *
+ * Documentation inline and at https://www.mediawiki.org/wiki/Manual:ORMTable
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @since 1.20
+ *
+ * @file ORMResult.php
+ * @ingroup ORM
+ *
+ * @license GNU GPL v2 or later
+ * @author Jeroen De Dauw < [email protected] >
+ */
+
+class ORMResult implements ORMIterator {
+ /**
+ * @var \ResultWrapper
+ */
+ protected $res;
+
+ /**
+ * @var int
+ */
+ protected $key;
+
+ /**
+ * @var IORMRow
+ */
+ protected $current;
+
+ /**
+ * @var IORMTable
+ */
+ protected $table;
+
+ /**
+ * @param IORMTable $table
+ * @param \ResultWrapper $res
+ */
+ public function __construct( IORMTable $table, \ResultWrapper $res ) {
+ $this->table = $table;
+ $this->res = $res;
+ $this->key = 0;
+ $this->setCurrent( $this->res->current() );
+ }
+
+ /**
+ * @param bool|object $row
+ */
+ protected function setCurrent( $row ) {
+ if ( $row === false ) {
+ $this->current = false;
+ } else {
+ $this->current = $this->table->newRowFromDBResult( $row
);
+ }
+ }
+
+ /**
+ * @return int
+ */
+ public function count() {
+ return $this->res->numRows();
+ }
+
+ /**
+ * @return bool
+ */
+ public function isEmpty() {
+ return $this->res->numRows() === 0;
+ }
+
+ /**
+ * @return IORMRow
+ */
+ public function current() {
+ return $this->current;
+ }
+
+ /**
+ * @return int
+ */
+ public function key() {
+ return $this->key;
+ }
+
+ public function next() {
+ $row = $this->res->next();
+ $this->setCurrent( $row );
+ $this->key++;
+ }
+
+ public function rewind() {
+ $this->res->rewind();
+ $this->key = 0;
+ $this->setCurrent( $this->res->current() );
+ }
+
+ /**
+ * @return bool
+ */
+ public function valid() {
+ return $this->current !== false;
+ }
+}
diff --git a/includes/RoleObject.php b/includes/RoleObject.php
index b9a5b03..4a233c5 100644
--- a/includes/RoleObject.php
+++ b/includes/RoleObject.php
@@ -13,7 +13,7 @@
* @licence GNU GPL v2+
* @author Jeroen De Dauw < [email protected] >
*/
-abstract class RoleObject extends \ORMRow implements IRole {
+abstract class RoleObject extends ORMRow implements IRole {
/**
* Field for caching the linked user.
diff --git a/includes/pagers/CoursePager.php b/includes/pagers/CoursePager.php
index 0ad95b4..4eb58f0 100644
--- a/includes/pagers/CoursePager.php
+++ b/includes/pagers/CoursePager.php
@@ -238,7 +238,7 @@
/**
* @see EPPager::getControlLinks()
*/
- protected function getControlLinks( \IORMRow $item ) {
+ protected function getControlLinks( ORMRow $item ) {
$links = parent::getControlLinks( $item );
$links[] = $item->getLink( 'view', $this->msg( 'view'
)->escaped() );
diff --git a/includes/rows/EPRevision.php b/includes/rows/EPRevision.php
index 24819fd..f7966c6 100644
--- a/includes/rows/EPRevision.php
+++ b/includes/rows/EPRevision.php
@@ -13,7 +13,7 @@
* @licence GNU GPL v2+
* @author Jeroen De Dauw < [email protected] >
*/
-class EPRevision extends \ORMRow {
+class EPRevision extends ORMRow {
/**
* Cached user object for this revision.
diff --git a/includes/rows/IORMRow.php b/includes/rows/IORMRow.php
new file mode 100644
index 0000000..039172c
--- /dev/null
+++ b/includes/rows/IORMRow.php
@@ -0,0 +1,272 @@
+<?php
+namespace EducationProgram;
+
+/**
+ * Interface for representing objects that are stored in some DB table.
+ * This is basically an ORM-like wrapper around rows in database tables that
+ * aims to be both simple and very flexible. It is centered around an
associative
+ * array of fields and various methods to do common interaction with the
database.
+ *
+ * Documentation inline and at https://www.mediawiki.org/wiki/Manual:ORMTable
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @since 1.20
+ *
+ * @file
+ * @ingroup ORM
+ *
+ * @license GNU GPL v2 or later
+ * @author Jeroen De Dauw < [email protected] >
+ */
+
+interface IORMRow {
+ /**
+ * Load the specified fields from the database.
+ *
+ * @since 1.20
+ * @deprecated since 1.22
+ *
+ * @param array|null $fields
+ * @param bool $override
+ * @param bool $skipLoaded
+ *
+ * @return bool Success indicator
+ */
+ public function loadFields( $fields = null, $override = true,
$skipLoaded = false );
+
+ /**
+ * Gets the value of a field.
+ *
+ * @since 1.20
+ *
+ * @param string $name
+ * @param mixed $default
+ *
+ * @throws \MWException
+ * @return mixed
+ */
+ public function getField( $name, $default = null );
+
+ /**
+ * Gets the value of a field but first loads it if not done so already.
+ *
+ * @since 1.20
+ * @deprecated since 1.22
+ *
+ * @param string $name
+ *
+ * @return mixed
+ */
+ public function loadAndGetField( $name );
+
+ /**
+ * Remove a field.
+ *
+ * @since 1.20
+ *
+ * @param string $name
+ */
+ public function removeField( $name );
+
+ /**
+ * Returns the objects database id.
+ *
+ * @since 1.20
+ *
+ * @return int|null
+ */
+ public function getId();
+
+ /**
+ * Sets the objects database id.
+ *
+ * @since 1.20
+ *
+ * @param int|null $id
+ */
+ public function setId( $id );
+
+ /**
+ * Gets if a certain field is set.
+ *
+ * @since 1.20
+ *
+ * @param string $name
+ *
+ * @return bool
+ */
+ public function hasField( $name );
+
+ /**
+ * Gets if the id field is set.
+ *
+ * @since 1.20
+ *
+ * @return bool
+ */
+ public function hasIdField();
+
+ /**
+ * Sets multiple fields.
+ *
+ * @since 1.20
+ *
+ * @param array $fields The fields to set
+ * @param bool $override Override already set fields with the provided
values?
+ */
+ public function setFields( array $fields, $override = true );
+
+ /**
+ * Serializes the object to an associative array which
+ * can then easily be converted into JSON or similar.
+ *
+ * @since 1.20
+ *
+ * @param null|array $fields
+ * @param bool $incNullId
+ *
+ * @return array
+ */
+ public function toArray( $fields = null, $incNullId = false );
+
+ /**
+ * Load the default values, via getDefaults.
+ *
+ * @since 1.20
+ * @deprecated since 1.22
+ *
+ * @param bool $override
+ */
+ public function loadDefaults( $override = true );
+
+ /**
+ * Writes the answer to the database, either updating it
+ * when it already exists, or inserting it when it doesn't.
+ *
+ * @since 1.20
+ *
+ * @param string|null $functionName
+ * @deprecated since 1.22
+ *
+ * @return bool Success indicator
+ */
+ public function save( $functionName = null );
+
+ /**
+ * Removes the object from the database.
+ *
+ * @since 1.20
+ * @deprecated since 1.22
+ *
+ * @return bool Success indicator
+ */
+ public function remove();
+
+ /**
+ * Return the names and values of the fields.
+ *
+ * @since 1.20
+ *
+ * @return array
+ */
+ public function getFields();
+
+ /**
+ * Return the names of the fields.
+ *
+ * @since 1.20
+ *
+ * @return array
+ */
+ public function getSetFieldNames();
+
+ /**
+ * Sets the value of a field.
+ * Strings can be provided for other types,
+ * so this method can be called from unserialization handlers.
+ *
+ * @since 1.20
+ *
+ * @param string $name
+ * @param mixed $value
+ *
+ * @throws \MWException
+ */
+ public function setField( $name, $value );
+
+ /**
+ * Add an amount (can be negative) to the specified field (needs to be
numeric).
+ *
+ * @since 1.20
+ * @deprecated since 1.22
+ *
+ * @param string $field
+ * @param int $amount
+ *
+ * @return bool Success indicator
+ */
+ public function addToField( $field, $amount );
+
+ /**
+ * Return the names of the fields.
+ *
+ * @since 1.20
+ *
+ * @return array
+ */
+ public function getFieldNames();
+
+ /**
+ * Computes and updates the values of the summary fields.
+ *
+ * @since 1.20
+ * @deprecated since 1.22
+ *
+ * @param array|string|null $summaryFields
+ */
+ public function loadSummaryFields( $summaryFields = null );
+
+ /**
+ * Sets the value for the @see $updateSummaries field.
+ *
+ * @since 1.20
+ * @deprecated since 1.22
+ *
+ * @param bool $update
+ */
+ public function setUpdateSummaries( $update );
+
+ /**
+ * Sets the value for the @see $inSummaryMode field.
+ *
+ * @since 1.20
+ * @deprecated since 1.22
+ *
+ * @param bool $summaryMode
+ */
+ public function setSummaryMode( $summaryMode );
+
+ /**
+ * Returns the table this IORMRow is a row in.
+ *
+ * @since 1.20
+ * @deprecated since 1.22
+ *
+ * @return IORMTable
+ */
+ public function getTable();
+}
diff --git a/includes/rows/ORMRow.php b/includes/rows/ORMRow.php
new file mode 100644
index 0000000..e3a0d6c
--- /dev/null
+++ b/includes/rows/ORMRow.php
@@ -0,0 +1,595 @@
+<?php
+namespace EducationProgram;
+
+/**
+ * Abstract base class for representing objects that are stored in some DB
table.
+ * This is basically an ORM-like wrapper around rows in database tables that
+ * aims to be both simple and very flexible. It is centered around an
associative
+ * array of fields and various methods to do common interaction with the
database.
+ *
+ * Documentation inline and at https://www.mediawiki.org/wiki/Manual:ORMTable
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @since 1.20
+ *
+ * @file ORMRow.php
+ * @ingroup ORM
+ *
+ * @license GNU GPL v2 or later
+ * @author Jeroen De Dauw < [email protected] >
+ */
+
+class ORMRow implements IORMRow {
+ /**
+ * The fields of the object.
+ * field name (w/o prefix) => value
+ *
+ * @since 1.20
+ * @var array
+ */
+ protected $fields = array( 'id' => null );
+
+ /**
+ * If the object should update summaries of linked items when changed.
+ * For example, update the course_count field in universities when a
course in courses is deleted.
+ * Settings this to false can prevent needless updating work in
situations
+ * such as deleting a university, which will then delete all it's
courses.
+ *
+ * @deprecated since 1.22
+ * @since 1.20
+ * @var bool
+ */
+ protected $updateSummaries = true;
+
+ /**
+ * Indicates if the object is in summary mode.
+ * This mode indicates that only summary fields got updated,
+ * which allows for optimizations.
+ *
+ * @deprecated since 1.22
+ * @since 1.20
+ * @var bool
+ */
+ protected $inSummaryMode = false;
+
+ /**
+ * @deprecated since 1.22
+ * @since 1.20
+ * @var ORMTable|null
+ */
+ protected $table;
+
+ /**
+ * Constructor.
+ *
+ * @since 1.20
+ *
+ * @param IORMTable|null $table Deprecated since 1.22
+ * @param array|null $fields
+ * @param bool $loadDefaults Deprecated since 1.22
+ */
+ public function __construct( IORMTable $table = null, $fields = null,
$loadDefaults = false ) {
+ $this->table = $table;
+
+ if ( !is_array( $fields ) ) {
+ $fields = array();
+ }
+
+ if ( $loadDefaults ) {
+ $fields = array_merge( $this->table->getDefaults(),
$fields );
+ }
+
+ $this->setFields( $fields );
+ }
+
+ /**
+ * Load the specified fields from the database.
+ *
+ * @since 1.20
+ * @deprecated since 1.22
+ *
+ * @param array|null $fields
+ * @param bool $override
+ * @param bool $skipLoaded
+ *
+ * @return bool Success indicator
+ */
+ public function loadFields( $fields = null, $override = true,
$skipLoaded = false ) {
+ if ( is_null( $this->getId() ) ) {
+ return false;
+ }
+
+ if ( is_null( $fields ) ) {
+ $fields = array_keys( $this->table->getFields() );
+ }
+
+ if ( $skipLoaded ) {
+ $fields = array_diff( $fields, array_keys(
$this->fields ) );
+ }
+
+ if ( !empty( $fields ) ) {
+ $result = $this->table->rawSelectRow(
+ $this->table->getPrefixedFields( $fields ),
+ array( $this->table->getPrefixedField( 'id' )
=> $this->getId() ),
+ array( 'LIMIT' => 1 ),
+ __METHOD__
+ );
+
+ if ( $result !== false ) {
+ $this->setFields(
$this->table->getFieldsFromDBResult( $result ), $override );
+
+ return true;
+ }
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Gets the value of a field.
+ *
+ * @since 1.20
+ *
+ * @param string $name Field name
+ * @param mixed $default Default value to return when none is found
+ * (default: null)
+ *
+ * @throws \MWException
+ * @return mixed
+ */
+ public function getField( $name, $default = null ) {
+ if ( $this->hasField( $name ) ) {
+ return $this->fields[$name];
+ } elseif ( !is_null( $default ) ) {
+ return $default;
+ } else {
+ throw new \MWException( 'Attempted to get not-set field
' . $name );
+ }
+ }
+
+ /**
+ * Gets the value of a field but first loads it if not done so already.
+ *
+ * @since 1.20
+ * @deprecated since 1.22
+ *
+ * @param string $name
+ *
+ * @return mixed
+ */
+ public function loadAndGetField( $name ) {
+ if ( !$this->hasField( $name ) ) {
+ $this->loadFields( array( $name ) );
+ }
+
+ return $this->getField( $name );
+ }
+
+ /**
+ * Remove a field.
+ *
+ * @since 1.20
+ *
+ * @param string $name
+ */
+ public function removeField( $name ) {
+ unset( $this->fields[$name] );
+ }
+
+ /**
+ * Returns the objects database id.
+ *
+ * @since 1.20
+ *
+ * @return int|null
+ */
+ public function getId() {
+ return $this->getField( 'id' );
+ }
+
+ /**
+ * Sets the objects database id.
+ *
+ * @since 1.20
+ *
+ * @param int|null $id
+ */
+ public function setId( $id ) {
+ $this->setField( 'id', $id );
+ }
+
+ /**
+ * Gets if a certain field is set.
+ *
+ * @since 1.20
+ *
+ * @param string $name
+ *
+ * @return bool
+ */
+ public function hasField( $name ) {
+ return array_key_exists( $name, $this->fields );
+ }
+
+ /**
+ * Gets if the id field is set.
+ *
+ * @since 1.20
+ *
+ * @return bool
+ */
+ public function hasIdField() {
+ return $this->hasField( 'id' ) && !is_null( $this->getField(
'id' ) );
+ }
+
+ /**
+ * Gets the fields => values to write to the table.
+ *
+ * @since 1.20
+ * @deprecated since 1.22
+ *
+ * @return array
+ */
+ protected function getWriteValues() {
+ $values = array();
+
+ foreach ( $this->table->getFields() as $name => $type ) {
+ if ( array_key_exists( $name, $this->fields ) ) {
+ $value = $this->fields[$name];
+
+ // Skip null id fields so that the DBMS can set
the default.
+ if ( $name === 'id' && is_null( $value ) ) {
+ continue;
+ }
+
+ switch ( $type ) {
+ case 'array':
+ $value = (array)$value;
+ // fall-through!
+ case 'blob':
+ $value = serialize( $value );
+ // fall-through!
+ }
+
+ $values[$this->table->getPrefixedField( $name
)] = $value;
+ }
+ }
+
+ return $values;
+ }
+
+ /**
+ * Sets multiple fields.
+ *
+ * @since 1.20
+ *
+ * @param array $fields The fields to set
+ * @param bool $override Override already set fields with the provided
values?
+ */
+ public function setFields( array $fields, $override = true ) {
+ foreach ( $fields as $name => $value ) {
+ if ( $override || !$this->hasField( $name ) ) {
+ $this->setField( $name, $value );
+ }
+ }
+ }
+
+ /**
+ * Serializes the object to an associative array which
+ * can then easily be converted into JSON or similar.
+ *
+ * @since 1.20
+ *
+ * @param null|array $fields
+ * @param bool $incNullId
+ *
+ * @return array
+ */
+ public function toArray( $fields = null, $incNullId = false ) {
+ $data = array();
+ $setFields = array();
+
+ if ( !is_array( $fields ) ) {
+ $setFields = $this->getSetFieldNames();
+ } else {
+ foreach ( $fields as $field ) {
+ if ( $this->hasField( $field ) ) {
+ $setFields[] = $field;
+ }
+ }
+ }
+
+ foreach ( $setFields as $field ) {
+ if ( $incNullId || $field != 'id' ||
$this->hasIdField() ) {
+ $data[$field] = $this->getField( $field );
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * Load the default values, via getDefaults.
+ *
+ * @since 1.20
+ * @deprecated since 1.22
+ *
+ * @param bool $override
+ */
+ public function loadDefaults( $override = true ) {
+ $this->setFields( $this->table->getDefaults(), $override );
+ }
+
+ /**
+ * Writes the answer to the database, either updating it
+ * when it already exists, or inserting it when it doesn't.
+ *
+ * @since 1.20
+ * @deprecated since 1.22 Use IORMTable->updateRow or ->insertRow
+ *
+ * @param string|null $functionName
+ *
+ * @return bool Success indicator
+ */
+ public function save( $functionName = null ) {
+ if ( $this->hasIdField() ) {
+ return $this->table->updateRow( $this, $functionName );
+ } else {
+ return $this->table->insertRow( $this, $functionName );
+ }
+ }
+
+ /**
+ * Updates the object in the database.
+ *
+ * @since 1.20
+ * @deprecated since 1.22
+ *
+ * @param string|null $functionName
+ *
+ * @return bool Success indicator
+ */
+ protected function saveExisting( $functionName = null ) {
+ $dbw = $this->table->getWriteDbConnection();
+
+ $success = $dbw->update(
+ $this->table->getName(),
+ $this->getWriteValues(),
+ $this->table->getPrefixedValues(
$this->getUpdateConditions() ),
+ is_null( $functionName ) ? __METHOD__ : $functionName
+ );
+
+ $this->table->releaseConnection( $dbw );
+
+ // DatabaseBase::update does not always return true for success
as documented...
+ return $success !== false;
+ }
+
+ /**
+ * Returns the WHERE considtions needed to identify this object so
+ * it can be updated.
+ *
+ * @since 1.20
+ *
+ * @return array
+ */
+ protected function getUpdateConditions() {
+ return array( 'id' => $this->getId() );
+ }
+
+ /**
+ * Inserts the object into the database.
+ *
+ * @since 1.20
+ * @deprecated since 1.22
+ *
+ * @param string|null $functionName
+ * @param array|null $options
+ *
+ * @return bool Success indicator
+ */
+ protected function insert( $functionName = null, array $options = null
) {
+ $dbw = $this->table->getWriteDbConnection();
+
+ $success = $dbw->insert(
+ $this->table->getName(),
+ $this->getWriteValues(),
+ is_null( $functionName ) ? __METHOD__ : $functionName,
+ $options
+ );
+
+ // DatabaseBase::insert does not always return true for success
as documented...
+ $success = $success !== false;
+
+ if ( $success ) {
+ $this->setField( 'id', $dbw->insertId() );
+ }
+
+ $this->table->releaseConnection( $dbw );
+
+ return $success;
+ }
+
+ /**
+ * Removes the object from the database.
+ *
+ * @since 1.20
+ * @deprecated since 1.22, use IORMTable->removeRow
+ *
+ * @return bool Success indicator
+ */
+ public function remove() {
+ $this->beforeRemove();
+
+ $success = $this->table->removeRow( $this, __METHOD__ );
+
+ if ( $success ) {
+ $this->onRemoved();
+ }
+
+ return $success;
+ }
+
+ /**
+ * Gets called before an object is removed from the database.
+ *
+ * @since 1.20
+ * @deprecated since 1.22
+ */
+ protected function beforeRemove() {
+ $this->loadFields( $this->getBeforeRemoveFields(), false, true
);
+ }
+
+ /**
+ * Before removal of an object happens, @see beforeRemove gets called.
+ * This method loads the fields of which the names have been returned by
+ * this one (or all fields if null is returned). This allows for loading
+ * info needed after removal to get rid of linked data and the like.
+ *
+ * @since 1.20
+ *
+ * @return array|null
+ */
+ protected function getBeforeRemoveFields() {
+ return array();
+ }
+
+ /**
+ * Gets called after successful removal.
+ * Can be overridden to get rid of linked data.
+ *
+ * @since 1.20
+ * @deprecated since 1.22
+ */
+ protected function onRemoved() {
+ $this->setField( 'id', null );
+ }
+
+ /**
+ * Return the names and values of the fields.
+ *
+ * @since 1.20
+ *
+ * @return array
+ */
+ public function getFields() {
+ return $this->fields;
+ }
+
+ /**
+ * Return the names of the fields.
+ *
+ * @since 1.20
+ *
+ * @return array
+ */
+ public function getSetFieldNames() {
+ return array_keys( $this->fields );
+ }
+
+ /**
+ * Sets the value of a field.
+ * Strings can be provided for other types,
+ * so this method can be called from unserialization handlers.
+ *
+ * @since 1.20
+ *
+ * @param string $name
+ * @param mixed $value
+ *
+ * @throws \MWException
+ */
+ public function setField( $name, $value ) {
+ $this->fields[$name] = $value;
+ }
+
+ /**
+ * Add an amount (can be negative) to the specified field (needs to be
numeric).
+ *
+ * @since 1.20
+ * @deprecated since 1.22, use IORMTable->addToField
+ *
+ * @param string $field
+ * @param int $amount
+ *
+ * @return bool Success indicator
+ */
+ public function addToField( $field, $amount ) {
+ return $this->table->addToField( $this->getUpdateConditions(),
$field, $amount );
+ }
+
+ /**
+ * Return the names of the fields.
+ *
+ * @since 1.20
+ * @deprecated since 1.22
+ *
+ * @return array
+ */
+ public function getFieldNames() {
+ return array_keys( $this->table->getFields() );
+ }
+
+ /**
+ * Computes and updates the values of the summary fields.
+ *
+ * @since 1.20
+ * @deprecated since 1.22
+ *
+ * @param array|string|null $summaryFields
+ */
+ public function loadSummaryFields( $summaryFields = null ) {
+ }
+
+ /**
+ * Sets the value for the @see $updateSummaries field.
+ *
+ * @since 1.20
+ * @deprecated since 1.22
+ *
+ * @param bool $update
+ */
+ public function setUpdateSummaries( $update ) {
+ $this->updateSummaries = $update;
+ }
+
+ /**
+ * Sets the value for the @see $inSummaryMode field.
+ *
+ * @since 1.20
+ * @deprecated since 1.22
+ *
+ * @param bool $summaryMode
+ */
+ public function setSummaryMode( $summaryMode ) {
+ $this->inSummaryMode = $summaryMode;
+ }
+
+ /**
+ * Returns the table this IORMRow is a row in.
+ *
+ * @since 1.20
+ * @deprecated since 1.22
+ *
+ * @return IORMTable
+ */
+ public function getTable() {
+ return $this->table;
+ }
+}
diff --git a/includes/rows/RevisionedObject.php
b/includes/rows/RevisionedObject.php
index df9b5fa..36e7ddc 100644
--- a/includes/rows/RevisionedObject.php
+++ b/includes/rows/RevisionedObject.php
@@ -14,7 +14,7 @@
* @licence GNU GPL v2+
* @author Jeroen De Dauw < [email protected] >
*/
-abstract class RevisionedObject extends \ORMRow {
+abstract class RevisionedObject extends ORMRow {
/**
* If the object should log changes.
* Can be changed via disableLogging and enableLogging.
diff --git a/includes/tables/CAs.php b/includes/tables/CAs.php
index 9ea3292..56f6ac2 100644
--- a/includes/tables/CAs.php
+++ b/includes/tables/CAs.php
@@ -12,7 +12,7 @@
* @licence GNU GPL v2+
* @author Jeroen De Dauw < [email protected] >
*/
-class CAs extends \ORMTable {
+class CAs extends ORMTable {
public function __construct() {
$this->fieldPrefix = 'ca_';
diff --git a/includes/tables/Events.php b/includes/tables/Events.php
index e770cef..9b489b9 100644
--- a/includes/tables/Events.php
+++ b/includes/tables/Events.php
@@ -12,7 +12,7 @@
* @licence GNU GPL v2+
* @author Jeroen De Dauw < [email protected] >
*/
-class Events extends \ORMTable {
+class Events extends ORMTable {
public function __construct() {
$this->fieldPrefix = 'event_';
diff --git a/includes/tables/IORMTable.php b/includes/tables/IORMTable.php
new file mode 100644
index 0000000..be6d69b
--- /dev/null
+++ b/includes/tables/IORMTable.php
@@ -0,0 +1,522 @@
+<?php
+namespace EducationProgram;
+
+/**
+ * Interface for objects representing a single database table.
+ * Documentation inline and at https://www.mediawiki.org/wiki/Manual:ORMTable
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @since 1.20
+ *
+ * @file
+ * @ingroup ORM
+ *
+ * @license GNU GPL v2 or later
+ * @author Jeroen De Dauw < [email protected] >
+ */
+
+interface IORMTable {
+ /**
+ * Returns the name of the database table objects of this type are
stored in.
+ *
+ * @since 1.20
+ *
+ * @return string
+ */
+ public function getName();
+
+ /**
+ * Returns the name of a IORMRow implementing class that
+ * represents single rows in this table.
+ *
+ * @since 1.20
+ *
+ * @return string
+ */
+ public function getRowClass();
+
+ /**
+ * Returns an array with the fields and their types this object
contains.
+ * This corresponds directly to the fields in the database, without
prefix.
+ *
+ * field name => type
+ *
+ * Allowed types:
+ * * id
+ * * str
+ * * int
+ * * float
+ * * bool
+ * * array
+ * * blob
+ *
+ * @todo Get rid of the id field. Every row instance needs to have one
so
+ * this is just causing hassle at various locations by requiring an
extra
+ * check for field name.
+ *
+ * @since 1.20
+ *
+ * @return array
+ */
+ public function getFields();
+
+ /**
+ * Returns a list of default field values.
+ * field name => field value
+ *
+ * @since 1.20
+ *
+ * @return array
+ */
+ public function getDefaults();
+
+ /**
+ * Returns a list of the summary fields.
+ * These are fields that cache computed values, such as the amount of
linked objects of $type.
+ * This is relevant as one might not want to do actions such as log
changes when these get updated.
+ *
+ * @since 1.20
+ *
+ * @return array
+ */
+ public function getSummaryFields();
+
+ /**
+ * Selects the specified fields of the records matching the provided
+ * conditions and returns them as DBDataObject. Field names get
prefixed.
+ *
+ * @see DatabaseBase::select()
+ *
+ * @since 1.20
+ *
+ * @param array|string|null $fields
+ * @param array $conditions
+ * @param array $options
+ * @param string|null $functionName
+ *
+ * @return ORMResult The result set
+ * @throws \DBQueryError If the query failed (even if the database was
in ignoreErrors mode)
+ */
+ public function select( $fields = null, array $conditions = array(),
+ array $options = array(), $functionName = null );
+
+ /**
+ * Selects the specified fields of the records matching the provided
+ * conditions and returns them as DBDataObject. Field names get
prefixed.
+ *
+ * @since 1.20
+ *
+ * @param array|string|null $fields
+ * @param array $conditions
+ * @param array $options
+ * @param string|null $functionName
+ *
+ * @return array Array of self
+ */
+ public function selectObjects( $fields = null, array $conditions =
array(),
+ array $options = array(), $functionName = null );
+
+ /**
+ * Do the actual select.
+ *
+ * @since 1.20
+ *
+ * @param null|string|array $fields
+ * @param array $conditions
+ * @param array $options
+ * @param null|string $functionName
+ *
+ * @return \ResultWrapper
+ * @throws \DBQueryError If the query failed (even if the database was
in ignoreErrors mode)
+ */
+ public function rawSelect( $fields = null, array $conditions = array(),
+ array $options = array(), $functionName = null );
+
+ /**
+ * Selects the specified fields of the records matching the provided
+ * conditions and returns them as associative arrays.
+ * Provided field names get prefixed.
+ * Returned field names will not have a prefix.
+ *
+ * When $collapse is true:
+ * If one field is selected, each item in the result array will be this
field.
+ * If two fields are selected, each item in the result array will have
as key
+ * the first field and as value the second field.
+ * If more then two fields are selected, each item will be an
associative array.
+ *
+ * @since 1.20
+ *
+ * @param array|string|null $fields
+ * @param array $conditions
+ * @param array $options
+ * @param bool $collapse Set to false to always return each result row
as associative array.
+ * @param string|null $functionName
+ *
+ * @return array Array of array
+ */
+ public function selectFields( $fields = null, array $conditions =
array(),
+ array $options = array(), $collapse = true, $functionName =
null );
+
+ /**
+ * Selects the specified fields of the first matching record.
+ * Field names get prefixed.
+ *
+ * @since 1.20
+ *
+ * @param array|string|null $fields
+ * @param array $conditions
+ * @param array $options
+ * @param string|null $functionName
+ *
+ * @return IORMRow|bool False on failure
+ */
+ public function selectRow( $fields = null, array $conditions = array(),
+ array $options = array(), $functionName = null );
+
+ /**
+ * Selects the specified fields of the records matching the provided
+ * conditions. Field names do NOT get prefixed.
+ *
+ * @since 1.20
+ *
+ * @param array $fields
+ * @param array $conditions
+ * @param array $options
+ * @param string|null $functionName
+ *
+ * @return \ResultWrapper
+ */
+ public function rawSelectRow( array $fields, array $conditions =
array(),
+ array $options = array(), $functionName = null );
+
+ /**
+ * Selects the specified fields of the first record matching the
provided
+ * conditions and returns it as an associative array, or false when
nothing matches.
+ * This method makes use of selectFields and expects the same
parameters and
+ * returns the same results (if there are any, if there are none, this
method returns false).
+ * @see IORMTable::selectFields
+ *
+ * @since 1.20
+ *
+ * @param array|string|null $fields
+ * @param array $conditions
+ * @param array $options
+ * @param bool $collapse Set to false to always return each result row
as associative array.
+ * @param string|null $functionName
+ *
+ * @return mixed|array|bool False on failure
+ */
+ public function selectFieldsRow( $fields = null, array $conditions =
array(),
+ array $options = array(), $collapse = true, $functionName =
null );
+
+ /**
+ * Returns if there is at least one record matching the provided
conditions.
+ * Condition field names get prefixed.
+ *
+ * @since 1.20
+ *
+ * @param array $conditions
+ *
+ * @return bool
+ */
+ public function has( array $conditions = array() );
+
+ /**
+ * Checks if the table exists
+ *
+ * @since 1.21
+ *
+ * @return bool
+ */
+ public function exists();
+
+ /**
+ * Returns the amount of matching records.
+ * Condition field names get prefixed.
+ *
+ * Note that this can be expensive on large tables.
+ * In such cases you might want to use DatabaseBase::estimateRowCount
instead.
+ *
+ * @since 1.20
+ *
+ * @param array $conditions
+ * @param array $options
+ *
+ * @return int
+ */
+ public function count( array $conditions = array(), array $options =
array() );
+
+ /**
+ * Removes the object from the database.
+ *
+ * @since 1.20
+ *
+ * @param array $conditions
+ * @param string|null $functionName
+ *
+ * @return bool Success indicator
+ */
+ public function delete( array $conditions, $functionName = null );
+
+ /**
+ * Get API parameters for the fields supported by this object.
+ *
+ * @since 1.20
+ *
+ * @param bool $requireParams
+ * @param bool $setDefaults
+ *
+ * @return array
+ */
+ public function getAPIParams( $requireParams = false, $setDefaults =
false );
+
+ /**
+ * Returns an array with the fields and their descriptions.
+ *
+ * field name => field description
+ *
+ * @since 1.20
+ *
+ * @return array
+ */
+ public function getFieldDescriptions();
+
+ /**
+ * Get the database type used for read operations.
+ *
+ * @since 1.20
+ *
+ * @return int DB_ enum
+ */
+ public function getReadDb();
+
+ /**
+ * Set the database type to use for read operations.
+ *
+ * @param int $db
+ *
+ * @since 1.20
+ */
+ public function setReadDb( $db );
+
+ /**
+ * Get the ID of the any foreign wiki to use as a target for database
operations
+ *
+ * @since 1.20
+ *
+ * @return string|bool The target wiki, in a form that LBFactory
+ * understands (or false if the local wiki is used)
+ */
+ public function getTargetWiki();
+
+ /**
+ * Set the ID of the any foreign wiki to use as a target for database
operations
+ *
+ * @param string|bool $wiki The target wiki, in a form that LBFactory
+ * understands (or false if the local wiki shall be used)
+ *
+ * @since 1.20
+ */
+ public function setTargetWiki( $wiki );
+
+ /**
+ * Get the database type used for read operations.
+ * This is to be used instead of wfGetDB.
+ *
+ * @see LoadBalancer::getConnection
+ *
+ * @since 1.20
+ *
+ * @return \DatabaseBase The database object
+ */
+ public function getReadDbConnection();
+
+ /**
+ * Get the database type used for read operations.
+ * This is to be used instead of wfGetDB.
+ *
+ * @see LoadBalancer::getConnection
+ *
+ * @since 1.20
+ *
+ * @return \DatabaseBase The database object
+ */
+ public function getWriteDbConnection();
+
+ /**
+ * Get the database type used for read operations.
+ *
+ * @see wfGetLB
+ *
+ * @since 1.20
+ *
+ * @return \LoadBalancer The database load balancer object
+ */
+ public function getLoadBalancer();
+
+ /**
+ * Releases the lease on the given database connection. This is useful
mainly
+ * for connections to a foreign wiki. It does nothing for connections
to the local wiki.
+ *
+ * @see LoadBalancer::reuseConnection
+ *
+ * @param \DatabaseBase $db The database
+ *
+ * @since 1.20
+ */
+ public function releaseConnection( \DatabaseBase $db );
+
+ /**
+ * Update the records matching the provided conditions by
+ * setting the fields that are keys in the $values param to
+ * their corresponding values.
+ *
+ * @since 1.20
+ *
+ * @param array $values
+ * @param array $conditions
+ *
+ * @return bool Success indicator
+ */
+ public function update( array $values, array $conditions = array() );
+
+ /**
+ * Computes the values of the summary fields of the objects matching
the provided conditions.
+ *
+ * @since 1.20
+ *
+ * @param array|string|null $summaryFields
+ * @param array $conditions
+ */
+ public function updateSummaryFields( $summaryFields = null, array
$conditions = array() );
+
+ /**
+ * Takes in an associative array with field names as keys and
+ * their values as value. The field names are prefixed with the
+ * db field prefix.
+ *
+ * @since 1.20
+ *
+ * @param array $values
+ *
+ * @return array
+ */
+ public function getPrefixedValues( array $values );
+
+ /**
+ * Takes in a field or array of fields and returns an
+ * array with their prefixed versions, ready for db usage.
+ *
+ * @since 1.20
+ *
+ * @param array $fields
+ *
+ * @return array
+ */
+ public function getPrefixedFields( array $fields );
+
+ /**
+ * Takes in a field and returns an it's prefixed version, ready for db
usage.
+ *
+ * @since 1.20
+ *
+ * @param string|array $field
+ *
+ * @return string
+ */
+ public function getPrefixedField( $field );
+
+ /**
+ * Takes an array of field names with prefix and returns the unprefixed
equivalent.
+ *
+ * @since 1.20
+ * @deprecated since 1.25, will be removed
+ *
+ * @param string[] $fieldNames
+ *
+ * @return string[]
+ */
+ public function unprefixFieldNames( array $fieldNames );
+
+ /**
+ * Takes a field name with prefix and returns the unprefixed equivalent.
+ *
+ * @since 1.20
+ * @deprecated since 1.25, will be removed
+ *
+ * @param string $fieldName
+ *
+ * @return string
+ */
+ public function unprefixFieldName( $fieldName );
+
+ /**
+ * Get an array with fields from a database result,
+ * that can be fed directly to the constructor or
+ * to setFields.
+ *
+ * @since 1.20
+ *
+ * @param stdClass $result
+ *
+ * @return array
+ */
+ public function getFieldsFromDBResult( stdClass $result );
+
+ /**
+ * Get a new instance of the class from a database result.
+ *
+ * @since 1.20
+ *
+ * @param stdClass $result
+ *
+ * @return IORMRow
+ */
+ public function newRowFromDBResult( stdClass $result );
+
+ /**
+ * Get a new instance of the class from an array.
+ *
+ * @since 1.20
+ *
+ * @param array $data
+ * @param bool $loadDefaults
+ *
+ * @return IORMRow
+ */
+ public function newRow( array $data, $loadDefaults = false );
+
+ /**
+ * Return the names of the fields.
+ *
+ * @since 1.20
+ *
+ * @return array
+ */
+ public function getFieldNames();
+
+ /**
+ * Gets if the object can take a certain field.
+ *
+ * @since 1.20
+ *
+ * @param string $name
+ *
+ * @return bool
+ */
+ public function canHaveField( $name );
+}
diff --git a/includes/tables/Instructors.php b/includes/tables/Instructors.php
index 2d5e18e..dbc9c3b 100644
--- a/includes/tables/Instructors.php
+++ b/includes/tables/Instructors.php
@@ -12,7 +12,7 @@
* @licence GNU GPL v2+
* @author Jeroen De Dauw < [email protected] >
*/
-class Instructors extends \ORMTable {
+class Instructors extends ORMTable {
public function __construct() {
$this->fieldPrefix = 'instructor_';
diff --git a/includes/tables/OAs.php b/includes/tables/OAs.php
index 88796f6..02a5d04 100644
--- a/includes/tables/OAs.php
+++ b/includes/tables/OAs.php
@@ -12,7 +12,7 @@
* @licence GNU GPL v2+
* @author Jeroen De Dauw < [email protected] >
*/
-class OAs extends \ORMTable {
+class OAs extends ORMTable {
public function __construct() {
$this->fieldPrefix = 'oa_';
diff --git a/includes/tables/ORMTable.php b/includes/tables/ORMTable.php
new file mode 100644
index 0000000..a58bd67
--- /dev/null
+++ b/includes/tables/ORMTable.php
@@ -0,0 +1,1147 @@
+<?php
+
+namespace EducationProgram;
+
+/**
+ * Abstract base class for representing a single database table.
+ * Documentation inline and at https://www.mediawiki.org/wiki/Manual:ORMTable
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @since 1.20
+ * Non-abstract since 1.21
+ *
+ * @file ORMTable.php
+ * @ingroup ORM
+ *
+ * @license GNU GPL v2 or later
+ * @author Jeroen De Dauw < [email protected] >
+ */
+
+class ORMTable extends \DBAccessBase implements IORMTable {
+ /**
+ * Cache for instances, used by the singleton method.
+ *
+ * @since 1.20
+ * @deprecated since 1.21
+ *
+ * @var ORMTable[]
+ */
+ protected static $instanceCache = array();
+
+ /**
+ * @since 1.21
+ *
+ * @var string
+ */
+ protected $tableName;
+
+ /**
+ * @since 1.21
+ *
+ * @var string[]
+ */
+ protected $fields = array();
+
+ /**
+ * @since 1.21
+ *
+ * @var string
+ */
+ protected $fieldPrefix = '';
+
+ /**
+ * @since 1.21
+ *
+ * @var string
+ */
+ protected $rowClass = 'ORMRow';
+
+ /**
+ * @since 1.21
+ *
+ * @var array
+ */
+ protected $defaults = array();
+
+ /**
+ * ID of the database connection to use for read operations.
+ * Can be changed via @see setReadDb.
+ *
+ * @since 1.20
+ *
+ * @var int DB_ enum
+ */
+ protected $readDb = DB_SLAVE;
+
+ /**
+ * Constructor.
+ *
+ * @since 1.21
+ *
+ * @param string $tableName
+ * @param string[] $fields
+ * @param array $defaults
+ * @param string|null $rowClass
+ * @param string $fieldPrefix
+ */
+ public function __construct( $tableName = '', array $fields = array(),
+ array $defaults = array(), $rowClass = null, $fieldPrefix = ''
+ ) {
+ $this->tableName = $tableName;
+ $this->fields = $fields;
+ $this->defaults = $defaults;
+
+ if ( is_string( $rowClass ) ) {
+ $this->rowClass = $rowClass;
+ }
+
+ $this->fieldPrefix = $fieldPrefix;
+ }
+
+ /**
+ * @see IORMTable::getName
+ *
+ * @since 1.21
+ *
+ * @return string
+ * @throws \MWException
+ */
+ public function getName() {
+ if ( $this->tableName === '' ) {
+ throw new \MWException( 'The table name needs to be
set' );
+ }
+
+ return $this->tableName;
+ }
+
+ /**
+ * Gets the db field prefix.
+ *
+ * @since 1.20
+ * @deprecated since 1.25, use the $this->fieldPrefix property instead
+ *
+ * @return string
+ */
+ protected function getFieldPrefix() {
+ return $this->fieldPrefix;
+ }
+
+ /**
+ * @see IORMTable::getRowClass
+ *
+ * @since 1.21
+ *
+ * @return string
+ */
+ public function getRowClass() {
+ return $this->rowClass;
+ }
+
+ /**
+ * @see ORMTable::getFields
+ *
+ * @since 1.21
+ *
+ * @return array
+ * @throws \MWException
+ */
+ public function getFields() {
+ if ( $this->fields === array() ) {
+ throw new \MWException( 'The table needs to have one or
more fields' );
+ }
+
+ return $this->fields;
+ }
+
+ /**
+ * Returns a list of default field values.
+ * field name => field value
+ *
+ * @since 1.20
+ *
+ * @return array
+ */
+ public function getDefaults() {
+ return $this->defaults;
+ }
+
+ /**
+ * Returns a list of the summary fields.
+ * These are fields that cache computed values, such as the amount of
linked objects of $type.
+ * This is relevant as one might not want to do actions such as log
changes when these get updated.
+ *
+ * @since 1.20
+ *
+ * @return array
+ */
+ public function getSummaryFields() {
+ return array();
+ }
+
+ /**
+ * Selects the specified fields of the records matching the provided
+ * conditions and returns them as DBDataObject. Field names get
prefixed.
+ *
+ * @since 1.20
+ *
+ * @param array|string|null $fields
+ * @param array $conditions
+ * @param array $options
+ * @param string|null $functionName
+ *
+ * @return ORMResult
+ */
+ public function select( $fields = null, array $conditions = array(),
+ array $options = array(), $functionName = null
+ ) {
+ $res = $this->rawSelect( $fields, $conditions, $options,
$functionName );
+
+ return new ORMResult( $this, $res );
+ }
+
+ /**
+ * Selects the specified fields of the records matching the provided
+ * conditions and returns them as DBDataObject. Field names get
prefixed.
+ *
+ * @since 1.20
+ *
+ * @param array|string|null $fields
+ * @param array $conditions
+ * @param array $options
+ * @param string|null $functionName
+ *
+ * @return array Array of row objects
+ * @throws \DBQueryError If the query failed (even if the database was
in ignoreErrors mode).
+ */
+ public function selectObjects( $fields = null, array $conditions =
array(),
+ array $options = array(), $functionName = null
+ ) {
+ $result = $this->selectFields( $fields, $conditions, $options,
false, $functionName );
+
+ $objects = array();
+
+ foreach ( $result as $record ) {
+ $objects[] = $this->newRow( $record );
+ }
+
+ return $objects;
+ }
+
+ /**
+ * Do the actual select.
+ *
+ * @since 1.20
+ *
+ * @param null|string|array $fields
+ * @param array $conditions
+ * @param array $options
+ * @param null|string $functionName
+ * @return \ResultWrapper
+ * @throws \Exception
+ * @throws \MWException
+ */
+ public function rawSelect( $fields = null, array $conditions = array(),
+ array $options = array(), $functionName = null
+ ) {
+ if ( is_null( $fields ) ) {
+ $fields = array_keys( $this->getFields() );
+ } else {
+ $fields = (array)$fields;
+ }
+
+ $dbr = $this->getReadDbConnection();
+ $result = $dbr->select(
+ $this->getName(),
+ $this->getPrefixedFields( $fields ),
+ $this->getPrefixedValues( $conditions ),
+ is_null( $functionName ) ? __METHOD__ : $functionName,
+ $options
+ );
+
+ /* @var \Exception $error */
+ $error = null;
+
+ if ( $result === false ) {
+ // Database connection was in "ignoreErrors" mode. We
don't like that.
+ // So, we emulate the DBQueryError that should have
been thrown.
+ $error = new \DBQueryError(
+ $dbr,
+ $dbr->lastError(),
+ $dbr->lastErrno(),
+ $dbr->lastQuery(),
+ is_null( $functionName ) ? __METHOD__ :
$functionName
+ );
+ }
+
+ $this->releaseConnection( $dbr );
+
+ if ( $error ) {
+ // Note: construct the error before releasing the
connection,
+ // but throw it after.
+ throw $error;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Selects the specified fields of the records matching the provided
+ * conditions and returns them as associative arrays.
+ * Provided field names get prefixed.
+ * Returned field names will not have a prefix.
+ *
+ * When $collapse is true:
+ * If one field is selected, each item in the result array will be this
field.
+ * If two fields are selected, each item in the result array will have
as key
+ * the first field and as value the second field.
+ * If more then two fields are selected, each item will be an
associative array.
+ *
+ * @since 1.20
+ *
+ * @param array|string|null $fields
+ * @param array $conditions
+ * @param array $options
+ * @param bool $collapse Set to false to always return each result row
as associative array.
+ * @param string|null $functionName
+ *
+ * @return array Array of array
+ */
+ public function selectFields( $fields = null, array $conditions =
array(),
+ array $options = array(), $collapse = true, $functionName = null
+ ) {
+ $objects = array();
+
+ $result = $this->rawSelect( $fields, $conditions, $options,
$functionName );
+
+ foreach ( $result as $record ) {
+ $objects[] = $this->getFieldsFromDBResult( $record );
+ }
+
+ if ( $collapse ) {
+ if ( count( $fields ) === 1 ) {
+ $objects = array_map( 'array_shift', $objects );
+ } elseif ( count( $fields ) === 2 ) {
+ $o = array();
+
+ foreach ( $objects as $object ) {
+ $o[array_shift( $object )] =
array_shift( $object );
+ }
+
+ $objects = $o;
+ }
+ }
+
+ return $objects;
+ }
+
+ /**
+ * Selects the specified fields of the first matching record.
+ * Field names get prefixed.
+ *
+ * @since 1.20
+ *
+ * @param array|string|null $fields
+ * @param array $conditions
+ * @param array $options
+ * @param string|null $functionName
+ *
+ * @return IORMRow|bool False on failure
+ */
+ public function selectRow( $fields = null, array $conditions = array(),
+ array $options = array(), $functionName = null
+ ) {
+ $options['LIMIT'] = 1;
+
+ $objects = $this->select( $fields, $conditions, $options,
$functionName );
+
+ return ( !$objects || $objects->isEmpty() ) ? false :
$objects->current();
+ }
+
+ /**
+ * Selects the specified fields of the records matching the provided
+ * conditions. Field names do NOT get prefixed.
+ *
+ * @since 1.20
+ *
+ * @param array $fields
+ * @param array $conditions
+ * @param array $options
+ * @param string|null $functionName
+ *
+ * @return stdClass
+ */
+ public function rawSelectRow( array $fields, array $conditions =
array(),
+ array $options = array(), $functionName = null
+ ) {
+ $dbr = $this->getReadDbConnection();
+
+ $result = $dbr->selectRow(
+ $this->getName(),
+ $fields,
+ $conditions,
+ is_null( $functionName ) ? __METHOD__ : $functionName,
+ $options
+ );
+
+ $this->releaseConnection( $dbr );
+
+ return $result;
+ }
+
+ /**
+ * Selects the specified fields of the first record matching the
provided
+ * conditions and returns it as an associative array, or false when
nothing matches.
+ * This method makes use of selectFields and expects the same
parameters and
+ * returns the same results (if there are any, if there are none, this
method returns false).
+ * @see ORMTable::selectFields
+ *
+ * @since 1.20
+ *
+ * @param array|string|null $fields
+ * @param array $conditions
+ * @param array $options
+ * @param bool $collapse Set to false to always return each result row
as associative array.
+ * @param string|null $functionName
+ *
+ * @return mixed|array|bool False on failure
+ */
+ public function selectFieldsRow( $fields = null, array $conditions =
array(),
+ array $options = array(), $collapse = true, $functionName = null
+ ) {
+ $options['LIMIT'] = 1;
+
+ $objects = $this->selectFields( $fields, $conditions, $options,
$collapse, $functionName );
+
+ return empty( $objects ) ? false : $objects[0];
+ }
+
+ /**
+ * Returns if there is at least one record matching the provided
conditions.
+ * Condition field names get prefixed.
+ *
+ * @since 1.20
+ *
+ * @param array $conditions
+ *
+ * @return bool
+ */
+ public function has( array $conditions = array() ) {
+ return $this->selectRow( array( 'id' ), $conditions ) !== false;
+ }
+
+ /**
+ * Checks if the table exists
+ *
+ * @since 1.21
+ *
+ * @return bool
+ */
+ public function exists() {
+ $dbr = $this->getReadDbConnection();
+ $exists = $dbr->tableExists( $this->getName() );
+ $this->releaseConnection( $dbr );
+
+ return $exists;
+ }
+
+ /**
+ * Returns the amount of matching records.
+ * Condition field names get prefixed.
+ *
+ * Note that this can be expensive on large tables.
+ * In such cases you might want to use DatabaseBase::estimateRowCount
instead.
+ *
+ * @since 1.20
+ *
+ * @param array $conditions
+ * @param array $options
+ *
+ * @return int
+ */
+ public function count( array $conditions = array(), array $options =
array() ) {
+ $res = $this->rawSelectRow(
+ array( 'rowcount' => 'COUNT(*)' ),
+ $this->getPrefixedValues( $conditions ),
+ $options,
+ __METHOD__
+ );
+
+ return $res->rowcount;
+ }
+
+ /**
+ * Removes the object from the database.
+ *
+ * @since 1.20
+ *
+ * @param array $conditions
+ * @param string|null $functionName
+ *
+ * @return bool Success indicator
+ */
+ public function delete( array $conditions, $functionName = null ) {
+ $dbw = $this->getWriteDbConnection();
+
+ $result = $dbw->delete(
+ $this->getName(),
+ $conditions === array() ? '*' :
$this->getPrefixedValues( $conditions ),
+ is_null( $functionName ) ? __METHOD__ : $functionName
+ ) !== false; // DatabaseBase::delete does not always return
true for success as documented...
+
+ $this->releaseConnection( $dbw );
+
+ return $result;
+ }
+
+ /**
+ * Get API parameters for the fields supported by this object.
+ *
+ * @since 1.20
+ *
+ * @param bool $requireParams
+ * @param bool $setDefaults
+ *
+ * @return array
+ */
+ public function getAPIParams( $requireParams = false, $setDefaults =
false ) {
+ $typeMap = array(
+ 'id' => 'integer',
+ 'int' => 'integer',
+ 'float' => 'NULL',
+ 'str' => 'string',
+ 'bool' => 'integer',
+ 'array' => 'string',
+ 'blob' => 'string',
+ );
+
+ $params = array();
+ $defaults = $this->getDefaults();
+
+ foreach ( $this->getFields() as $field => $type ) {
+ if ( $field == 'id' ) {
+ continue;
+ }
+
+ $hasDefault = array_key_exists( $field, $defaults );
+
+ $params[$field] = array(
+ \ApiBase::PARAM_TYPE => $typeMap[$type],
+ \ApiBase::PARAM_REQUIRED => $requireParams &&
!$hasDefault
+ );
+
+ if ( $type == 'array' ) {
+ $params[$field][\ApiBase::PARAM_ISMULTI] = true;
+ }
+
+ if ( $setDefaults && $hasDefault ) {
+ $default = is_array( $defaults[$field] )
+ ? implode( '|', $defaults[$field] )
+ : $defaults[$field];
+ $params[$field][\ApiBase::PARAM_DFLT] =
$default;
+ }
+ }
+
+ return $params;
+ }
+
+ /**
+ * Returns an array with the fields and their descriptions.
+ *
+ * field name => field description
+ *
+ * @since 1.20
+ *
+ * @return array
+ */
+ public function getFieldDescriptions() {
+ return array();
+ }
+
+ /**
+ * Get the database ID used for read operations.
+ *
+ * @since 1.20
+ *
+ * @return int DB_ enum
+ */
+ public function getReadDb() {
+ return $this->readDb;
+ }
+
+ /**
+ * Set the database ID to use for read operations, use DB_XXX constants
or
+ * an index to the load balancer setup.
+ *
+ * @param int $db
+ *
+ * @since 1.20
+ */
+ public function setReadDb( $db ) {
+ $this->readDb = $db;
+ }
+
+ /**
+ * Get the ID of the any foreign wiki to use as a target for database
operations
+ *
+ * @since 1.20
+ *
+ * @return string|bool The target wiki, in a form that LBFactory
understands
+ * (or false if the local wiki is used)
+ */
+ public function getTargetWiki() {
+ return $this->wiki;
+ }
+
+ /**
+ * Set the ID of the any foreign wiki to use as a target for database
operations
+ *
+ * @param string|bool $wiki The target wiki, in a form that LBFactory
+ * understands (or false if the local wiki shall be used)
+ *
+ * @since 1.20
+ */
+ public function setTargetWiki( $wiki ) {
+ $this->wiki = $wiki;
+ }
+
+ /**
+ * Get the database type used for read operations.
+ * This is to be used instead of wfGetDB.
+ *
+ * @see LoadBalancer::getConnection
+ *
+ * @since 1.20
+ *
+ * @return \DatabaseBase The database object
+ */
+ public function getReadDbConnection() {
+ return $this->getConnection( $this->getReadDb(), array() );
+ }
+
+ /**
+ * Get the database type used for read operations.
+ * This is to be used instead of wfGetDB.
+ *
+ * @see LoadBalancer::getConnection
+ *
+ * @since 1.20
+ *
+ * @return \DatabaseBase The database object
+ */
+ public function getWriteDbConnection() {
+ return $this->getConnection( DB_MASTER, array() );
+ }
+
+ /**
+ * Releases the lease on the given database connection. This is useful
mainly
+ * for connections to a foreign wiki. It does nothing for connections
to the local wiki.
+ *
+ * @see LoadBalancer::reuseConnection
+ *
+ * @param \DatabaseBase $db
+ *
+ * @since 1.20
+ */
+ // @codingStandardsIgnoreStart Suppress "useless method overriding"
sniffer warning
+ public function releaseConnection( \DatabaseBase $db ) {
+ parent::releaseConnection( $db ); // just make it public
+ }
+ // @codingStandardsIgnoreEnd
+
+ /**
+ * Update the records matching the provided conditions by
+ * setting the fields that are keys in the $values param to
+ * their corresponding values.
+ *
+ * @since 1.20
+ *
+ * @param array $values
+ * @param array $conditions
+ *
+ * @return bool Success indicator
+ */
+ public function update( array $values, array $conditions = array() ) {
+ $dbw = $this->getWriteDbConnection();
+
+ $result = $dbw->update(
+ $this->getName(),
+ $this->getPrefixedValues( $values ),
+ $this->getPrefixedValues( $conditions ),
+ __METHOD__
+ ) !== false; // DatabaseBase::update does not always return
true for success as documented...
+
+ $this->releaseConnection( $dbw );
+
+ return $result;
+ }
+
+ /**
+ * Computes the values of the summary fields of the objects matching
the provided conditions.
+ *
+ * @since 1.20
+ *
+ * @param array|string|null $summaryFields
+ * @param array $conditions
+ */
+ public function updateSummaryFields( $summaryFields = null, array
$conditions = array() ) {
+ $slave = $this->getReadDb();
+ $this->setReadDb( DB_MASTER );
+
+ /**
+ * @var IORMRow $item
+ */
+ foreach ( $this->select( null, $conditions ) as $item ) {
+ $item->loadSummaryFields( $summaryFields );
+ $item->setSummaryMode( true );
+ $item->save();
+ }
+
+ $this->setReadDb( $slave );
+ }
+
+ /**
+ * Takes in an associative array with field names as keys and
+ * their values as value. The field names are prefixed with the
+ * db field prefix.
+ *
+ * @since 1.20
+ *
+ * @param array $values
+ *
+ * @return array
+ */
+ public function getPrefixedValues( array $values ) {
+ $prefixedValues = array();
+
+ foreach ( $values as $field => $value ) {
+ if ( is_integer( $field ) ) {
+ if ( is_array( $value ) ) {
+ $field = $value[0];
+ $value = $value[1];
+ } else {
+ $value = explode( ' ', $value, 2 );
+ $value[0] = $this->getPrefixedField(
$value[0] );
+ $prefixedValues[] = implode( ' ',
$value );
+ continue;
+ }
+ }
+
+ $prefixedValues[$this->getPrefixedField( $field )] =
$value;
+ }
+
+ return $prefixedValues;
+ }
+
+ /**
+ * Takes in a field or array of fields and returns an
+ * array with their prefixed versions, ready for db usage.
+ *
+ * @since 1.20
+ *
+ * @param array $fields
+ *
+ * @return array
+ */
+ public function getPrefixedFields( array $fields ) {
+ foreach ( $fields as &$field ) {
+ $field = $this->getPrefixedField( $field );
+ }
+
+ return $fields;
+ }
+
+ /**
+ * Takes in a field and returns an it's prefixed version, ready for db
usage.
+ *
+ * @since 1.20
+ *
+ * @param string|array $field
+ *
+ * @return string
+ */
+ public function getPrefixedField( $field ) {
+ return $this->fieldPrefix . $field;
+ }
+
+ /**
+ * Takes an array of field names with prefix and returns the unprefixed
equivalent.
+ *
+ * @since 1.20
+ * @deprecated since 1.25, will be removed
+ *
+ * @param string[] $fieldNames
+ *
+ * @return string[]
+ */
+ public function unprefixFieldNames( array $fieldNames ) {
+ wfDeprecated( __METHOD__, '1.25' );
+
+ return $this->stripFieldPrefix( $fieldNames );
+ }
+
+ /**
+ * Takes an array of field names with prefix and returns the unprefixed
equivalent.
+ *
+ * @param string[] $fieldNames
+ *
+ * @return string[]
+ */
+ private function stripFieldPrefix( array $fieldNames ) {
+ $start = strlen( $this->fieldPrefix );
+
+ return array_map( function ( $fieldName ) use ( $start ) {
+ return substr( $fieldName, $start );
+ }, $fieldNames );
+ }
+
+ /**
+ * Takes a field name with prefix and returns the unprefixed equivalent.
+ *
+ * @since 1.20
+ * @deprecated since 1.25, will be removed
+ *
+ * @param string $fieldName
+ *
+ * @return string
+ */
+ public function unprefixFieldName( $fieldName ) {
+ wfDeprecated( __METHOD__, '1.25' );
+
+ return substr( $fieldName, strlen( $this->fieldPrefix ) );
+ }
+
+ /**
+ * Get an instance of this class.
+ *
+ * @since 1.20
+ * @deprecated since 1.21
+ *
+ * @return IORMTable
+ */
+ public static function singleton() {
+ $class = get_called_class();
+
+ if ( !array_key_exists( $class, self::$instanceCache ) ) {
+ self::$instanceCache[$class] = new $class;
+ }
+
+ return self::$instanceCache[$class];
+ }
+
+ /**
+ * Get an array with fields from a database result,
+ * that can be fed directly to the constructor or
+ * to setFields.
+ *
+ * @since 1.20
+ *
+ * @param stdClass $result
+ * @throws \MWException
+ * @return array
+ */
+ public function getFieldsFromDBResult( stdClass $result ) {
+ $result = (array)$result;
+
+ $rawFields = array_combine(
+ $this->stripFieldPrefix( array_keys( $result ) ),
+ array_values( $result )
+ );
+
+ $fieldDefinitions = $this->getFields();
+ $fields = array();
+
+ foreach ( $rawFields as $name => $value ) {
+ if ( array_key_exists( $name, $fieldDefinitions ) ) {
+ switch ( $fieldDefinitions[$name] ) {
+ case 'int':
+ $value = (int)$value;
+ break;
+ case 'float':
+ $value = (float)$value;
+ break;
+ case 'bool':
+ if ( is_string( $value ) ) {
+ $value = $value !== '0';
+ } elseif ( is_int( $value ) ) {
+ $value = $value !== 0;
+ }
+ break;
+ case 'array':
+ if ( is_string( $value ) ) {
+ $value = unserialize(
$value );
+ }
+
+ if ( !is_array( $value ) ) {
+ $value = array();
+ }
+ break;
+ case 'blob':
+ if ( is_string( $value ) ) {
+ $value = unserialize(
$value );
+ }
+ break;
+ case 'id':
+ if ( is_string( $value ) ) {
+ $value = (int)$value;
+ }
+ break;
+ }
+
+ $fields[$name] = $value;
+ } else {
+ throw new \MWException( 'Attempted to set
unknown field ' . $name );
+ }
+ }
+
+ return $fields;
+ }
+
+ /**
+ * @see ORMTable::newRowFromFromDBResult
+ *
+ * @deprecated since 1.20 use newRowFromDBResult instead
+ * @since 1.20
+ *
+ * @param stdClass $result
+ *
+ * @return IORMRow
+ */
+ public function newFromDBResult( stdClass $result ) {
+ return self::newRowFromDBResult( $result );
+ }
+
+ /**
+ * Get a new instance of the class from a database result.
+ *
+ * @since 1.20
+ *
+ * @param stdClass $result
+ *
+ * @return IORMRow
+ */
+ public function newRowFromDBResult( stdClass $result ) {
+ return $this->newRow( $this->getFieldsFromDBResult( $result ) );
+ }
+
+ /**
+ * @see ORMTable::newRow
+ *
+ * @deprecated since 1.20 use newRow instead
+ * @since 1.20
+ *
+ * @param array $data
+ * @param bool $loadDefaults
+ *
+ * @return IORMRow
+ */
+ public function newFromArray( array $data, $loadDefaults = false ) {
+ return static::newRow( $data, $loadDefaults );
+ }
+
+ /**
+ * Get a new instance of the class from an array.
+ *
+ * @since 1.20
+ *
+ * @param array $fields
+ * @param bool $loadDefaults
+ *
+ * @return IORMRow
+ */
+ public function newRow( array $fields, $loadDefaults = false ) {
+ $class = $this->getRowClass();
+
+ return new $class( $this, $fields, $loadDefaults );
+ }
+
+ /**
+ * Return the names of the fields.
+ *
+ * @since 1.20
+ *
+ * @return array
+ */
+ public function getFieldNames() {
+ return array_keys( $this->getFields() );
+ }
+
+ /**
+ * Gets if the object can take a certain field.
+ *
+ * @since 1.20
+ *
+ * @param string $name
+ *
+ * @return bool
+ */
+ public function canHaveField( $name ) {
+ return array_key_exists( $name, $this->getFields() );
+ }
+
+ /**
+ * Updates the provided row in the database.
+ *
+ * @since 1.22
+ *
+ * @param IORMRow $row The row to save
+ * @param string|null $functionName
+ *
+ * @return bool Success indicator
+ */
+ public function updateRow( IORMRow $row, $functionName = null ) {
+ $dbw = $this->getWriteDbConnection();
+
+ $success = $dbw->update(
+ $this->getName(),
+ $this->getWriteValues( $row ),
+ $this->getPrefixedValues( array( 'id' => $row->getId()
) ),
+ is_null( $functionName ) ? __METHOD__ : $functionName
+ );
+
+ $this->releaseConnection( $dbw );
+
+ // DatabaseBase::update does not always return true for success
as documented...
+ return $success !== false;
+ }
+
+ /**
+ * Inserts the provided row into the database.
+ *
+ * @since 1.22
+ *
+ * @param IORMRow $row
+ * @param string|null $functionName
+ * @param array|null $options
+ *
+ * @return bool Success indicator
+ */
+ public function insertRow( IORMRow $row, $functionName = null, array
$options = null ) {
+ $dbw = $this->getWriteDbConnection();
+
+ $success = $dbw->insert(
+ $this->getName(),
+ $this->getWriteValues( $row ),
+ is_null( $functionName ) ? __METHOD__ : $functionName,
+ $options
+ );
+
+ // DatabaseBase::insert does not always return true for success
as documented...
+ $success = $success !== false;
+
+ if ( $success ) {
+ $row->setField( 'id', $dbw->insertId() );
+ }
+
+ $this->releaseConnection( $dbw );
+
+ return $success;
+ }
+
+ /**
+ * Gets the fields => values to write to the table.
+ *
+ * @since 1.22
+ *
+ * @param IORMRow $row
+ *
+ * @return array
+ */
+ protected function getWriteValues( IORMRow $row ) {
+ $values = array();
+
+ $rowFields = $row->getFields();
+
+ foreach ( $this->getFields() as $name => $type ) {
+ if ( array_key_exists( $name, $rowFields ) ) {
+ $value = $rowFields[$name];
+
+ switch ( $type ) {
+ case 'array':
+ $value = (array)$value;
+ // fall-through!
+ case 'blob':
+ $value = serialize( $value );
+ // fall-through!
+ }
+
+ $values[$this->getPrefixedField( $name )] =
$value;
+ }
+ }
+
+ return $values;
+ }
+
+ /**
+ * Removes the provided row from the database.
+ *
+ * @since 1.22
+ *
+ * @param IORMRow $row
+ * @param string|null $functionName
+ *
+ * @return bool Success indicator
+ */
+ public function removeRow( IORMRow $row, $functionName = null ) {
+ $success = $this->delete(
+ array( 'id' => $row->getId() ),
+ is_null( $functionName ) ? __METHOD__ : $functionName
+ );
+
+ // DatabaseBase::delete does not always return true for success
as documented...
+ return $success !== false;
+ }
+
+ /**
+ * Add an amount (can be negative) to the specified field (needs to be
numeric).
+ *
+ * @since 1.22
+ *
+ * @param array $conditions
+ * @param string $field
+ * @param int $amount
+ *
+ * @return bool Success indicator
+ * @throws \MWException
+ */
+ public function addToField( array $conditions, $field, $amount ) {
+ if ( !array_key_exists( $field, $this->fields ) ) {
+ throw new \MWException( 'Unknown field "' . $field . '"
provided' );
+ }
+
+ if ( $amount == 0 ) {
+ return true;
+ }
+
+ $absoluteAmount = abs( $amount );
+ $isNegative = $amount < 0;
+
+ $fullField = $this->getPrefixedField( $field );
+
+ $dbw = $this->getWriteDbConnection();
+
+ $success = $dbw->update(
+ $this->getName(),
+ array( "$fullField=$fullField" . ( $isNegative ? '-' :
'+' ) . $absoluteAmount ),
+ $this->getPrefixedValues( $conditions ),
+ __METHOD__
+ ) !== false; // DatabaseBase::update does not always return
true for success as documented...
+
+ $this->releaseConnection( $dbw );
+
+ return $success;
+ }
+}
diff --git a/includes/tables/PageTable.php b/includes/tables/PageTable.php
index 8ef917a..546e741 100644
--- a/includes/tables/PageTable.php
+++ b/includes/tables/PageTable.php
@@ -15,7 +15,7 @@
* @licence GNU GPL v2+
* @author Jeroen De Dauw < [email protected] >
*/
-abstract class PageTable extends \ORMTable {
+abstract class PageTable extends ORMTable {
/**
* Returns the field use to identify this object, ie the part used as
page title.
diff --git a/includes/tables/Revisions.php b/includes/tables/Revisions.php
index 5f647f5..e5461e6 100644
--- a/includes/tables/Revisions.php
+++ b/includes/tables/Revisions.php
@@ -12,7 +12,7 @@
* @licence GNU GPL v2+
* @author Jeroen De Dauw < [email protected] >
*/
-class Revisions extends \ORMTable {
+class Revisions extends ORMTable {
public function __construct() {
$this->fieldPrefix = 'rev_';
diff --git a/includes/tables/Students.php b/includes/tables/Students.php
index 91b3819..38567d4 100644
--- a/includes/tables/Students.php
+++ b/includes/tables/Students.php
@@ -12,7 +12,7 @@
* @licence GNU GPL v2+
* @author Jeroen De Dauw < [email protected] >
*/
-class Students extends \ORMTable {
+class Students extends ORMTable {
public function __construct() {
$this->fieldPrefix = 'student_';
diff --git a/tests/phpunit/db/ORMRowTest.php b/tests/phpunit/db/ORMRowTest.php
new file mode 100644
index 0000000..db65342
--- /dev/null
+++ b/tests/phpunit/db/ORMRowTest.php
@@ -0,0 +1,229 @@
+<?php
+
+namespace EducationProgram\Tests;
+use EducationProgram\IORMRow;
+use EducationProgram\IORMTable;
+
+/**
+ * Abstract class to construct tests for ORMRow deriving classes.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @since 1.20
+ *
+ * @ingroup Test
+ *
+ * @group ORM
+ *
+ * The database group has as a side effect that temporal database tables are
created. This makes
+ * it possible to test without poisoning a production database.
+ * @group Database
+ *
+ * Some of the tests takes more time, and needs therefor longer time before
they can be aborted
+ * as non-functional. The reason why tests are aborted is assumed to be set up
of temporal databases
+ * that hold the first tests in a pending state awaiting access to the
database.
+ * @group medium
+ *
+ * @author Jeroen De Dauw < [email protected] >
+ */
+abstract class ORMRowTest extends \MediaWikiTestCase {
+
+ /**
+ * @since 1.20
+ * @return string
+ */
+ abstract protected function getRowClass();
+
+ /**
+ * @since 1.20
+ * @return IORMTable
+ */
+ abstract protected function getTableInstance();
+
+ /**
+ * @since 1.20
+ * @return array
+ */
+ abstract public function constructorTestProvider();
+
+ /**
+ * @since 1.20
+ * @param IORMRow $row
+ * @param array $data
+ */
+ protected function verifyFields( IORMRow $row, array $data ) {
+ foreach ( array_keys( $data ) as $fieldName ) {
+ $this->assertEquals( $data[$fieldName], $row->getField(
$fieldName ) );
+ }
+ }
+
+ /**
+ * @since 1.20
+ * @param array $data
+ * @param bool $loadDefaults
+ * @return IORMRow
+ */
+ protected function getRowInstance( array $data, $loadDefaults ) {
+ $class = $this->getRowClass();
+
+ return new $class( $this->getTableInstance(), $data,
$loadDefaults );
+ }
+
+ /**
+ * @since 1.20
+ * @return array
+ */
+ protected function getMockValues() {
+ return array(
+ 'id' => 1,
+ 'str' => 'foobar4645645',
+ 'int' => 42,
+ 'float' => 4.2,
+ 'bool' => true,
+ 'array' => array( 42, 'foobar' ),
+ 'blob' => new stdClass()
+ );
+ }
+
+ /**
+ * @since 1.20
+ * @return array
+ */
+ protected function getMockFields() {
+ $mockValues = $this->getMockValues();
+ $mockFields = array();
+
+ foreach ( $this->getTableInstance()->getFields() as $name =>
$type ) {
+ if ( $name !== 'id' ) {
+ $mockFields[$name] = $mockValues[$type];
+ }
+ }
+
+ return $mockFields;
+ }
+
+ /**
+ * @since 1.20
+ * @return array Array of IORMRow
+ */
+ public function instanceProvider() {
+ $instances = array();
+
+ foreach ( $this->constructorTestProvider() as $arguments ) {
+ $instances[] = array( call_user_func_array( array(
$this, 'getRowInstance' ), $arguments ) );
+ }
+
+ return $instances;
+ }
+
+ /**
+ * @dataProvider constructorTestProvider
+ */
+ public function testConstructor( array $data, $loadDefaults ) {
+ $this->verifyFields( $this->getRowInstance( $data,
$loadDefaults ), $data );
+ }
+
+ /**
+ * @dataProvider constructorTestProvider
+ */
+ public function testSaveAndRemove( array $data, $loadDefaults ) {
+ $item = $this->getRowInstance( $data, $loadDefaults );
+
+ $this->assertTrue( $item->save() );
+
+ $this->assertTrue( $item->hasIdField() );
+ $this->assertTrue( is_integer( $item->getId() ) );
+
+ $id = $item->getId();
+
+ $this->assertTrue( $item->save() );
+
+ $this->assertEquals( $id, $item->getId() );
+
+ $this->verifyFields( $item, $data );
+
+ $this->assertTrue( $item->remove() );
+
+ $this->assertFalse( $item->hasIdField() );
+
+ $this->assertTrue( $item->save() );
+
+ $this->verifyFields( $item, $data );
+
+ $this->assertTrue( $item->remove() );
+
+ $this->assertFalse( $item->hasIdField() );
+
+ $this->verifyFields( $item, $data );
+ }
+
+ /**
+ * @dataProvider instanceProvider
+ */
+ public function testSetField( IORMRow $item ) {
+ foreach ( $this->getMockFields() as $name => $value ) {
+ $item->setField( $name, $value );
+ $this->assertEquals( $value, $item->getField( $name ) );
+ }
+ }
+
+ /**
+ * @since 1.20
+ * @param array $expected
+ * @param IORMRow $item
+ */
+ protected function assertFieldValues( array $expected, IORMRow $item ) {
+ foreach ( $expected as $name => $type ) {
+ if ( $name !== 'id' ) {
+ $this->assertEquals( $expected[$name],
$item->getField( $name ) );
+ }
+ }
+ }
+
+ /**
+ * @dataProvider instanceProvider
+ */
+ public function testSetFields( IORMRow $item ) {
+ $originalValues = $item->getFields();
+
+ $item->setFields( array(), false );
+
+ foreach ( $item->getTable()->getFields() as $name => $type ) {
+ $originalHas = array_key_exists( $name, $originalValues
);
+ $newHas = $item->hasField( $name );
+
+ $this->assertEquals( $originalHas, $newHas );
+
+ if ( $originalHas && $newHas ) {
+ $this->assertEquals( $originalValues[$name],
$item->getField( $name ) );
+ }
+ }
+
+ $mockFields = $this->getMockFields();
+
+ $item->setFields( $mockFields, false );
+
+ $this->assertFieldValues( $originalValues, $item );
+
+ $item->setFields( $mockFields, true );
+
+ $this->assertFieldValues( $mockFields, $item );
+ }
+
+ // TODO: test all of the methods!
+
+}
diff --git a/tests/phpunit/db/ORMTableTest.php
b/tests/phpunit/db/ORMTableTest.php
new file mode 100644
index 0000000..60a55f2
--- /dev/null
+++ b/tests/phpunit/db/ORMTableTest.php
@@ -0,0 +1,130 @@
+<?php
+
+namespace EducationProgram\Tests;
+use EducationProgram\IORMRow;
+use EducationProgram\IORMTable;
+use EducationProgram\ORMTable;
+
+/**
+ * Abstract class to construct tests for ORMTable deriving classes.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @since 1.21
+ *
+ * @ingroup Test
+ *
+ * @group ORM
+ * @group Database
+ *
+ * @covers PageORMTableForTesting
+ *
+ * @author Jeroen De Dauw < [email protected] >
+ * @author Daniel Kinzler
+ */
+
+class ORMTableTest extends \MediaWikiTestCase {
+
+ /**
+ * @since 1.21
+ * @return string
+ */
+ protected function getTableClass() {
+ return 'PageORMTableForTesting';
+ }
+
+ /**
+ * @since 1.21
+ * @return IORMTable
+ */
+ public function getTable() {
+ $class = $this->getTableClass();
+
+ return $class::singleton();
+ }
+
+ /**
+ * @since 1.21
+ * @return string
+ */
+ public function getRowClass() {
+ return $this->getTable()->getRowClass();
+ }
+
+ /**
+ * @since 1.21
+ */
+ public function testSingleton() {
+ $class = $this->getTableClass();
+
+ $this->assertInstanceOf( $class, $class::singleton() );
+ $this->assertTrue( $class::singleton() === $class::singleton()
);
+ }
+}
+
+/**
+ * Dummy ORM table for testing, reading Title objects from the page table.
+ *
+ * @since 1.21
+ */
+
+class PageORMTableForTesting extends ORMTable {
+
+ public function __construct() {
+ $this->fieldPrefix = 'page_';
+ }
+
+ /**
+ * @see ORMTable::getName
+ *
+ * @return string
+ */
+ public function getName() {
+ return 'page';
+ }
+
+ /**
+ * @see ORMTable::getRowClass
+ *
+ * @return string
+ */
+ public function getRowClass() {
+ return 'Title';
+ }
+
+ /**
+ * @see ORMTable::newRow
+ *
+ * @return IORMRow
+ */
+ public function newRow( array $data, $loadDefaults = false ) {
+ return \Title::makeTitle( $data['namespace'], $data['title'] );
+ }
+
+ /**
+ * @see ORMTable::getFields
+ *
+ * @return array
+ */
+ public function getFields() {
+ return array(
+ 'id' => 'int',
+ 'namespace' => 'int',
+ 'title' => 'str',
+ );
+ }
+}
diff --git a/tests/phpunit/db/TestORMRowTest.php
b/tests/phpunit/db/TestORMRowTest.php
new file mode 100644
index 0000000..aba55ec
--- /dev/null
+++ b/tests/phpunit/db/TestORMRowTest.php
@@ -0,0 +1,212 @@
+<?php
+
+namespace EducationProgram\Tests;
+
+/**
+ * Tests for the TestORMRow class.
+ * TestORMRow is a dummy class to be able to test the abstract ORMRow class.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Test
+ * @author Jeroen De Dauw < [email protected] >
+ */
+use EducationProgram\IORMTable;
+use EducationProgram\ORMRow;
+use EducationProgram\ORMTable;
+
+/**
+ * The database group has as a side effect that temporal database tables are
created. This makes
+ * it possible to test without poisoning a production database.
+ *
+ * Some of the tests takes more time, and needs therefor longer time before
they can be aborted
+ * as non-functional. The reason why tests are aborted is assumed to be set up
of temporal databases
+ * that hold the first tests in a pending state awaiting access to the
database.
+ *
+ * @since 1.20
+ *
+ * @group ORM
+ * @group Database
+ * @group medium
+ * @covers TestORMRow
+ */
+class TestORMRowTest extends ORMRowTest {
+
+ /**
+ * @since 1.20
+ * @return string
+ */
+ protected function getRowClass() {
+ return 'TestORMRow';
+ }
+
+ /**
+ * @since 1.20
+ * @return IORMTable
+ */
+ protected function getTableInstance() {
+ return TestORMTable::singleton();
+ }
+
+ protected function setUp() {
+ parent::setUp();
+
+ $dbw = wfGetDB( DB_MASTER );
+
+ $isSqlite = $GLOBALS['wgDBtype'] === 'sqlite';
+ $isPostgres = $GLOBALS['wgDBtype'] === 'postgres';
+
+ $idField = $isSqlite ? 'INTEGER' : 'INT unsigned';
+ $primaryKey = $isSqlite ? 'PRIMARY KEY AUTOINCREMENT' :
'auto_increment PRIMARY KEY';
+
+ if ( $isPostgres ) {
+ $dbw->query(
+ 'CREATE TABLE IF NOT EXISTS ' .
$dbw->tableName( 'orm_test' ) . "(
+ test_id serial PRIMARY KEY,
+ test_name TEXT NOT NULL DEFAULT '',
+ test_age INTEGER NOT NULL DEFAULT 0,
+ test_height REAL NOT NULL DEFAULT 0,
+ test_awesome INTEGER NOT NULL DEFAULT 0,
+ test_stuff BYTEA,
+ test_moarstuff BYTEA,
+ test_time TIMESTAMPTZ
+ );",
+ __METHOD__
+ );
+ } else {
+ $dbw->query(
+ 'CREATE TABLE IF NOT EXISTS ' .
$dbw->tableName( 'orm_test' ) . '(
+ test_id ' . $idField
. ' NOT NULL ' . $primaryKey . ',
+ test_name VARCHAR(255)
NOT NULL,
+ test_age TINYINT
unsigned NOT NULL,
+ test_height FLOAT
NOT NULL,
+ test_awesome TINYINT
unsigned NOT NULL,
+ test_stuff BLOB
NOT NULL,
+ test_moarstuff BLOB
NOT NULL,
+ test_time
varbinary(14) NOT NULL
+ );',
+ __METHOD__
+ );
+ }
+ }
+
+ protected function tearDown() {
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->dropTable( 'orm_test', __METHOD__ );
+
+ parent::tearDown();
+ }
+
+ public function constructorTestProvider() {
+ $dbw = wfGetDB( DB_MASTER );
+ return array(
+ array(
+ array(
+ 'name' => 'Foobar',
+ 'time' => $dbw->timestamp(
'20120101020202' ),
+ 'age' => 42,
+ 'height' => 9000.1,
+ 'awesome' => true,
+ 'stuff' => array( 13, 11, 7, 5, 3, 2 ),
+ 'moarstuff' => (object)array( 'foo' =>
'bar', 'bar' => array( 4, 2 ), 'baz' => true )
+ ),
+ true
+ ),
+ );
+ }
+
+ /**
+ * @since 1.21
+ * @return array
+ */
+ protected function getMockValues() {
+ return array(
+ 'id' => 1,
+ 'str' => 'foobar4645645',
+ 'int' => 42,
+ 'float' => 4.2,
+ 'bool' => '',
+ 'array' => array( 42, 'foobar' ),
+ 'blob' => new stdClass()
+ );
+ }
+}
+
+class TestORMRow extends ORMRow {
+}
+
+class TestORMTable extends ORMTable {
+
+ public function __construct() {
+ $this->fieldPrefix = 'test_';
+ }
+
+ /**
+ * Returns the name of the database table objects of this type are
stored in.
+ *
+ * @since 1.20
+ *
+ * @return string
+ */
+ public function getName() {
+ return 'orm_test';
+ }
+
+ /**
+ * Returns the name of a IORMRow implementing class that
+ * represents single rows in this table.
+ *
+ * @since 1.20
+ *
+ * @return string
+ */
+ public function getRowClass() {
+ return 'TestORMRow';
+ }
+
+ /**
+ * Returns an array with the fields and their types this object
contains.
+ * This corresponds directly to the fields in the database, without
prefix.
+ *
+ * field name => type
+ *
+ * Allowed types:
+ * * id
+ * * str
+ * * int
+ * * float
+ * * bool
+ * * array
+ * * blob
+ *
+ * @since 1.20
+ *
+ * @return array
+ */
+ public function getFields() {
+ return array(
+ 'id' => 'id',
+ 'name' => 'str',
+ 'age' => 'int',
+ 'height' => 'float',
+ 'awesome' => 'bool',
+ 'stuff' => 'array',
+ 'moarstuff' => 'blob',
+ 'time' => 'str', // TS_MW
+ );
+ }
+}
diff --git a/tests/phpunit/rows/CourseTest.php
b/tests/phpunit/rows/CourseTest.php
index 8405e31..44815dd 100644
--- a/tests/phpunit/rows/CourseTest.php
+++ b/tests/phpunit/rows/CourseTest.php
@@ -22,7 +22,7 @@
* Returns the name of the subclass of ORMRow that we're testing (in
this
* case, \EducationProgram\Course).
*
- * @see \ORMRowTest::getRowClass()
+ * @see ORMRowTest::getRowClass()
* @since 0.4 alpha
* @return string
*/
@@ -33,7 +33,7 @@
/**
* Returns the table whose rows are represented by the class we're
testing.
*
- * @see \ORMRowTest::getTableInstance()
+ * @see ORMRowTest::getTableInstance()
* @since 0.4 alpha
* @return \EducationProgram\Courses
*/
@@ -46,7 +46,7 @@
* of the class we're testing (in this case, \EducationProgram\Course).
* The test instances we'll use will be created with these arguments.
*
- * @see \ORMRowTest::constructorTestProvider()
+ * @see ORMRowTest::constructorTestProvider()
* @since 0.4 alpha
* @return array
*/
diff --git a/tests/phpunit/rows/OrgTest.php b/tests/phpunit/rows/OrgTest.php
index 7d38332..c6f53ec 100644
--- a/tests/phpunit/rows/OrgTest.php
+++ b/tests/phpunit/rows/OrgTest.php
@@ -21,7 +21,7 @@
* Returns the name of the subclass of ORMRow that we're testing (in
this
* case, \EducationProgram\Org).
*
- * @see \ORMRowTest::getRowClass()
+ * @see ORMRowTest::getRowClass()
* @since 0.4 alpha
* @return string
*/
@@ -32,7 +32,7 @@
/**
* Returns the table whose rows are represented by the class we're
testing.
*
- * @see \ORMRowTest::getTableInstance()
+ * @see ORMRowTest::getTableInstance()
* @since 0.4 alpha
* @return \EducationProgram\Orgs
*/
@@ -45,7 +45,7 @@
* of the class we're testing (in this case, \EducationProgram\Org).
* The test instances we'll use will be created with these arguments.
*
- * @see \ORMRowTest::constructorTestProvider()
+ * @see ORMRowTest::constructorTestProvider()
* @since 0.4 alpha
* @return array
*/
@@ -74,7 +74,7 @@
* is always munged to start with a capital, and the superclass
* provides a string that doesn't.
*
- * @see \ORMRowTest::getMockValues()
+ * @see ORMRowTest::getMockValues()
* @since 1.20
* @return array
*/
diff --git a/tests/phpunit/rows/PageObjectTest.php
b/tests/phpunit/rows/PageObjectTest.php
index 22c466d..25c2122 100644
--- a/tests/phpunit/rows/PageObjectTest.php
+++ b/tests/phpunit/rows/PageObjectTest.php
@@ -2,7 +2,7 @@
namespace EducationProgram\Tests\Rows;
-abstract class PageObjectTest extends \ORMRowTest {
+abstract class PageObjectTest extends ORMRowTest {
/**
* Provides pairs of duplicate instances for testing the addition of an
* instance with the same name/title as one that already exists.
--
To view, visit https://gerrit.wikimedia.org/r/246718
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: Id223222aff7d375687bbf0126f3514d248660bab
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/EducationProgram
Gerrit-Branch: master
Gerrit-Owner: Reedy <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits