summaryrefslogtreecommitdiff
path: root/includes/site
diff options
context:
space:
mode:
Diffstat (limited to 'includes/site')
-rw-r--r--includes/site/CachingSiteStore.php195
-rw-r--r--includes/site/DBSiteStore.php345
-rw-r--r--includes/site/FileBasedSiteLookup.php139
-rw-r--r--includes/site/HashSiteStore.php123
-rw-r--r--includes/site/MediaWikiSite.php4
-rw-r--r--includes/site/SiteExporter.php114
-rw-r--r--includes/site/SiteImporter.php263
-rw-r--r--includes/site/SiteLookup.php50
-rw-r--r--includes/site/SiteSQLStore.php459
-rw-r--r--includes/site/SiteStore.php29
-rw-r--r--includes/site/SitesCacheFileBuilder.php113
11 files changed, 1353 insertions, 481 deletions
diff --git a/includes/site/CachingSiteStore.php b/includes/site/CachingSiteStore.php
new file mode 100644
index 00000000..9243f12b
--- /dev/null
+++ b/includes/site/CachingSiteStore.php
@@ -0,0 +1,195 @@
+<?php
+
+/**
+ * Represents the site configuration of a wiki.
+ * Holds a list of sites (ie SiteList), with a caching layer.
+ *
+ * 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.25
+ *
+ * @file
+ * @ingroup Site
+ *
+ * @license GNU GPL v2+
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ * @author Katie Filbert < aude.wiki@gmail.com >
+ */
+class CachingSiteStore implements SiteStore {
+
+ /**
+ * @var SiteList|null
+ */
+ private $sites = null;
+
+ /**
+ * @var string|null
+ */
+ private $cacheKey;
+
+ /**
+ * @var int
+ */
+ private $cacheTimeout;
+
+ /**
+ * @var BagOStuff
+ */
+ private $cache;
+
+ /**
+ * @var SiteStore
+ */
+ private $siteStore;
+
+ /**
+ * @param SiteStore $siteStore
+ * @param BagOStuff $cache
+ * @param string|null $cacheKey
+ * @param int $cacheTimeout
+ */
+ public function __construct(
+ SiteStore $siteStore,
+ BagOStuff $cache,
+ $cacheKey = null,
+ $cacheTimeout = 3600
+ ) {
+ $this->siteStore = $siteStore;
+ $this->cache = $cache;
+ $this->cacheKey = $cacheKey;
+ $this->cacheTimeout = $cacheTimeout;
+ }
+
+ /**
+ * Constructs a cache key to use for caching the list of sites.
+ *
+ * This includes the concrete class name of the site list as well as a version identifier
+ * for the list's serialization, to avoid problems when unserializing site lists serialized
+ * by an older version, e.g. when reading from a cache.
+ *
+ * The cache key also includes information about where the sites were loaded from, e.g.
+ * the name of a database table.
+ *
+ * @see SiteList::getSerialVersionId
+ *
+ * @return string The cache key.
+ */
+ private function getCacheKey() {
+ if ( $this->cacheKey === null ) {
+ $type = 'SiteList#' . SiteList::getSerialVersionId();
+ $this->cacheKey = wfMemcKey( "sites/$type" );
+ }
+
+ return $this->cacheKey;
+ }
+
+ /**
+ * @see SiteStore::getSites
+ *
+ * @since 1.25
+ *
+ * @return SiteList
+ */
+ public function getSites() {
+ if ( $this->sites === null ) {
+ $this->sites = $this->cache->get( $this->getCacheKey() );
+
+ if ( !is_object( $this->sites ) ) {
+ $this->sites = $this->siteStore->getSites();
+
+ $this->cache->set( $this->getCacheKey(), $this->sites, $this->cacheTimeout );
+ }
+ }
+
+ return $this->sites;
+ }
+
+ /**
+ * @see SiteStore::getSite
+ *
+ * @since 1.25
+ *
+ * @param string $globalId
+ *
+ * @return Site|null
+ */
+ public function getSite( $globalId ) {
+ $sites = $this->getSites();
+
+ return $sites->hasSite( $globalId ) ? $sites->getSite( $globalId ) : null;
+ }
+
+ /**
+ * @see SiteStore::saveSite
+ *
+ * @since 1.25
+ *
+ * @param Site $site
+ *
+ * @return bool Success indicator
+ */
+ public function saveSite( Site $site ) {
+ return $this->saveSites( array( $site ) );
+ }
+
+ /**
+ * @see SiteStore::saveSites
+ *
+ * @since 1.25
+ *
+ * @param Site[] $sites
+ *
+ * @return bool Success indicator
+ */
+ public function saveSites( array $sites ) {
+ if ( empty( $sites ) ) {
+ return true;
+ }
+
+ $success = $this->siteStore->saveSites( $sites );
+
+ // purge cache
+ $this->reset();
+
+ return $success;
+ }
+
+ /**
+ * Purges the internal and external cache of the site list, forcing the list
+ * of sites to be reloaded.
+ *
+ * @since 1.25
+ */
+ public function reset() {
+ // purge cache
+ $this->cache->delete( $this->getCacheKey() );
+ $this->sites = null;
+ }
+
+ /**
+ * Clears the list of sites stored.
+ *
+ * @see SiteStore::clear()
+ *
+ * @return bool Success
+ */
+ public function clear() {
+ $this->reset();
+
+ return $this->siteStore->clear();
+ }
+
+}
diff --git a/includes/site/DBSiteStore.php b/includes/site/DBSiteStore.php
new file mode 100644
index 00000000..f167584e
--- /dev/null
+++ b/includes/site/DBSiteStore.php
@@ -0,0 +1,345 @@
+<?php
+
+/**
+ * Represents the site configuration of a wiki.
+ * Holds a list of sites (ie SiteList), stored in the database.
+ *
+ * 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.25
+ *
+ * @file
+ * @ingroup Site
+ *
+ * @license GNU GPL v2+
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+class DBSiteStore implements SiteStore {
+
+ /**
+ * @var SiteList|null
+ */
+ protected $sites = null;
+
+ /**
+ * @var ORMTable
+ */
+ protected $sitesTable;
+
+ /**
+ * @since 1.25
+ *
+ * @param ORMTable|null $sitesTable
+ */
+ public function __construct( ORMTable $sitesTable = null ) {
+ if ( $sitesTable === null ) {
+ $sitesTable = $this->newSitesTable();
+ }
+
+ $this->sitesTable = $sitesTable;
+ }
+
+ /**
+ * @see SiteStore::getSites
+ *
+ * @since 1.25
+ *
+ * @return SiteList
+ */
+ public function getSites() {
+ $this->loadSites();
+
+ return $this->sites;
+ }
+
+ /**
+ * Returns a new Site object constructed from the provided ORMRow.
+ *
+ * @since 1.25
+ *
+ * @param ORMRow $siteRow
+ *
+ * @return Site
+ */
+ protected function siteFromRow( ORMRow $siteRow ) {
+
+ $site = Site::newForType( $siteRow->getField( 'type', Site::TYPE_UNKNOWN ) );
+
+ $site->setGlobalId( $siteRow->getField( 'global_key' ) );
+
+ $site->setInternalId( $siteRow->getField( 'id' ) );
+
+ if ( $siteRow->hasField( 'forward' ) ) {
+ $site->setForward( $siteRow->getField( 'forward' ) );
+ }
+
+ if ( $siteRow->hasField( 'group' ) ) {
+ $site->setGroup( $siteRow->getField( 'group' ) );
+ }
+
+ if ( $siteRow->hasField( 'language' ) ) {
+ $site->setLanguageCode( $siteRow->getField( 'language' ) === ''
+ ? null
+ : $siteRow->getField( 'language' )
+ );
+ }
+
+ if ( $siteRow->hasField( 'source' ) ) {
+ $site->setSource( $siteRow->getField( 'source' ) );
+ }
+
+ if ( $siteRow->hasField( 'data' ) ) {
+ $site->setExtraData( $siteRow->getField( 'data' ) );
+ }
+
+ if ( $siteRow->hasField( 'config' ) ) {
+ $site->setExtraConfig( $siteRow->getField( 'config' ) );
+ }
+
+ return $site;
+ }
+
+ /**
+ * Get a new ORMRow from a Site object
+ *
+ * @since 1.25
+ *
+ * @param Site $site
+ *
+ * @return ORMRow
+ */
+ protected function getRowFromSite( Site $site ) {
+ $fields = array(
+ // Site data
+ 'global_key' => $site->getGlobalId(), // TODO: check not null
+ 'type' => $site->getType(),
+ 'group' => $site->getGroup(),
+ 'source' => $site->getSource(),
+ 'language' => $site->getLanguageCode() === null ? '' : $site->getLanguageCode(),
+ 'protocol' => $site->getProtocol(),
+ 'domain' => strrev( $site->getDomain() ) . '.',
+ 'data' => $site->getExtraData(),
+
+ // Site config
+ 'forward' => $site->shouldForward(),
+ 'config' => $site->getExtraConfig(),
+ );
+
+ if ( $site->getInternalId() !== null ) {
+ $fields['id'] = $site->getInternalId();
+ }
+
+ return new ORMRow( $this->sitesTable, $fields );
+ }
+
+ /**
+ * Fetches the site from the database and loads them into the sites field.
+ *
+ * @since 1.25
+ */
+ protected function loadSites() {
+ $this->sites = new SiteList();
+
+ foreach ( $this->sitesTable->select() as $siteRow ) {
+ $this->sites[] = $this->siteFromRow( $siteRow );
+ }
+
+ // Batch load the local site identifiers.
+ $ids = wfGetDB( $this->sitesTable->getReadDb() )->select(
+ 'site_identifiers',
+ array(
+ 'si_site',
+ 'si_type',
+ 'si_key',
+ ),
+ array(),
+ __METHOD__
+ );
+
+ foreach ( $ids as $id ) {
+ if ( $this->sites->hasInternalId( $id->si_site ) ) {
+ $site = $this->sites->getSiteByInternalId( $id->si_site );
+ $site->addLocalId( $id->si_type, $id->si_key );
+ $this->sites->setSite( $site );
+ }
+ }
+ }
+
+ /**
+ * @see SiteStore::getSite
+ *
+ * @since 1.25
+ *
+ * @param string $globalId
+ *
+ * @return Site|null
+ */
+ public function getSite( $globalId ) {
+ if ( $this->sites === null ) {
+ $this->sites = $this->getSites();
+ }
+
+ return $this->sites->hasSite( $globalId ) ? $this->sites->getSite( $globalId ) : null;
+ }
+
+ /**
+ * @see SiteStore::saveSite
+ *
+ * @since 1.25
+ *
+ * @param Site $site
+ *
+ * @return bool Success indicator
+ */
+ public function saveSite( Site $site ) {
+ return $this->saveSites( array( $site ) );
+ }
+
+ /**
+ * @see SiteStore::saveSites
+ *
+ * @since 1.25
+ *
+ * @param Site[] $sites
+ *
+ * @return bool Success indicator
+ */
+ public function saveSites( array $sites ) {
+ if ( empty( $sites ) ) {
+ return true;
+ }
+
+ $dbw = $this->sitesTable->getWriteDbConnection();
+
+ $dbw->startAtomic( __METHOD__ );
+
+ $success = true;
+
+ $internalIds = array();
+ $localIds = array();
+
+ foreach ( $sites as $site ) {
+ if ( $site->getInternalId() !== null ) {
+ $internalIds[] = $site->getInternalId();
+ }
+
+ $siteRow = $this->getRowFromSite( $site );
+ $success = $siteRow->save( __METHOD__ ) && $success;
+
+ foreach ( $site->getLocalIds() as $idType => $ids ) {
+ foreach ( $ids as $id ) {
+ $localIds[] = array( $siteRow->getId(), $idType, $id );
+ }
+ }
+ }
+
+ if ( $internalIds !== array() ) {
+ $dbw->delete(
+ 'site_identifiers',
+ array( 'si_site' => $internalIds ),
+ __METHOD__
+ );
+ }
+
+ foreach ( $localIds as $localId ) {
+ $dbw->insert(
+ 'site_identifiers',
+ array(
+ 'si_site' => $localId[0],
+ 'si_type' => $localId[1],
+ 'si_key' => $localId[2],
+ ),
+ __METHOD__
+ );
+ }
+
+ $dbw->endAtomic( __METHOD__ );
+
+ $this->reset();
+
+ return $success;
+ }
+
+ /**
+ * Resets the SiteList
+ *
+ * @since 1.25
+ */
+ public function reset() {
+ $this->sites = null;
+ }
+
+ /**
+ * Clears the list of sites stored in the database.
+ *
+ * @see SiteStore::clear()
+ *
+ * @return bool Success
+ */
+ public function clear() {
+ $dbw = $this->sitesTable->getWriteDbConnection();
+
+ $dbw->startAtomic( __METHOD__ );
+ $ok = $dbw->delete( 'sites', '*', __METHOD__ );
+ $ok = $dbw->delete( 'site_identifiers', '*', __METHOD__ ) && $ok;
+ $dbw->endAtomic( __METHOD__ );
+
+ $this->reset();
+
+ return $ok;
+ }
+
+ /**
+ * @since 1.25
+ *
+ * @return ORMTable
+ */
+ protected function newSitesTable() {
+ return new ORMTable(
+ 'sites',
+ array(
+ 'id' => 'id',
+
+ // Site data
+ 'global_key' => 'str',
+ 'type' => 'str',
+ 'group' => 'str',
+ 'source' => 'str',
+ 'language' => 'str',
+ 'protocol' => 'str',
+ 'domain' => 'str',
+ 'data' => 'array',
+
+ // Site config
+ 'forward' => 'bool',
+ 'config' => 'array',
+ ),
+ array(
+ 'type' => Site::TYPE_UNKNOWN,
+ 'group' => Site::GROUP_NONE,
+ 'source' => Site::SOURCE_LOCAL,
+ 'data' => array(),
+
+ 'forward' => false,
+ 'config' => array(),
+ 'language' => '',
+ ),
+ 'ORMRow',
+ 'site_'
+ );
+ }
+
+}
diff --git a/includes/site/FileBasedSiteLookup.php b/includes/site/FileBasedSiteLookup.php
new file mode 100644
index 00000000..96544403
--- /dev/null
+++ b/includes/site/FileBasedSiteLookup.php
@@ -0,0 +1,139 @@
+<?php
+/**
+ * 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
+ *
+ * @license GNU GPL v2+
+ */
+
+/**
+ * Provides a file-based cache of a SiteStore. The sites are stored in
+ * a json file. (see docs/sitescache.txt regarding format)
+ *
+ * The cache can be built with the rebuildSitesCache.php maintenance script,
+ * and a MediaWiki instance can be setup to use this by setting the
+ * 'wgSitesCacheFile' configuration to the cache file location.
+ *
+ * @since 1.25
+ */
+class FileBasedSiteLookup implements SiteLookup {
+
+ /**
+ * @var SiteList
+ */
+ private $sites = null;
+
+ /**
+ * @var string
+ */
+ private $cacheFile;
+
+ /**
+ * @param string $cacheFile
+ */
+ public function __construct( $cacheFile ) {
+ $this->cacheFile = $cacheFile;
+ }
+
+ /**
+ * @since 1.25
+ *
+ * @return SiteList
+ */
+ public function getSites() {
+ if ( $this->sites === null ) {
+ $this->sites = $this->loadSitesFromCache();
+ }
+
+ return $this->sites;
+ }
+
+ /**
+ * @param string $globalId
+ *
+ * @since 1.25
+ *
+ * @return Site|null
+ */
+ public function getSite( $globalId ) {
+ $sites = $this->getSites();
+
+ return $sites->hasSite( $globalId ) ? $sites->getSite( $globalId ) : null;
+ }
+
+ /**
+ * @return SiteList
+ */
+ private function loadSitesFromCache() {
+ $data = $this->loadJsonFile();
+
+ $sites = new SiteList();
+
+ // @todo lazy initialize the site objects in the site list (e.g. only when needed to access)
+ foreach ( $data['sites'] as $siteArray ) {
+ $sites[] = $this->newSiteFromArray( $siteArray );
+ }
+
+ return $sites;
+ }
+
+ /**
+ * @throws MWException
+ * @return array see docs/sitescache.txt for format of the array.
+ */
+ private function loadJsonFile() {
+ if ( !is_readable( $this->cacheFile ) ) {
+ throw new MWException( 'SiteList cache file not found.' );
+ }
+
+ $contents = file_get_contents( $this->cacheFile );
+ $data = json_decode( $contents, true );
+
+ if ( !is_array( $data ) || !is_array( $data['sites'] )
+ || !array_key_exists( 'sites', $data )
+ ) {
+ throw new MWException( 'SiteStore json cache data is invalid.' );
+ }
+
+ return $data;
+ }
+
+ /**
+ * @param array $data
+ *
+ * @return Site
+ */
+ private function newSiteFromArray( array $data ) {
+ $siteType = array_key_exists( 'type', $data ) ? $data['type'] : Site::TYPE_UNKNOWN;
+ $site = Site::newForType( $siteType );
+
+ $site->setGlobalId( $data['globalid'] );
+ $site->setForward( $data['forward'] );
+ $site->setGroup( $data['group'] );
+ $site->setLanguageCode( $data['language'] );
+ $site->setSource( $data['source'] );
+ $site->setExtraData( $data['data'] );
+ $site->setExtraConfig( $data['config'] );
+
+ foreach ( $data['identifiers'] as $identifier ) {
+ $site->addLocalId( $identifier['type'], $identifier['key'] );
+ }
+
+ return $site;
+ }
+
+}
diff --git a/includes/site/HashSiteStore.php b/includes/site/HashSiteStore.php
new file mode 100644
index 00000000..2c254721
--- /dev/null
+++ b/includes/site/HashSiteStore.php
@@ -0,0 +1,123 @@
+<?php
+/**
+ * In-memory implementation of SiteStore.
+ *
+ * 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
+ */
+
+/**
+ * In-memory SiteStore implementation, storing sites in an associative array.
+ *
+ * @author Daniel Kinzler
+ * @author Katie Filbert < aude.wiki@gmail.com >
+ *
+ * @since 1.25
+ * @ingroup Site
+ */
+class HashSiteStore implements SiteStore {
+
+ /**
+ * @var Site[]
+ */
+ private $sites = array();
+
+ /**
+ * @param array $sites
+ */
+ public function __construct( $sites = array() ) {
+ $this->saveSites( $sites );
+ }
+
+ /**
+ * Saves the provided site.
+ *
+ * @since 1.25
+ *
+ * @param Site $site
+ *
+ * @return boolean Success indicator
+ */
+ public function saveSite( Site $site ) {
+ $this->sites[$site->getGlobalId()] = $site;
+
+ return true;
+ }
+
+ /**
+ * Saves the provided sites.
+ *
+ * @since 1.25
+ *
+ * @param Site[] $sites
+ *
+ * @return boolean Success indicator
+ */
+ public function saveSites( array $sites ) {
+ foreach ( $sites as $site ) {
+ $this->saveSite( $site );
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the site with provided global id, or null if there is no such site.
+ *
+ * @since 1.25
+ *
+ * @param string $globalId
+ * @param string $source either 'cache' or 'recache'.
+ * If 'cache', the values can (but not obliged) come from a cache.
+ *
+ * @return Site|null
+ */
+ public function getSite( $globalId, $source = 'cache' ) {
+ if ( isset( $this->sites[$globalId] ) ) {
+ return $this->sites[$globalId];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns a list of all sites. By default this site is
+ * fetched from the cache, which can be changed to loading
+ * the list from the database using the $useCache parameter.
+ *
+ * @since 1.25
+ *
+ * @param string $source either 'cache' or 'recache'.
+ * If 'cache', the values can (but not obliged) come from a cache.
+ *
+ * @return SiteList
+ */
+ public function getSites( $source = 'cache' ) {
+ return new SiteList( $this->sites );
+ }
+
+ /**
+ * Deletes all sites from the database. After calling clear(), getSites() will return an empty
+ * list and getSite() will return null until saveSite() or saveSites() is called.
+ */
+ public function clear() {
+ $this->sites = array();
+
+ return true;
+ }
+
+}
diff --git a/includes/site/MediaWikiSite.php b/includes/site/MediaWikiSite.php
index 9711f042..95631f8e 100644
--- a/includes/site/MediaWikiSite.php
+++ b/includes/site/MediaWikiSite.php
@@ -116,7 +116,7 @@ class MediaWikiSite extends Site {
// Make sure the string is normalized into NFC (due to the bug 40017)
// but do nothing to the whitespaces, that should work appropriately.
// @see https://bugzilla.wikimedia.org/show_bug.cgi?id=40017
- $pageName = UtfNormal::cleanUp( $pageName );
+ $pageName = UtfNormal\Validator::cleanUp( $pageName );
// Build the args for the specific call
$args = array(
@@ -137,7 +137,7 @@ class MediaWikiSite extends Site {
// Go on call the external site
// @todo we need a good way to specify a timeout here.
- $ret = Http::get( $url );
+ $ret = Http::get( $url, array(), __METHOD__ );
}
if ( $ret === false ) {
diff --git a/includes/site/SiteExporter.php b/includes/site/SiteExporter.php
new file mode 100644
index 00000000..62f6ca3c
--- /dev/null
+++ b/includes/site/SiteExporter.php
@@ -0,0 +1,114 @@
+<?php
+
+/**
+ * Utility for exporting site entries to XML.
+ * For the output file format, see docs/sitelist.txt and docs/sitelist-1.0.xsd.
+ *
+ * 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.25
+ *
+ * @file
+ * @ingroup Site
+ *
+ * @license GNU GPL v2+
+ * @author Daniel Kinzler
+ */
+class SiteExporter {
+
+ /**
+ * @var resource
+ */
+ private $sink;
+
+ /**
+ * @param resource $sink A file handle open for writing
+ */
+ public function __construct( $sink ) {
+ if ( !is_resource( $sink ) || get_resource_type( $sink ) !== 'stream' ) {
+ throw new InvalidArgumentException( '$sink must be a file handle' );
+ }
+
+ $this->sink = $sink;
+ }
+
+ /**
+ * Writes a <site> tag for each Site object in $sites, and encloses the entire list
+ * between <sites> tags.
+ *
+ * @param Site[]|SiteList $sites
+ */
+ public function exportSites( $sites ) {
+ $attributes = array(
+ 'version' => '1.0',
+ 'xmlns' => 'http://www.mediawiki.org/xml/sitelist-1.0/',
+ );
+
+ fwrite( $this->sink, XML::openElement( 'sites', $attributes ) . "\n" );
+
+ foreach ( $sites as $site ) {
+ $this->exportSite( $site );
+ }
+
+ fwrite( $this->sink, XML::closeElement( 'sites' ) . "\n" );
+ fflush( $this->sink );
+ }
+
+ /**
+ * Writes a <site> tag representing the given Site object.
+ *
+ * @param Site $site
+ */
+ private function exportSite( Site $site ) {
+ if ( $site->getType() !== Site::TYPE_UNKNOWN ) {
+ $siteAttr = array( 'type' => $site->getType() );
+ } else {
+ $siteAttr = null;
+ }
+
+ fwrite( $this->sink, "\t" . XML::openElement( 'site', $siteAttr ) . "\n" );
+
+ fwrite( $this->sink, "\t\t" . XML::element( 'globalid', null, $site->getGlobalId() ) . "\n" );
+
+ if ( $site->getGroup() !== Site::GROUP_NONE ) {
+ fwrite( $this->sink, "\t\t" . XML::element( 'group', null, $site->getGroup() ) . "\n" );
+ }
+
+ if ( $site->getSource() !== Site::SOURCE_LOCAL ) {
+ fwrite( $this->sink, "\t\t" . XML::element( 'source', null, $site->getSource() ) . "\n" );
+ }
+
+ if ( $site->shouldForward() ) {
+ fwrite( $this->sink, "\t\t" . XML::element( 'forward', null, '' ) . "\n" );
+ }
+
+ foreach ( $site->getAllPaths() as $type => $path ) {
+ fwrite( $this->sink, "\t\t" . XML::element( 'path', array( 'type' => $type ), $path ) . "\n" );
+ }
+
+ foreach ( $site->getLocalIds() as $type => $ids ) {
+ foreach ( $ids as $id ) {
+ fwrite( $this->sink, "\t\t" . XML::element( 'localid', array( 'type' => $type ), $id ) . "\n" );
+ }
+ }
+
+ //@todo: export <data>
+ //@todo: export <config>
+
+ fwrite( $this->sink, "\t" . XML::closeElement( 'site' ) . "\n" );
+ }
+
+}
diff --git a/includes/site/SiteImporter.php b/includes/site/SiteImporter.php
new file mode 100644
index 00000000..a05bad5d
--- /dev/null
+++ b/includes/site/SiteImporter.php
@@ -0,0 +1,263 @@
+<?php
+
+/**
+ * Utility for importing site entries from XML.
+ * For the expected format of the input, see docs/sitelist.txt and docs/sitelist-1.0.xsd.
+ *
+ * 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.25
+ *
+ * @file
+ * @ingroup Site
+ *
+ * @license GNU GPL v2+
+ * @author Daniel Kinzler
+ */
+class SiteImporter {
+
+ /**
+ * @var SiteStore
+ */
+ private $store;
+
+ /**
+ * @var callable|null
+ */
+ private $exceptionCallback;
+
+ /**
+ * @param SiteStore $store
+ */
+ public function __construct( SiteStore $store ) {
+ $this->store = $store;
+ }
+
+ /**
+ * @return callable
+ */
+ public function getExceptionCallback() {
+ return $this->exceptionCallback;
+ }
+
+ /**
+ * @param callable $exceptionCallback
+ */
+ public function setExceptionCallback( $exceptionCallback ) {
+ $this->exceptionCallback = $exceptionCallback;
+ }
+
+ /**
+ * @param string $file
+ */
+ public function importFromFile( $file ) {
+ $xml = file_get_contents( $file );
+
+ if ( $xml === false ) {
+ throw new RuntimeException( 'Failed to read ' . $file . '!' );
+ }
+
+ $this->importFromXML( $xml );
+ }
+
+ /**
+ * @param string $xml
+ *
+ * @throws InvalidArgumentException
+ */
+ public function importFromXML( $xml ) {
+ $document = new DOMDocument();
+
+ $oldLibXmlErrors = libxml_use_internal_errors( true );
+ $ok = $document->loadXML( $xml, LIBXML_NONET );
+
+ if ( !$ok ) {
+ $errors = libxml_get_errors();
+ libxml_use_internal_errors( $oldLibXmlErrors );
+
+ foreach ( $errors as $error ) {
+ /** @var LibXMLError $error */
+ throw new InvalidArgumentException(
+ 'Malformed XML: ' . $error->message . ' in line ' . $error->line
+ );
+ }
+
+ throw new InvalidArgumentException( 'Malformed XML!' );
+ }
+
+ libxml_use_internal_errors( $oldLibXmlErrors );
+ $this->importFromDOM( $document->documentElement );
+ }
+
+ /**
+ * @param DOMElement $root
+ */
+ private function importFromDOM( DOMElement $root ) {
+ $sites = $this->makeSiteList( $root );
+ $this->store->saveSites( $sites );
+ }
+
+ /**
+ * @param DOMElement $root
+ *
+ * @return Site[]
+ */
+ private function makeSiteList( DOMElement $root ) {
+ $sites = array();
+
+ // Old sites, to get the row IDs that correspond to the global site IDs.
+ // TODO: Get rid of internal row IDs, they just get in the way. Get rid of ORMRow, too.
+ $oldSites = $this->store->getSites();
+
+ $current = $root->firstChild;
+ while ( $current ) {
+ if ( $current instanceof DOMElement && $current->tagName === 'site' ) {
+ try {
+ $site = $this->makeSite( $current );
+ $key = $site->getGlobalId();
+
+ if ( $oldSites->hasSite( $key ) ) {
+ $oldSite = $oldSites->getSite( $key );
+ $site->setInternalId( $oldSite->getInternalId() );
+ }
+
+ $sites[$key] = $site;
+ } catch ( Exception $ex ) {
+ $this->handleException( $ex );
+ }
+ }
+
+ $current = $current->nextSibling;
+ }
+
+ return $sites;
+ }
+
+ /**
+ * @param DOMElement $siteElement
+ *
+ * @return Site
+ * @throws InvalidArgumentException
+ */
+ public function makeSite( DOMElement $siteElement ) {
+ if ( $siteElement->tagName !== 'site' ) {
+ throw new InvalidArgumentException( 'Expected <site> tag, found ' . $siteElement->tagName );
+ }
+
+ $type = $this->getAttributeValue( $siteElement, 'type', Site::TYPE_UNKNOWN );
+ $site = Site::newForType( $type );
+
+ $site->setForward( $this->hasChild( $siteElement, 'forward' ) );
+ $site->setGlobalId( $this->getChildText( $siteElement, 'globalid' ) );
+ $site->setGroup( $this->getChildText( $siteElement, 'group', Site::GROUP_NONE ) );
+ $site->setSource( $this->getChildText( $siteElement, 'source', Site::SOURCE_LOCAL ) );
+
+ $pathTags = $siteElement->getElementsByTagName( 'path' );
+ for ( $i = 0; $i < $pathTags->length; $i++ ) {
+ $pathElement = $pathTags->item( $i );
+ $pathType = $this->getAttributeValue( $pathElement, 'type' );
+ $path = $pathElement->textContent;
+
+ $site->setPath( $pathType, $path );
+ }
+
+ $idTags = $siteElement->getElementsByTagName( 'localid' );
+ for ( $i = 0; $i < $idTags->length; $i++ ) {
+ $idElement = $idTags->item( $i );
+ $idType = $this->getAttributeValue( $idElement, 'type' );
+ $id = $idElement->textContent;
+
+ $site->addLocalId( $idType, $id );
+ }
+
+ //@todo: import <data>
+ //@todo: import <config>
+
+ return $site;
+ }
+
+ /**
+ * @param DOMElement $element
+ * @param $name
+ * @param string|null|bool $default
+ *
+ * @return null|string
+ * @throws MWException If the attribute is not found and no default is provided
+ */
+ private function getAttributeValue( DOMElement $element, $name, $default = false ) {
+ $node = $element->getAttributeNode( $name );
+
+ if ( !$node ) {
+ if ( $default !== false ) {
+ return $default;
+ } else {
+ throw new MWException(
+ 'Required ' . $name . ' attribute not found in <' . $element->tagName . '> tag'
+ );
+ }
+ }
+
+ return $node->textContent;
+ }
+
+ /**
+ * @param DOMElement $element
+ * @param string $name
+ * @param string|null|bool $default
+ *
+ * @return null|string
+ * @throws MWException If the child element is not found and no default is provided
+ */
+ private function getChildText( DOMElement $element, $name, $default = false ) {
+ $elements = $element->getElementsByTagName( $name );
+
+ if ( $elements->length < 1 ) {
+ if ( $default !== false ) {
+ return $default;
+ } else {
+ throw new MWException(
+ 'Required <' . $name . '> tag not found inside <' . $element->tagName . '> tag'
+ );
+ }
+ }
+
+ $node = $elements->item( 0 );
+ return $node->textContent;
+ }
+
+ /**
+ * @param DOMElement $element
+ * @param string $name
+ *
+ * @return bool
+ * @throws MWException
+ */
+ private function hasChild( DOMElement $element, $name ) {
+ return $this->getChildText( $element, $name, null ) !== null;
+ }
+
+ /**
+ * @param Exception $ex
+ */
+ private function handleException( Exception $ex ) {
+ if ( $this->exceptionCallback ) {
+ call_user_func( $this->exceptionCallback, $ex );
+ } else {
+ wfLogWarning( $ex->getMessage() );
+ }
+ }
+
+}
diff --git a/includes/site/SiteLookup.php b/includes/site/SiteLookup.php
new file mode 100644
index 00000000..610bf0b7
--- /dev/null
+++ b/includes/site/SiteLookup.php
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * Interface for service objects providing a lookup of Site objects.
+ *
+ * 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.25
+ *
+ * @file
+ * @ingroup Site
+ *
+ * @license GNU GPL v2+
+ */
+interface SiteLookup {
+
+ /**
+ * Returns the site with provided global id, or null if there is no such site.
+ *
+ * @since 1.25
+ *
+ * @param string $globalId
+ *
+ * @return Site|null
+ */
+ public function getSite( $globalId );
+
+ /**
+ * Returns a list of all sites.
+ *
+ * @since 1.25
+ *
+ * @return SiteList
+ */
+ public function getSites();
+
+}
diff --git a/includes/site/SiteSQLStore.php b/includes/site/SiteSQLStore.php
index d1334680..d77f07be 100644
--- a/includes/site/SiteSQLStore.php
+++ b/includes/site/SiteSQLStore.php
@@ -28,468 +28,25 @@
* @license GNU GPL v2+
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
*/
-class SiteSQLStore implements SiteStore {
- /**
- * @since 1.21
- *
- * @var SiteList|null
- */
- protected $sites = null;
-
- /**
- * @var ORMTable
- */
- protected $sitesTable;
-
- /**
- * @var string|null
- */
- private $cacheKey = null;
-
- /**
- * @var int
- */
- private $cacheTimeout = 3600;
+class SiteSQLStore extends CachingSiteStore {
/**
* @since 1.21
+ * @deprecated 1.25 Construct a SiteStore instance directly instead.
*
* @param ORMTable|null $sitesTable
+ * @param BagOStuff|null $cache
*
* @return SiteStore
*/
- public static function newInstance( ORMTable $sitesTable = null ) {
- return new static( $sitesTable );
- }
-
- /**
- * Constructor.
- *
- * @since 1.21
- *
- * @param ORMTable|null $sitesTable
- */
- protected function __construct( ORMTable $sitesTable = null ) {
- if ( $sitesTable === null ) {
- $sitesTable = $this->newSitesTable();
- }
-
- $this->sitesTable = $sitesTable;
- }
-
- /**
- * Constructs a cache key to use for caching the list of sites.
- *
- * This includes the concrete class name of the site list as well as a version identifier
- * for the list's serialization, to avoid problems when unserializing site lists serialized
- * by an older version, e.g. when reading from a cache.
- *
- * The cache key also includes information about where the sites were loaded from, e.g.
- * the name of a database table.
- *
- * @see SiteList::getSerialVersionId
- *
- * @return string The cache key.
- */
- protected function getCacheKey() {
- wfProfileIn( __METHOD__ );
-
- if ( $this->cacheKey === null ) {
- $type = 'SiteList#' . SiteList::getSerialVersionId();
- $source = $this->sitesTable->getName();
-
- if ( $this->sitesTable->getTargetWiki() !== false ) {
- $source = $this->sitesTable->getTargetWiki() . '.' . $source;
- }
-
- $this->cacheKey = wfMemcKey( "$source/$type" );
- }
-
- wfProfileOut( __METHOD__ );
- return $this->cacheKey;
- }
-
- /**
- * @see SiteStore::getSites
- *
- * @since 1.21
- *
- * @param string $source Either 'cache' or 'recache'
- *
- * @return SiteList
- */
- public function getSites( $source = 'cache' ) {
- wfProfileIn( __METHOD__ );
-
- if ( $source === 'cache' ) {
- if ( $this->sites === null ) {
- $cache = wfGetMainCache();
- $sites = $cache->get( $this->getCacheKey() );
-
- if ( is_object( $sites ) ) {
- $this->sites = $sites;
- } else {
- $this->loadSites();
- }
- }
- }
- else {
- $this->loadSites();
- }
-
- wfProfileOut( __METHOD__ );
- return $this->sites;
- }
-
- /**
- * Returns a new Site object constructed from the provided ORMRow.
- *
- * @since 1.21
- *
- * @param ORMRow $siteRow
- *
- * @return Site
- */
- protected function siteFromRow( ORMRow $siteRow ) {
- wfProfileIn( __METHOD__ );
-
- $site = Site::newForType( $siteRow->getField( 'type', Site::TYPE_UNKNOWN ) );
-
- $site->setGlobalId( $siteRow->getField( 'global_key' ) );
-
- $site->setInternalId( $siteRow->getField( 'id' ) );
-
- if ( $siteRow->hasField( 'forward' ) ) {
- $site->setForward( $siteRow->getField( 'forward' ) );
- }
-
- if ( $siteRow->hasField( 'group' ) ) {
- $site->setGroup( $siteRow->getField( 'group' ) );
- }
-
- if ( $siteRow->hasField( 'language' ) ) {
- $site->setLanguageCode( $siteRow->getField( 'language' ) === ''
- ? null
- : $siteRow->getField( 'language' )
- );
- }
-
- if ( $siteRow->hasField( 'source' ) ) {
- $site->setSource( $siteRow->getField( 'source' ) );
- }
-
- if ( $siteRow->hasField( 'data' ) ) {
- $site->setExtraData( $siteRow->getField( 'data' ) );
- }
-
- if ( $siteRow->hasField( 'config' ) ) {
- $site->setExtraConfig( $siteRow->getField( 'config' ) );
- }
-
- wfProfileOut( __METHOD__ );
- return $site;
- }
-
- /**
- * Get a new ORMRow from a Site object
- *
- * @since 1.22
- *
- * @param Site $site
- *
- * @return ORMRow
- */
- protected function getRowFromSite( Site $site ) {
- $fields = array(
- // Site data
- 'global_key' => $site->getGlobalId(), // TODO: check not null
- 'type' => $site->getType(),
- 'group' => $site->getGroup(),
- 'source' => $site->getSource(),
- 'language' => $site->getLanguageCode() === null ? '' : $site->getLanguageCode(),
- 'protocol' => $site->getProtocol(),
- 'domain' => strrev( $site->getDomain() ) . '.',
- 'data' => $site->getExtraData(),
-
- // Site config
- 'forward' => $site->shouldForward(),
- 'config' => $site->getExtraConfig(),
- );
-
- if ( $site->getInternalId() !== null ) {
- $fields['id'] = $site->getInternalId();
- }
-
- return new ORMRow( $this->sitesTable, $fields );
- }
-
- /**
- * Fetches the site from the database and loads them into the sites field.
- *
- * @since 1.21
- */
- protected function loadSites() {
- wfProfileIn( __METHOD__ );
-
- $this->sites = new SiteList();
-
- foreach ( $this->sitesTable->select() as $siteRow ) {
- $this->sites[] = $this->siteFromRow( $siteRow );
- }
-
- // Batch load the local site identifiers.
- $ids = wfGetDB( $this->sitesTable->getReadDb() )->select(
- 'site_identifiers',
- array(
- 'si_site',
- 'si_type',
- 'si_key',
- ),
- array(),
- __METHOD__
- );
-
- foreach ( $ids as $id ) {
- if ( $this->sites->hasInternalId( $id->si_site ) ) {
- $site = $this->sites->getSiteByInternalId( $id->si_site );
- $site->addLocalId( $id->si_type, $id->si_key );
- $this->sites->setSite( $site );
- }
- }
-
- $cache = wfGetMainCache();
- $cache->set( $this->getCacheKey(), $this->sites, $this->cacheTimeout );
-
- wfProfileOut( __METHOD__ );
- }
-
- /**
- * @see SiteStore::getSite
- *
- * @since 1.21
- *
- * @param string $globalId
- * @param string $source
- *
- * @return Site|null
- */
- public function getSite( $globalId, $source = 'cache' ) {
- wfProfileIn( __METHOD__ );
-
- $sites = $this->getSites( $source );
-
- wfProfileOut( __METHOD__ );
- return $sites->hasSite( $globalId ) ? $sites->getSite( $globalId ) : null;
- }
-
- /**
- * @see SiteStore::saveSite
- *
- * @since 1.21
- *
- * @param Site $site
- *
- * @return bool Success indicator
- */
- public function saveSite( Site $site ) {
- return $this->saveSites( array( $site ) );
- }
-
- /**
- * @see SiteStore::saveSites
- *
- * @since 1.21
- *
- * @param Site[] $sites
- *
- * @return bool Success indicator
- */
- public function saveSites( array $sites ) {
- wfProfileIn( __METHOD__ );
-
- if ( empty( $sites ) ) {
- wfProfileOut( __METHOD__ );
- return true;
- }
-
- $dbw = $this->sitesTable->getWriteDbConnection();
-
- $dbw->startAtomic( __METHOD__ );
-
- $success = true;
-
- $internalIds = array();
- $localIds = array();
-
- foreach ( $sites as $site ) {
- if ( $site->getInternalId() !== null ) {
- $internalIds[] = $site->getInternalId();
- }
-
- $siteRow = $this->getRowFromSite( $site );
- $success = $siteRow->save( __METHOD__ ) && $success;
-
- foreach ( $site->getLocalIds() as $idType => $ids ) {
- foreach ( $ids as $id ) {
- $localIds[] = array( $siteRow->getId(), $idType, $id );
- }
- }
- }
-
- if ( $internalIds !== array() ) {
- $dbw->delete(
- 'site_identifiers',
- array( 'si_site' => $internalIds ),
- __METHOD__
- );
- }
-
- foreach ( $localIds as $localId ) {
- $dbw->insert(
- 'site_identifiers',
- array(
- 'si_site' => $localId[0],
- 'si_type' => $localId[1],
- 'si_key' => $localId[2],
- ),
- __METHOD__
- );
+ public static function newInstance( ORMTable $sitesTable = null, BagOStuff $cache = null ) {
+ if ( $cache === null ) {
+ $cache = wfGetMainCache();
}
- $dbw->endAtomic( __METHOD__ );
+ $siteStore = new DBSiteStore();
- // purge cache
- $this->reset();
-
- wfProfileOut( __METHOD__ );
- return $success;
+ return new static( $siteStore, $cache );
}
- /**
- * Purges the internal and external cache of the site list, forcing the list
- * of sites to be re-read from the database.
- *
- * @since 1.21
- */
- public function reset() {
- wfProfileIn( __METHOD__ );
- // purge cache
- $cache = wfGetMainCache();
- $cache->delete( $this->getCacheKey() );
- $this->sites = null;
-
- wfProfileOut( __METHOD__ );
- }
-
- /**
- * Clears the list of sites stored in the database.
- *
- * @see SiteStore::clear()
- *
- * @return bool Success
- */
- public function clear() {
- wfProfileIn( __METHOD__ );
- $dbw = $this->sitesTable->getWriteDbConnection();
-
- $dbw->startAtomic( __METHOD__ );
- $ok = $dbw->delete( 'sites', '*', __METHOD__ );
- $ok = $dbw->delete( 'site_identifiers', '*', __METHOD__ ) && $ok;
- $dbw->endAtomic( __METHOD__);
-
- $this->reset();
-
- wfProfileOut( __METHOD__ );
- return $ok;
- }
-
- /**
- * @since 1.21
- *
- * @return ORMTable
- */
- protected function newSitesTable() {
- return new ORMTable(
- 'sites',
- array(
- 'id' => 'id',
-
- // Site data
- 'global_key' => 'str',
- 'type' => 'str',
- 'group' => 'str',
- 'source' => 'str',
- 'language' => 'str',
- 'protocol' => 'str',
- 'domain' => 'str',
- 'data' => 'array',
-
- // Site config
- 'forward' => 'bool',
- 'config' => 'array',
- ),
- array(
- 'type' => Site::TYPE_UNKNOWN,
- 'group' => Site::GROUP_NONE,
- 'source' => Site::SOURCE_LOCAL,
- 'data' => array(),
-
- 'forward' => false,
- 'config' => array(),
- 'language' => '',
- ),
- 'ORMRow',
- 'site_'
- );
- }
-
-}
-
-/**
- * @deprecated since 1.21
- */
-class Sites extends SiteSQLStore {
-
- /**
- * Factory for creating new site objects.
- *
- * @since 1.21
- * @deprecated since 1.21
- *
- * @param string|bool $globalId
- *
- * @return Site
- */
- public static function newSite( $globalId = false ) {
- $site = new Site();
-
- if ( $globalId !== false ) {
- $site->setGlobalId( $globalId );
- }
-
- return $site;
- }
-
- /**
- * @deprecated since 1.21
- * @return SiteStore
- */
- public static function singleton() {
- static $singleton;
-
- if ( $singleton === null ) {
- $singleton = new static();
- }
-
- return $singleton;
- }
-
- /**
- * @deprecated since 1.21
- * @param string $group
- * @return SiteList
- */
- public function getSiteGroup( $group ) {
- return $this->getSites()->getGroup( $group );
- }
}
diff --git a/includes/site/SiteStore.php b/includes/site/SiteStore.php
index 537f1ccb..10e0c1b9 100644
--- a/includes/site/SiteStore.php
+++ b/includes/site/SiteStore.php
@@ -26,7 +26,7 @@
* @license GNU GPL v2+
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
*/
-interface SiteStore {
+interface SiteStore extends SiteLookup {
/**
* Saves the provided site.
@@ -51,33 +51,6 @@ interface SiteStore {
public function saveSites( array $sites );
/**
- * Returns the site with provided global id, or null if there is no such site.
- *
- * @since 1.21
- *
- * @param string $globalId
- * @param string $source Either 'cache' or 'recache'.
- * If 'cache', the values are allowed (but not obliged) to come from a cache.
- *
- * @return Site|null
- */
- public function getSite( $globalId, $source = 'cache' );
-
- /**
- * Returns a list of all sites. By default this site is
- * fetched from the cache, which can be changed to loading
- * the list from the database using the $useCache parameter.
- *
- * @since 1.21
- *
- * @param string $source Either 'cache' or 'recache'.
- * If 'cache', the values are allowed (but not obliged) to come from a cache.
- *
- * @return SiteList
- */
- public function getSites( $source = 'cache' );
-
- /**
* Deletes all sites from the database. After calling clear(), getSites() will return an empty
* list and getSite() will return null until saveSite() or saveSites() is called.
*/
diff --git a/includes/site/SitesCacheFileBuilder.php b/includes/site/SitesCacheFileBuilder.php
new file mode 100644
index 00000000..2e420409
--- /dev/null
+++ b/includes/site/SitesCacheFileBuilder.php
@@ -0,0 +1,113 @@
+<?php
+
+/**
+ * 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.25
+ *
+ * @file
+ *
+ * @license GNU GPL v2+
+ */
+class SitesCacheFileBuilder {
+
+ /**
+ * @var SiteLookup
+ */
+ private $siteLookup;
+
+ /**
+ * @var string
+ */
+ private $cacheFile;
+
+ /**
+ * @param SiteLookup $siteLookup
+ * @param string $cacheFile
+ */
+ public function __construct( SiteLookup $siteLookup, $cacheFile ) {
+ $this->siteLookup = $siteLookup;
+ $this->cacheFile = $cacheFile;
+ }
+
+ public function build() {
+ $this->sites = $this->siteLookup->getSites();
+ $this->cacheSites( $this->sites->getArrayCopy() );
+ }
+
+ /**
+ * @param Site[] $sites
+ *
+ * @throws MWException if in manualRecache mode
+ * @return bool
+ */
+ private function cacheSites( array $sites ) {
+ $sitesArray = array();
+
+ foreach ( $sites as $site ) {
+ $globalId = $site->getGlobalId();
+ $sitesArray[$globalId] = $this->getSiteAsArray( $site );
+ }
+
+ $json = json_encode( array(
+ 'sites' => $sitesArray
+ ) );
+
+ $result = file_put_contents( $this->cacheFile, $json );
+
+ return $result !== false;
+ }
+
+ /**
+ * @param Site $site
+ *
+ * @return array
+ */
+ private function getSiteAsArray( Site $site ) {
+ $siteEntry = unserialize( $site->serialize() );
+ $siteIdentifiers = $this->buildLocalIdentifiers( $site );
+ $identifiersArray = array();
+
+ foreach ( $siteIdentifiers as $identifier ) {
+ $identifiersArray[] = $identifier;
+ }
+
+ $siteEntry['identifiers'] = $identifiersArray;
+
+ return $siteEntry;
+ }
+
+ /**
+ * @param Site $site
+ *
+ * @return array Site local identifiers
+ */
+ private function buildLocalIdentifiers( Site $site ) {
+ $localIds = array();
+
+ foreach ( $site->getLocalIds() as $idType => $ids ) {
+ foreach ( $ids as $id ) {
+ $localIds[] = array(
+ 'type' => $idType,
+ 'key' => $id
+ );
+ }
+ }
+
+ return $localIds;
+ }
+
+}