'BmpHandler' => 'includes/media/BMP.php',
'DjVuHandler' => 'includes/media/DjVu.php',
'Exif' => 'includes/media/Exif.php',
+ 'GlobalUsageQuery' => 'includes/GlobalUsageQuery.php',
'FormatExif' => 'includes/media/FormatMetadata.php',
'FormatMetadata' => 'includes/media/FormatMetadata.php',
'GIFHandler' => 'includes/media/GIF.php',
'SpecialEmailUser' => 'includes/specials/SpecialEmailuser.php',
'SpecialExport' => 'includes/specials/SpecialExport.php',
'SpecialFilepath' => 'includes/specials/SpecialFilepath.php',
+ 'SpecialGlobalFileUsage' => 'includes/specials/SpecialGlobalFileUsage.php',
+ 'SpecialGlobalTemplateUsage' => 'includes/specials/SpecialGlobalTemplateUsage.php',
'SpecialImport' => 'includes/specials/SpecialImport.php',
'SpecialListFiles' => 'includes/specials/SpecialListfiles.php',
'SpecialListGroupRights' => 'includes/specials/SpecialListgrouprights.php',
return $ta;
}
+ /**
+ * Get the distant backtemplatelinks for the table globaltemplatelinks. Cached in process memory only.
+ * @return ResultWrapper list of distant pages that use the local title
+ */
+ public function getDistantTemplateLinks( ) {
+ global $wgGlobalDatabase, $wgLocalInterwiki;
+
+ $dbr = $dbr = wfGetDB( DB_SLAVE, array(), $wgGlobalDatabase );
+ $res = $dbr->select(
+ array( 'globaltemplatelinks', 'globalinterwiki' ),
+ array( 'gtl_from_wiki', 'gtl_from_page', 'gtl_from_title', 'giw_prefix' ),
+ array( 'gtl_to_prefix' => $wgLocalInterwiki, 'gtl_to_title' => $this->title->getDBkey( ) ),
+ __METHOD__,
+ null,
+ array( 'gtl_from_wiki = giw_wikiid' )
+ );
+ return $res;
+ }
+
/**
* Get the field name prefix for a given table
* @param $table String
'categorylinks' => 'cl',
'templatelinks' => 'tl',
'redirect' => 'rd',
+ 'globaltemplatelinks' => 'gtl',
);
if ( isset( $prefixes[$table] ) ) {
$wgPreprocessorCacheThreshold = 1000;
/**
- * Enable interwiki transcluding. Only when iw_trans=1.
+ * Enable interwiki transcluding. Only when iw_trans=1 in the interwiki table.
+ * If the interwiki prefix is associated with a wiki ID in the interwiki table,
+ * then the distant templates will be retrieved in the distant DB. If there is
+ * no wiki ID but a API URL for that prefix, the distant templates will be
+ * retrieved using the API and cached in memcached.
*/
-$wgEnableScaryTranscluding = false;
+$wgEnableInterwikiTranscluding = false;
/**
- * Expiry time for interwiki transclusion
+ * If $wgEnableInterwikiTranscluding is set to true and if an interwiki prefix
+ * is associated with a wiki ID, then, this option should be set to true to
+ * enable the cache invalidation of the distant pages when the local templates
+ * are edited and also to display the list of the distant templates used by
+ * the local pages. Enabling this requires to set up a global shared database
+ * (see next option $wgGlobalDatabase).
+ */
+$wgEnableInterwikiTemplatesTracking = false;
+
+/**
+ * If $wgEnableInterwikiTemplatesTracking is set to true, this option should
+ * contain the wiki ID of the database that hosts the globaltemplatelinks table.
+ */
+$wgGlobalDatabase = '';
+
+/**
+ * If $wgEnableInterwikiTranscluding is set to true and if an interwiki
+ * prefix is associated with an API URL and no wiki ID, this will be
+ * the expiry time for the transcluded templates cached in memcached.
*/
$wgTranscludeCacheExpiry = 3600;
'Export' => 'pagetools',
'Import' => 'pagetools',
'Whatlinkshere' => 'pagetools',
+ 'GlobalFileUsage' => 'pagetools',
+ 'GlobalTemplateUsage' => 'pagetools',
'Statistics' => 'wiki',
'Version' => 'wiki',
* during form output near the top, for captchas and the like.
*/
function showEditForm( $formCallback = null ) {
- global $wgOut, $wgUser;
+ global $wgOut, $wgUser, $wgEnableInterwikiTranscluding, $wgEnableInterwikiTemplatesTracking;
wfProfileIn( __METHOD__ );
$toolbar = '';
}
-
$wgOut->addHTML( $this->editFormPageTop );
if ( $wgUser->getOption( 'previewontop' ) ) {
$templates = $this->getTemplates();
$formattedtemplates = Linker::formatTemplates( $templates, $this->preview, $this->section != '');
+ $distantTemplates = $this->getDistantTemplates();
+ $formattedDistantTemplates = Linker::formatDistantTemplates( $distantTemplates, $this->preview, $this->section != '' );
+
$hiddencats = $this->mArticle->getHiddenCategories();
$formattedhiddencats = Linker::formatHiddenCategories( $hiddencats );
<div class='templatesUsed'>
{$formattedtemplates}
</div>
+HTML
+);
+
+ if ( $wgEnableInterwikiTranscluding && $wgEnableInterwikiTemplatesTracking ) {
+ $wgOut->addHTML( <<<HTML
+{$this->editFormTextAfterTools}
+<div class='distantTemplatesUsed'>
+{$formattedDistantTemplates}
+</div>
+HTML
+);
+ }
+
+ $wgOut->addHTML( <<<HTML
+{$this->editFormTextAfterTools}
<div class='hiddencats'>
{$formattedhiddencats}
</div>
}
}
+ function getDistantTemplates() {
+ global $wgEnableInterwikiTemplatesTracking;
+ if ( !$wgEnableInterwikiTemplatesTracking ) {
+ return array( );
+ }
+ if ( $this->preview || $this->section != '' ) {
+ $templates = array();
+ if ( !isset( $this->mParserOutput ) ) return $templates;
+ $templatesList = $this->mParserOutput->getDistantTemplates();
+ foreach( $templatesList as $prefix => $templatesbyns ) {
+ foreach( $templatesbyns as $ns => $template ) {
+ foreach( array_keys( $template ) as $dbk ) {
+ $templates[] = Title::makeTitle( $ns, $dbk, null, $prefix );
+ }
+ }
+ }
+ return $templates;
+ } else {
+ return $this->mArticle->getUsedDistantTemplates();
+ }
+ }
+
/**
* Call the stock "user is blocked" page
*/
--- /dev/null
+<?php
+/**
+ * A helper class to query the globalimagelinks table
+ *
+ */
+class GlobalUsageQuery {
+ private $limit = 50;
+ private $offset;
+ private $hasMore = false;
+ private $filterLocal = false;
+ private $result;
+ private $continue;
+ private $reversed = false;
+ private $target = null;
+
+ /**
+ * @param $target mixed Title or db key, or array of db keys of target(s)
+ */
+ public function __construct( $target ) {
+ global $wgGlobalDatabase;
+ $this->db = wfGetDB( DB_SLAVE, array(), $wgGlobalDatabase );
+ if ( $target instanceof Title && $target->getNamespace( ) == NS_FILE ) {
+ $this->target = $target->getDBKey();
+ } else {
+ $this->target = $target;
+ }
+ $this->offset = array();
+
+ }
+
+ /**
+ * Set the offset parameter
+ *
+ * @param $offset string offset
+ * @param $reversed bool True if this is the upper offset
+ */
+ public function setOffset( $offset, $reversed = null ) {
+ if ( !is_null( $reversed ) ) {
+ $this->reversed = $reversed;
+ }
+
+ if ( !is_array( $offset ) ) {
+ $offset = explode( '|', $offset );
+ }
+
+ if ( count( $offset ) == 3 ) {
+ $this->offset = $offset;
+ return true;
+ } else {
+ return false;
+ }
+ }
+ /**
+ * Return the offset set by the user
+ *
+ * @return array offset
+ */
+ public function getOffsetString() {
+ return implode( '|', $this->offset );
+ }
+ /**
+ * Is the result reversed
+ *
+ * @return bool
+ */
+ public function isReversed() {
+ return $this->reversed;
+ }
+
+ /**
+ * Returns the string used for continuation in a file search
+ *
+ * @return string
+ *
+ */
+ public function getContinueFileString() {
+ if ( $this->hasMore() ) {
+ return "{$this->lastRow->gil_to}|{$this->lastRow->gil_wiki}|{$this->lastRow->gil_page}";
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * Returns the string used for continuation in a template search
+ *
+ * @return string
+ *
+ */
+ public function getContinueTemplateString() {
+ if ( $this->hasMore() ) {
+ return "{$this->lastRow->gtl_to_title}|{$this->lastRow->gtl_from_wiki}|{$this->lastRow->gtl_from_page}";
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * Set the maximum amount of items to return. Capped at 500.
+ *
+ * @param $limit int The limit
+ */
+ public function setLimit( $limit ) {
+ $this->limit = min( $limit, 500 );
+ }
+ /**
+ * Returns the user set limit
+ */
+ public function getLimit() {
+ return $this->limit;
+ }
+
+ /**
+ * Set whether to filter out the local usage
+ */
+ public function filterLocal( $value = true ) {
+ $this->filterLocal = $value;
+ }
+
+ /**
+ * Executes the query for a file search
+ */
+ public function searchTemplate() {
+ global $wgLocalInterwiki;
+
+ /* Construct a where clause */
+ // Add target template(s)
+ $where = array( 'gtl_to_prefix' => $wgLocalInterwiki,
+ 'gtl_to_namespace' => $this->target->getNamespace( ),
+ 'gtl_to_title' => $this->target->getDBkey( )
+ );
+
+ // Set the continuation condition
+ $order = 'ASC';
+ if ( $this->offset ) {
+ $qTo = $this->db->addQuotes( $this->offset[0] );
+ $qWiki = $this->db->addQuotes( $this->offset[1] );
+ $qPage = intval( $this->offset[2] );
+
+ // Check which limit we got in order to determine which way to traverse rows
+ if ( $this->reversed ) {
+ // Reversed traversal; do not include offset row
+ $op1 = '<';
+ $op2 = '<';
+ $order = 'DESC';
+ } else {
+ // Normal traversal; include offset row
+ $op1 = '>';
+ $op2 = '>=';
+ $order = 'ASC';
+ }
+
+ $where[] = "(gtl_to_title $op1 $qTo) OR " .
+ "(gtl_to_title = $qTo AND gtl_from_wiki $op1 $qWiki) OR " .
+ "(gtl_to_title = $qTo AND gtl_from_wiki = $qWiki AND gtl_from_page $op2 $qPage)";
+ }
+
+ /* Perform select (Duh.) */
+ $res = $this->db->select(
+ array(
+ 'globaltemplatelinks',
+ 'globalnamespaces'
+ ),
+ array(
+ 'gtl_to_title',
+ 'gtl_from_wiki',
+ 'gtl_from_page',
+ 'gtl_from_namespace',
+ 'gtl_from_title'
+ ),
+ $where,
+ __METHOD__,
+ array(
+ 'ORDER BY' => "gtl_to_title $order, gtl_from_wiki $order, gtl_from_page $order",
+ // Select an extra row to check whether we have more rows available
+ 'LIMIT' => $this->limit + 1,
+ ),
+ array(
+ 'gtl_from_namespace = gn_namespace'
+ )
+ );
+
+ /* Process result */
+ // Always return the result in the same order; regardless whether reversed was specified
+ // reversed is really only used to determine from which direction the offset is
+ $rows = array();
+ foreach ( $res as $row ) {
+ $rows[] = $row;
+ }
+ if ( $this->reversed ) {
+ $rows = array_reverse( $rows );
+ }
+
+ // Build the result array
+ $count = 0;
+ $this->hasMore = false;
+ $this->result = array();
+ foreach ( $rows as $row ) {
+ $count++;
+ if ( $count > $this->limit ) {
+ // We've reached the extra row that indicates that there are more rows
+ $this->hasMore = true;
+ $this->lastRow = $row;
+ break;
+ }
+
+ if ( !isset( $this->result[$row->gtl_to_title] ) ) {
+ $this->result[$row->gtl_to_title] = array();
+ }
+ if ( !isset( $this->result[$row->gtl_to_title][$row->gtl_from_wiki] ) ) {
+ $this->result[$row->gtl_to_title][$row->gtl_from_wiki] = array();
+ }
+
+ $this->result[$row->gtl_to_title][$row->gtl_from_wiki][] = array(
+ 'template' => $row->gtl_to_title,
+ 'id' => $row->gtl_from_page,
+ 'namespace' => $row->gn_namespacetext,
+ 'title' => $row->gtl_from_title,
+ 'wiki' => $row->gtl_from_wiki,
+ );
+ }
+ }
+
+ /**
+ * Executes the query for a template search
+ */
+ public function searchFile() {
+ /* Construct a where clause */
+ // Add target image(s)
+ $where = array( 'gil_to' => $this->target );
+
+ if ( $this->filterLocal ) {
+ // Don't show local file usage
+ $where[] = 'gil_wiki != ' . $this->db->addQuotes( wfWikiId() );
+ }
+
+ // Set the continuation condition
+ $order = 'ASC';
+ if ( $this->offset ) {
+ $qTo = $this->db->addQuotes( $this->offset[0] );
+ $qWiki = $this->db->addQuotes( $this->offset[1] );
+ $qPage = intval( $this->offset[2] );
+
+ // Check which limit we got in order to determine which way to traverse rows
+ if ( $this->reversed ) {
+ // Reversed traversal; do not include offset row
+ $op1 = '<';
+ $op2 = '<';
+ $order = 'DESC';
+ } else {
+ // Normal traversal; include offset row
+ $op1 = '>';
+ $op2 = '>=';
+ $order = 'ASC';
+ }
+
+ $where[] = "(gil_to $op1 $qTo) OR " .
+ "(gil_to = $qTo AND gil_wiki $op1 $qWiki) OR " .
+ "(gil_to = $qTo AND gil_wiki = $qWiki AND gil_page $op2 $qPage)";
+ }
+
+ /* Perform select (Duh.) */
+ $res = $this->db->select( 'globalimagelinks',
+ array(
+ 'gil_to',
+ 'gil_wiki',
+ 'gil_page',
+ 'gil_page_namespace',
+ 'gil_page_title'
+ ),
+ $where,
+ __METHOD__,
+ array(
+ 'ORDER BY' => "gil_to $order, gil_wiki $order, gil_page $order",
+ // Select an extra row to check whether we have more rows available
+ 'LIMIT' => $this->limit + 1,
+ )
+ );
+
+ /* Process result */
+ // Always return the result in the same order; regardless whether reversed was specified
+ // reversed is really only used to determine from which direction the offset is
+ $rows = array();
+ foreach ( $res as $row ) {
+ $rows[] = $row;
+ }
+ if ( $this->reversed ) {
+ $rows = array_reverse( $rows );
+ }
+
+ // Build the result array
+ $count = 0;
+ $this->hasMore = false;
+ $this->result = array();
+ foreach ( $rows as $row ) {
+ $count++;
+ if ( $count > $this->limit ) {
+ // We've reached the extra row that indicates that there are more rows
+ $this->hasMore = true;
+ $this->lastRow = $row;
+ break;
+ }
+
+ if ( !isset( $this->result[$row->gil_to] ) ) {
+ $this->result[$row->gil_to] = array();
+ }
+ if ( !isset( $this->result[$row->gil_to][$row->gil_wiki] ) ) {
+ $this->result[$row->gil_to][$row->gil_wiki] = array();
+ }
+
+ $this->result[$row->gil_to][$row->gil_wiki][] = array(
+ 'image' => $row->gil_to,
+ 'id' => $row->gil_page,
+ 'namespace' => $row->gil_page_namespace,
+ 'title' => $row->gil_page_title,
+ 'wiki' => $row->gil_wiki,
+ );
+ }
+ }
+
+ /**
+ * Returns the result set. The result is a 4 dimensional array
+ * (file, wiki, page), whose items are arrays with keys:
+ * - image or template: File name or template name
+ * - id: Page id
+ * - namespace: Page namespace text
+ * - title: Unprefixed page title
+ * - wiki: Wiki id
+ *
+ * @return array Result set
+ */
+ public function getResult() {
+ return $this->result;
+ }
+ /**
+ * Returns a 3 dimensional array with the result of the first file. Useful
+ * if only one resource was queried.
+ *
+ * For further information see documentation of getResult()
+ *
+ * @return array Result set
+ */
+ public function getSingleResult() {
+ if ( $this->result ) {
+ return current( $this->result );
+ } else {
+ return array();
+ }
+ }
+
+ /**
+ * Returns whether there are more results
+ *
+ * @return bool
+ */
+ public function hasMore() {
+ return $this->hasMore;
+ }
+
+ /**
+ * Returns the result length
+ *
+ * @return int
+ */
+ public function count() {
+ return count( $this->result );
+ }
+}
return $outText;
}
+ /**
+ * Returns HTML for the "templates used on this page" list.
+ *
+ * @param $templates Array of templates from Article::getUsedTemplate
+ * or similar
+ * @param $preview Boolean: whether this is for a preview
+ * @param $section Boolean: whether this is for a section edit
+ * @return String: HTML output
+ */
+ public static function formatDistantTemplates( $templates, $preview = false, $section = false ) {
+ wfProfileIn( __METHOD__ );
+
+ $outText = '';
+ if ( count( $templates ) > 0 ) {
+
+ # Construct the HTML
+ $outText = '<div class="mw-templatesUsedExplanation">';
+ if ( $preview ) {
+ $outText .= wfMsgExt( 'distanttemplatesusedpreview', array( 'parse' ), count( $templates ) );
+ } elseif ( $section ) {
+ $outText .= wfMsgExt( 'distanttemplatesusedsection', array( 'parse' ), count( $templates ) );
+ } else {
+ $outText .= wfMsgExt( 'distanttemplatesused', array( 'parse' ), count( $templates ) );
+ }
+ $outText .= "</div><ul>\n";
+
+ usort( $templates, array( 'Title', 'compare' ) );
+ foreach ( $templates as $titleObj ) {
+ $outText .= '<li>' . self::link( $titleObj ) . '</li>';
+ }
+ $outText .= '</ul>';
+ }
+ wfProfileOut( __METHOD__ );
+ return $outText;
+ }
+
/**
* Returns HTML for the "hidden categories on this page" list.
*
$mLinks, //!< Map of title strings to IDs for the links in the document
$mImages, //!< DB keys of the images used, in the array key only
$mTemplates, //!< Map of title strings to IDs for the template references, including broken ones
+ $mDistantTemplates,//!< Map of title strings to IDs for the distant template references, including broken ones
$mExternals, //!< URLs of external links, array key only
$mCategories, //!< Map of category names to sort keys
$mInterlangs, //!< Map of language codes to titles
$this->mLinks = $parserOutput->getLinks();
$this->mImages = $parserOutput->getImages();
$this->mTemplates = $parserOutput->getTemplates();
+ $this->mDistantTemplates = $parserOutput->getDistantTemplates();
$this->mExternals = $parserOutput->getExternalLinks();
$this->mCategories = $parserOutput->getCategories();
$this->mProperties = $parserOutput->getProperties();
$this->incrTableUpdate( 'templatelinks', 'tl', $this->getTemplateDeletions( $existing ),
$this->getTemplateInsertions( $existing ) );
+ # Distant template links
+ global $wgGlobalDB;
+ if ( $wgGlobalDB ) {
+ $existing = $this->getDistantExistingTemplates();
+ $this->incrSharedTableUpdate( 'globaltemplatelinks', 'gtl',
+ $this->getDistantTemplateDeletions( $existing ),
+ $this->getDistantTemplateInsertions( $existing ) );
+ }
+
# Category links
$existing = $this->getExistingCategories();
if ( $where ) {
$this->mDb->delete( $table, $where, __METHOD__ );
}
+ if ( isset( $insertions['globaltemplatelinks'] ) ) {
+ $this->mDb->insert( 'globaltemplatelinks', $insertions['globaltemplatelinks'], __METHOD__, 'IGNORE' );
+ unset( $insertions['globaltemplatelinks'] );
+ }
+ if ( isset( $insertions['globalnamespaces'] ) ) {
+ $this->mDb->insert( 'globalnamespaces', $insertions['globalnamespaces'], __METHOD__, 'IGNORE' );
+ unset( $insertions['globalnamespaces'] );
+ }
+ if ( isset( $insertions['globalinterwiki'] ) ) {
+ $this->mDb->insert( 'globalinterwiki', $insertions['globalinterwiki'], __METHOD__, 'IGNORE' );
+ unset( $insertions['globalinterwiki'] );
+ }
if ( count( $insertions ) ) {
$this->mDb->insert( $table, $insertions, __METHOD__, 'IGNORE' );
}
}
+ /**
+ * Update a shared table by doing a delete query then an insert query
+ * @private
+ */
+ function incrSharedTableUpdate( $table, $prefix, $deletions, $insertions ) {
+
+ global $wgWikiID;
+ global $wgGlobalDB;
+
+ if ( $wgGlobalDB ) {
+ $dbw = wfGetDB( DB_MASTER, array(), $wgGlobalDB );
+ $where = array( "{$prefix}_from_wiki" => $wgWikiID,
+ "{$prefix}_from_page" => $this->mId
+ );
+ $baseKey = "{$prefix}_to_wiki";
+ $middleKey = "{$prefix}_to_namespace";
+
+ $clause = $dbw->makeWhereFrom3d( $deletions, $baseKey, $middleKey, "{$prefix}_to_title" );
+ if ( $clause ) {
+ $where[] = $clause;
+ } else {
+ $where = false;
+ }
+
+ if ( $where ) {
+ $dbw->delete( $table, $where, __METHOD__ );
+ }
+ if ( count( $insertions ) ) {
+ $dbw->insert( $table, $insertions, __METHOD__, 'IGNORE' );
+ }
+ }
+ }
/**
* Get an array of pagelinks insertions for passing to the DB
return $arr;
}
+ /**
+ * Get an array of distant template insertions. Like getLinkInsertions()
+ * @private
+ */
+ function getDistantTemplateInsertions( $existing = array() ) {
+ global $wgWikiID;
+ $arr = array();
+ foreach( $this->mDistantTemplates as $wikiid => $templatesToNS ) {
+ foreach( $templatesToNS as $ns => $dbkeys ) {
+ $diffs = isset( $existing[$wikiid] ) && isset( $existing[$wikiid][$ns] )
+ ? array_diff_key( $dbkeys, $existing[$wikiid][$ns] )
+ : $dbkeys;
+ $interwiki = Interwiki::fetch( $wikiid );
+ $wikiid = $interwiki->getWikiID();
+ foreach ( $diffs as $dbk => $id ) {
+ $arr['globaltemplatelinks'][] = array(
+ 'gtl_from_wiki' => $wgWikiID,
+ 'gtl_from_page' => $this->mId,
+ 'gtl_from_namespace' => $this->mTitle->getNamespace(),
+ 'gtl_from_title' => $this->mTitle->getText(),
+ 'gtl_to_wiki' => $wikiid,
+ 'gtl_to_namespace' => $ns,
+ 'gtl_to_title' => $dbk
+ );
+ $arr['globalinterwiki'][] = array(
+ 'giw_wikiid' => $wikiid,
+ 'giw_prefix' => $prefix // FIXME: $prefix ix undefined
+ );
+ $arr['globalnamespaces'][] = array(
+ 'gn_wiki' => wfWikiID( ),
+ 'gn_namespace' => $this->mTitle->getNamespace(),
+ 'gn_namespacetext' => $this->mTitle->getNsText(),
+ );
+ }
+ }
+ }
+ return $arr;
+ }
+
/**
* Get an array of image insertions
* Skips the names specified in $existing
return $del;
}
+ /**
+ * Given an array of existing templates, returns those templates which are not in $this
+ * and thus should be deleted.
+ * @private
+ */
+ function getDistantTemplateDeletions( $existing ) {
+ $del = array();
+ foreach ( $existing as $wikiid => $templatesForNS ) {
+ if ( isset( $this->mDistantTemplates[$wikiid] ) ) {
+ $del[$wikiid] = array_diff_key( $existing[$wikiid], $this->mDistantTemplates[$wikiid] );
+ } else {
+ $del[$wikiid] = $existing[$wikiid];
+ }
+ foreach ( $templatesForNS as $ns => $dbkeys ) {
+ if ( isset( $this->mDistantTemplates[$wikiid][$ns] ) ) {
+ $del[$wikiid][$ns] = array_diff_key( $existing[$wikiid][$ns], $this->mDistantTemplates[$wikiid][$ns] );
+ } else {
+ $del[$wikiid][$ns] = $existing[$wikiid][$ns];
+ }
+ }
+ }
+ return $del;
+ }
+
/**
* Given an array of existing images, returns those images which are not in $this
* and thus should be deleted.
return $arr;
}
+ /**
+ * Get an array of existing distant templates, as a 3-D array
+ * @private
+ */
+ function getDistantExistingTemplates() {
+ global $wgWikiID;
+ global $wgGlobalDB;
+
+ $arr = array();
+ if ( $wgGlobalDB ) {
+ $dbr = wfGetDB( DB_SLAVE, array(), $wgGlobalDB );
+ $res = $dbr->select( 'globaltemplatelinks', array( 'gtl_to_wiki', 'gtl_to_namespace', 'gtl_to_title' ),
+ array( 'gtl_from_wiki' => $wgWikiID, 'gtl_from_page' => $this->mId ), __METHOD__, $this->mOptions );
+ while ( $row = $dbr->fetchObject( $res ) ) {
+ if ( !isset( $arr[$row->gtl_to_wiki] ) ) {
+ $arr[$row->gtl_to_wiki] = array();
+ }
+ if ( !isset( $arr[$row->gtl_to_wiki][$row->gtl_to_namespace] ) ) {
+ $arr[$row->gtl_to_wiki][$row->gtl_to_namespace] = array();
+ }
+ $arr[$row->gtl_to_wiki][$row->gtl_to_namespace][$row->gtl_to_title] = 1;
+ }
+ $dbr->freeResult( $res );
+ }
+ return $arr;
+ }
+
/**
* Get an array of existing images, image names in the keys
* @private
* @param $action String: action that was denied or null if unknown
*/
public function readOnlyPage( $source = null, $protected = false, $reasons = array(), $action = null ) {
+ global $wgUser, $wgEnableInterwikiTranscluding, $wgEnableInterwikiTemplatesTracking;
+ $skin = $wgUser->getSkin();
+
$this->setRobotPolicy( 'noindex,nofollow' );
$this->setArticleRelated( false );
$templates
</div>
" );
+ if ( $wgEnableInterwikiTranscluding && $wgEnableInterwikiTemplatesTracking ) {
+ $this->addHTML( "<div class='distantTemplatesUsed'>
+{$skin->formatDistantTemplates( $article->getUsedDistantTemplates( ) )}
+</div>
+" );
+ }
}
# If the title doesn't exist, it's fairly pointless to print a return
return Revision::loadFromConds( $db, $conds );
}
+ /**
+ * Stores the origin wiki of a revision in case it is a foreign wiki
+ */
+ function setWikiID( $wikiID ) {
+ $this->mWikiID = $wikiID;
+ }
+
+ /**
+ * Load the current revision of a given page of a foreign wiki.
+ * The WikiID is stored for further use, such as loadText() and getTimestampFromId()
+ */
+ public static function loadFromTitleForeignWiki( $wikiID, $title ) {
+ $dbr = wfGetDB( DB_SLAVE, array(), $wikiID );
+
+ $revision = self::loadFromTitle( $dbr, $title );
+
+ if( $revision ) {
+ $revision->setWikiID( $wikiID );
+ }
+
+ return $revision;
+
+ }
+
/**
* Load either the current, or a specified, revision
* that's attached to a given page. If not attached
throw new MWException( 'Revision constructor passed invalid row format.' );
}
$this->mUnpatrolled = null;
+ $this->mWikiID = false;
}
/**
if( isset( $this->mTitle ) ) {
return $this->mTitle;
}
- $dbr = wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_SLAVE, array(), $this->mWikiID );
+
$row = $dbr->selectRow(
array( 'page', 'revision' ),
array( 'page_namespace', 'page_title' ),
if( $this->mUnpatrolled !== null ) {
return $this->mUnpatrolled;
}
- $dbr = wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_SLAVE, array(), $this->mWikiID );
$this->mUnpatrolled = $dbr->selectField( 'recentchanges',
'rc_id',
array( // Add redundant user,timestamp condition so we can use the existing index
// Caching may be beneficial for massive use of external storage
global $wgRevisionCacheExpiry, $wgMemc;
$textId = $this->getTextId();
+ if( isset( $this->mWikiID ) && $this->mWikiID !== false ) {
+ $key = wfForeignMemcKey( $this->mWikiID, null, 'revisiontext', 'textid', $textId );
+ } else {
$key = wfMemcKey( 'revisiontext', 'textid', $textId );
+ }
if( $wgRevisionCacheExpiry ) {
$text = $wgMemc->get( $key );
if( is_string( $text ) ) {
if( !$row ) {
// Text data is immutable; check slaves first.
- $dbr = wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_SLAVE, array(), $this->mWikiID );
$row = $dbr->selectRow( 'text',
array( 'old_text', 'old_flags' ),
array( 'old_id' => $this->getTextId() ),
if( !$row && wfGetLB()->getServerCount() > 1 ) {
// Possible slave lag!
- $dbw = wfGetDB( DB_MASTER );
+ $dbw = wfGetDB( DB_MASTER, array(), $this->mWikiID );
$row = $dbw->selectRow( 'text',
array( 'old_text', 'old_flags' ),
array( 'old_id' => $this->getTextId() ),
* @return String
*/
static function getTimestampFromId( $title, $id ) {
- $dbr = wfGetDB( DB_SLAVE );
+ $wikiId = wfWikiID();
+ $dbr = wfGetDB( DB_SLAVE, array(), $wikiId );
// Casting fix for DB2
if ( $id == '' ) {
$id = 0;
$timestamp = $dbr->selectField( 'revision', 'rev_timestamp', $conds, __METHOD__ );
if ( $timestamp === false && wfGetLB()->getServerCount() > 1 ) {
# Not in slave, try master
- $dbw = wfGetDB( DB_MASTER );
+ $dbw = wfGetDB( DB_MASTER, array(), $wikiId );
$timestamp = $dbw->selectField( 'revision', 'rev_timestamp', $conds, __METHOD__ );
}
return wfTimestamp( TS_MW, $timestamp );
// Page tools
'ComparePages' => 'SpecialComparePages',
'Export' => 'SpecialExport',
+ 'GlobalFileUsage' => 'SpecialGlobalFileUsage',
+ 'GlobalTemplateUsage' => 'SpecialGlobalTemplateUsage',
'Import' => 'SpecialImport',
'Undelete' => 'SpecialUndelete',
'Whatlinkshere' => 'SpecialWhatlinkshere',
return $this->mPrefixedText;
}
+ /**
+ * Return the prefixed title with spaces _without_ the interwiki prefix
+ *
+ * @return \type{\string} the title, prefixed by the namespace but not by the interwiki prefix, with spaces
+ */
+ public function getSemiPrefixedText() {
+ if ( !isset( $this->mSemiPrefixedText ) ){
+ $s = ( $this->mNamespace === NS_MAIN ? '' : $this->getNsText() . ':' ) . $this->mTextform;
+ $s = str_replace( '_', ' ', $s );
+ $this->mSemiPrefixedText = $s;
+ }
+ return $this->mSemiPrefixedText;
+ }
+
/**
* Get the prefixed title with spaces, plus any fragment
* (part beginning with '#')
* @return String the URL
*/
public function getInternalURL( $query = '', $variant = false ) {
- global $wgInternalServer, $wgServer;
- $server = $wgInternalServer !== false ? $wgInternalServer : $wgServer;
+ if ( $this->isExternal( ) ) {
+ $server = '';
+ } else {
+ global $wgInternalServer, $wgServer;
+ $server = $wgInternalServer !== false ? $wgInternalServer : $wgServer;
+ }
$url = wfExpandUrl( $server . $this->getLocalURL( $query, $variant ), PROTO_HTTP );
wfRunHooks( 'GetInternalURL', array( &$this, &$url, $query ) );
return $url;
$this->mFragment = str_replace( '_', ' ', substr( $fragment, 1 ) );
}
+ public function setInterwiki( $interwiki ) {
+ $this->mInterwiki = $interwiki;
+ }
+
/**
* Get a Title object associated with the talk page of this article
*
* @return Mixed true on success, getUserPermissionsErrors()-like array on failure
*/
public function moveTo( &$nt, $auth = true, $reason = '', $createRedirect = true ) {
+ global $wgContLang, $wgEnableInterwikiTemplatesTracking, $wgGlobalDatabase;
+
$err = $this->isValidMoveOperation( $nt, $auth, $reason );
if ( is_array( $err ) ) {
return $err;
);
}
+ if ( $wgEnableInterwikiTemplatesTracking && $wgGlobalDatabase ) {
+ $dbw2 = wfGetDB( DB_MASTER, array(), $wgGlobalDatabase );
+ $dbw2->update( 'globaltemplatelinks',
+ array( 'gtl_from_namespace' => $nt->getNamespace(),
+ 'gtl_from_title' => $nt->getText() ),
+ array ( 'gtl_from_page' => $pageid ),
+ __METHOD__ );
+ }
+
if ( $protected ) {
# Protect the redirect title as the title used to be...
$dbw->insertSelect( 'page_restrictions', 'page_restrictions',
* @param $createRedirect Bool Whether to leave a redirect at the old title. Ignored
* if the user doesn't have the suppressredirect right
*/
- private function moveToInternal( &$nt, $reason = '', $createRedirect = true ) {
- global $wgUser, $wgContLang;
+ private function moveOverExistingRedirect( &$nt, $reason = '', $createRedirect = true ) {
+ global $wgUseSquid, $wgUser, $wgContLang, $wgEnableInterwikiTemplatesTracking, $wgGlobalDatabase;
$moveOverRedirect = $nt->exists();
array( 'rc_timestamp' => $rcts, 'rc_namespace' => $newns, 'rc_title' => $newdbk, 'rc_new' => 1 ),
__METHOD__
);
+
+ if ( $wgEnableInterwikiTemplatesTracking && $wgGlobalDatabase ) {
+ $dbw2 = wfGetDB( DB_MASTER, array(), $wgGlobalDatabase );
+ $dbw2->delete( 'globaltemplatelinks',
+ array( 'gtl_from_wiki' => wfGetID(),
+ 'gtl_from_page' => $newid ),
+ __METHOD__ );
+ }
}
# Save a null revision in the page's history notifying of the move
public function doDeleteArticle(
$reason, $suppress = false, $id = 0, $commit = true, &$error = '', User $user = null
) {
- global $wgDeferredUpdateList, $wgUseTrackbacks, $wgUser;
+ global $wgDeferredUpdateList, $wgUseTrackbacks, $wgEnableInterwikiTemplatesTracking, $wgGlobalDatabase, $wgUser;
$user = is_null( $user ) ? $wgUser : $user;
wfDebug( __METHOD__ . "\n" );
$dbw->delete( 'iwlinks', array( 'iwl_from' => $id ), __METHOD__ );
$dbw->delete( 'redirect', array( 'rd_from' => $id ), __METHOD__ );
$dbw->delete( 'page_props', array( 'pp_page' => $id ), __METHOD__ );
+
+ if ( $wgEnableInterwikiTemplatesTracking && $wgGlobalDatabase ) {
+ $dbw2 = wfGetDB( DB_MASTER, array(), $wgGlobalDatabase );
+ $dbw2->delete( 'globaltemplatelinks',
+ array( 'gtl_from_wiki' => wfGetID(),
+ 'gtl_from_page' => $id )
+ );
+ }
}
# If using cleanup triggers, we can skip some manual deletes
* @param $title Title object
*/
public static function onArticleCreate( $title ) {
+ global $wgDeferredUpdateList;
+
# Update existence markers on article/talk tabs...
if ( $title->isTalkPage() ) {
$other = $title->getSubjectPage();
$title->touchLinks();
$title->purgeSquid();
$title->deleteTitleProtection();
+
+ # Invalidate caches of distant articles which transclude this page
+ $wgDeferredUpdateList[] = new HTMLCacheUpdate( $title, 'globaltemplatelinks' );
}
/**
* @param $title Title
*/
public static function onArticleDelete( $title ) {
+ global $wgMessageCache, $wgDeferredUpdateList;
+
# Update existence markers on article/talk tabs...
if ( $title->isTalkPage() ) {
$other = $title->getSubjectPage();
# Image redirects
RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $title );
+
+ # Invalidate caches of distant articles which transclude this page
+ $wgDeferredUpdateList[] = new HTMLCacheUpdate( $title, 'globaltemplatelinks' );
}
/**
// Invalidate caches of articles which include this page
$wgDeferredUpdateList[] = new HTMLCacheUpdate( $title, 'templatelinks' );
+ // Invalidate caches of distant articles which transclude this page
+ $wgDeferredUpdateList[] = new HTMLCacheUpdate( $title, 'globaltemplatelinks' );
+
// Invalidate the caches of all pages which redirect here
$wgDeferredUpdateList[] = new HTMLCacheUpdate( $title, 'redirect' );
return $result;
}
+ /**
+ * Return a list of distant templates used by this article.
+ * Uses the globaltemplatelinks table
+ *
+ * @return Array of Title objects
+ */
+ public function getUsedDistantTemplates() {
+ global $wgGlobalDatabase;
+
+ $result = array();
+
+ if ( $wgGlobalDatabase ) {
+ $id = $this->mTitle->getArticleID();
+
+ if ( $id == 0 ) {
+ return array();
+ }
+
+ $dbr = wfGetDB( DB_SLAVE, array(), $wgGlobalDatabase );
+ $res = $dbr->select( 'globaltemplatelinks',
+ array( 'gtl_to_prefix', 'gtl_to_namespace', 'gtl_to_title' ),
+ array( 'gtl_from_wiki' => wfWikiID( ), 'gtl_from_page' => $id ),
+ __METHOD__ );
+
+ if ( $res !== false ) {
+ foreach ( $res as $row ) {
+ $result[] = Title::makeTitle( $row->gtl_to_namespace, $row->gtl_to_title, null, $row->gtl_to_prefix );
+ }
+ }
+ }
+
+ return $result;
+ }
+
/**
* Returns a list of hidden categories this page is a member of.
* Uses the page_props and categorylinks tables.
return;
}
+ if ( $this->mTable === 'globaltemplatelinks' ) {
+ global $wgEnableInterwikiTemplatesTracking;
+
+ if ( $wgEnableInterwikiTemplatesTracking ) {
+ $distantPageArray = $this->mCache->getDistantTemplateLinks( 'globaltemplatelinks' );
+ $this->invalidateDistantTitles( $distantPageArray );
+ }
+ return;
+ }
+
# Get an estimate of the number of rows from the BacklinkCache
$numRows = $this->mCache->getNumLinks( $this->mTable );
if ( $numRows > $this->mRowsPerJob * 2 ) {
$this->invalidateTitles( $titleArray );
}
}
+ wfRunHooks( 'HTMLCacheUpdate::doUpdate', array($this->mTitle) );
}
/**
}
}
+ /**
+ * Invalidate an array of distant pages, given the wiki ID and page ID of those pages
+ */
+ protected function invalidateDistantTitles( $distantPageArray ) {
+ global $wgUseFileCache, $wgUseSquid, $wgLocalInterwiki;
+
+ $pagesByWiki = array();
+ $titleArray = array();
+ # Sort by WikiID in $pagesByWiki
+ # Create the distant titles for Squid in $titleArray
+ foreach ( $distantPageArray as $row ) {
+ $wikiid = $row->gtl_from_wiki;
+ if( !isset( $pagesByWiki[$wikiid] ) ) {
+ $pagesByWiki[$wikiid] = array();
+}
+ $pagesByWiki[$wikiid][] = $row->gtl_from_page;
+ $titleArray[] = Title::makeTitle( $row->gtl_from_namespace, $row->gtl_from_title, '', $row->gil_interwiki );
+ }
+
+ foreach ( $pagesByWiki as $wikiid => $pages ) {
+ $dbw = wfGetDB( DB_MASTER, array( ), $wikiid );
+ $timestamp = $dbw->timestamp();
+ $batches = array_chunk( $pages, $this->mRowsPerQuery );
+ foreach ( $batches as $batch ) {
+ $dbw->update( 'page',
+ array( 'page_touched' => $timestamp ),
+ array( 'page_id IN (' . $dbw->makeList( $batch ) . ')' ),
+ __METHOD__
+ );
+ }
+ }
+
+ # Update squid
+ if ( $wgUseSquid ) {
+ $u = SquidUpdate::newFromTitles( $titleArray );
+ $u->doUpdate();
+ }
+ }
}
/**
}
}
+ /**
+ * Build a partial where clause from a 3-d array
+ * The keys on each level may be either integers or strings.
+ *
+ * @param $data Array: organized as 3-d array(baseKeyVal => array(middleKeyVal => array(subKeyVal => <ignored>, ...), ...), ...)
+ * @param $baseKey String: field name to match the base-level keys to (eg 'gtl_to_prefix')
+ * @param $middleKey String: field name to match the middle-level keys to (eg 'gtl_to_namespace')
+ * @param $subKey String: field name to match the sub-level keys to (eg 'gtl_to_title')
+ * @return Mixed: string SQL fragment, or false if no items in array.
+ */
+ function makeWhereFrom3d( $data, $baseKey, $middleKey, $subKey ) {
+ $conds = array();
+ foreach ( $data as $base => $subdata ) {
+ foreach ( $subdata as $middle => $sub ) {
+ if ( count( $sub ) ) {
+ $conds[] = $this->makeList(
+ array( $baseKey => $base,
+ $middleKey => $middle,
+ $subKey => array_keys( $sub ) ),
+ LIST_AND
+ );
+ }
+ }
+ }
+
+ if ( $conds ) {
+ return $this->makeList( $conds, LIST_OR );
+ } else {
+ // Nothing to search for...
+ return false;
+ }
+ }
+
/**
* Bitwise operations
*/
// 1.19
array( 'addTable', 'config', 'patch-config.sql' ),
array( 'addIndex', 'logging', 'type_action', 'patch-logging-type-action-index.sql'),
+ array( 'addTable', 'globaltemplatelinks', 'patch-globaltemplatelinks.sql' ),
+ array( 'addTable', 'globalnamespaces', 'patch-globalnamespaces.sql' ),
+ array( 'addTable', 'globalinterwiki', 'patch-globalinterwiki.sql' ),
);
}
// 1.19
array( 'addTable', 'config', 'patch-config.sql' ),
array( 'addIndex', 'logging', 'type_action', 'patch-logging-type-action-index.sql')
+
+ // 1.19
+ array( 'addTable', 'globaltemplatelinks', 'patch-globaltemplatelinks.sql' ),
+ array( 'addTable', 'globalnamespaces', 'patch-globalnamespaces.sql' ),
+ array( 'addTable', 'globalinterwiki', 'patch-globalinterwiki.sql' ),
);
}
* All information is loaded on creation when called by Interwiki::fetch( $prefix ).
* All work is done on slave, because this should *never* change (except during
* schema updates etc, which aren't wiki-related)
+ * This class also contains the functions that allow interwiki templates transclusion.
*/
class Interwiki {
$mc = array(
'iw_url' => $iw->mURL,
'iw_api' => $iw->mAPI,
+ 'iw_wikiid' => $iw->mWikiID,
'iw_local' => $iw->mLocal,
'iw_trans' => $iw->mTrans
);
$iw->mURL = $mc['iw_url'];
$iw->mLocal = isset( $mc['iw_local'] ) ? $mc['iw_local'] : 0;
$iw->mTrans = isset( $mc['iw_trans'] ) ? $mc['iw_trans'] : 0;
+ $iw->mAPI = isset( $mc['iw_api'] ) ? $mc['iw_api'] :
$iw->mAPI = isset( $mc['iw_api'] ) ? $mc['iw_api'] : '';
$iw->mWikiID = isset( $mc['iw_wikiid'] ) ? $mc['iw_wikiid'] : '';
$msg = wfMessage( 'interwiki-desc-' . $this->mPrefix )->inContentLanguage();
return !$msg->exists() ? '' : $msg;
}
+
+
+ /**
+ * Transclude an interwiki link.
+ */
+ public static function interwikiTransclude( $title ) {
+
+ // If we have a wikiID, we will use it to get an access to the remote database
+ // if not, we will use the API URL to retrieve the data through a HTTP Get
+
+ $wikiID = $title->getTransWikiID( );
+ $transAPI = $title->getTransAPI( );
+
+ if ( $wikiID !== '') {
+
+ $finalText = self::fetchTemplateFromDB( $wikiID, $title );
+ return $finalText;
+
+ } else if( $transAPI !== '' ) {
+
+ $interwiki = $title->getInterwiki( );
+ $fullTitle = $title->getSemiPrefixedText( );
+
+ $finalText = self::fetchTemplateFromAPI( $interwiki, $transAPI, $fullTitle );
+
+ return $finalText;
+
+}
+ return false;
+ }
+
+ /**
+ * Retrieve the wikitext of a distant page accessing the foreign DB
+ */
+ public static function fetchTemplateFromDB ( $wikiID, $title ) {
+
+ $revision = Revision::loadFromTitleForeignWiki( $wikiID, $title );
+
+ if ( $revision ) {
+ $text = $revision->getText();
+ return $text;
+ }
+
+ return false;
+ }
+
+ /**
+ * Retrieve the wikitext of a distant page using the API of the foreign wiki
+ */
+ public static function fetchTemplateFromAPI( $interwiki, $transAPI, $fullTitle ) {
+ global $wgMemc, $wgTranscludeCacheExpiry;
+
+ $key = wfMemcKey( 'iwtransclustiontext', 'textid', $interwiki, $fullTitle );
+ $text = $wgMemc->get( $key );
+ if( is_array ( $text ) &&
+ isset ( $text['missing'] ) &&
+ $text['missing'] === true ) {
+ return false;
+ } else if ( $text ) {
+ return $text;
+ }
+
+ $url = wfAppendQuery(
+ $transAPI,
+ array( 'action' => 'query',
+ 'titles' => $fullTitle,
+ 'prop' => 'revisions',
+ 'rvprop' => 'content',
+ 'format' => 'json'
+ )
+ );
+
+ $get = Http::get( $url );
+ $content = FormatJson::decode( $get, true );
+
+ if ( isset ( $content['query'] ) &&
+ isset ( $content['query']['pages'] ) ) {
+ $page = array_pop( $content['query']['pages'] );
+ if ( $page && isset( $page['revisions'][0]['*'] ) ) {
+ $text = $page['revisions'][0]['*'];
+ $wgMemc->set( $key, $text, $wgTranscludeCacheExpiry );
+
+ // When we cache a template, we also retrieve and cache its subtemplates
+ $subtemplates = self::getSubtemplatesListFromAPI( $interwiki, $transAPI, $fullTitle );
+ self::cacheTemplatesFromAPI( $interwiki, $transAPI, $subtemplates );
+
+ return $text;
+ } else {
+ $wgMemc->set( $key, array ( 'missing' => true ), $wgTranscludeCacheExpiry );
+ }
+ }
+ return false;
+ }
+
+ public static function getSubtemplatesListFromAPI ( $interwiki, $transAPI, $title ) {
+ $url = wfAppendQuery( $transAPI,
+ array( 'action' => 'query',
+ 'titles' => $title,
+ 'prop' => 'templates',
+ 'format' => 'json'
+ )
+ );
+
+ $get = Http::get( $url );
+ $myArray = FormatJson::decode($get, true);
+
+ $templates = array( );
+ if ( ! empty( $myArray['query'] )) {
+ if ( ! empty( $myArray['query']['pages'] )) {
+ $templates = array_pop( $myArray['query']['pages'] );
+ if ( ! empty( $templates['templates'] )) {
+ $templates = $templates['templates'];
+ }
+ }
+ return $templates;
+ }
+ }
+
+ public static function cacheTemplatesFromAPI( $interwiki, $transAPI, $titles ){
+ global $wgMemc, $wgTranscludeCacheExpiry;
+
+ $outdatedTitles = array( );
+
+ foreach( $titles as $title ){
+ if ( isset ( $title['title'] ) ) {
+ $key = wfMemcKey( 'iwtransclustiontext', 'textid', $interwiki, $title['title'] );
+ $text = $wgMemc->get( $key );
+ if( !$text ){
+ $outdatedTitles[] = $title['title'];
+ }
+ }
+ }
+
+ $batches = array_chunk( $outdatedTitles, 50 );
+
+ foreach( $batches as $batch ){
+ $url = wfAppendQuery(
+ $transAPI,
+ array( 'action' => 'query',
+ 'titles' => implode( '|', $batch ),
+ 'prop' => 'revisions',
+ 'rvprop' => 'content',
+ 'format' => 'json'
+ )
+ );
+ $get = Http::get( $url );
+ $content = FormatJson::decode( $get, true );
+
+ if ( isset ( $content['query'] ) &&
+ isset ( $content['query']['pages'] ) ) {
+ foreach( $content['query']['pages'] as $page ) {
+ $key = wfMemcKey( 'iwtransclustiontext', 'textid', $interwiki, $page['title'] );
+ if ( isset ( $page['revisions'][0]['*'] ) ) {
+ $text = $page['revisions'][0]['*'];
+ } else {
+ $text = array ( 'missing' => true );
+ }
+ $wgMemc->set( $key, $text, $wgTranscludeCacheExpiry );
+ }
+ }
+ }
+ }
}
* @private
*/
function braceSubstitution( $piece, $frame ) {
- global $wgContLang, $wgNonincludableNamespaces;
+ global $wgContLang, $wgNonincludableNamespaces, $wgEnableInterwikiTranscluding, $wgEnableInterwikiTemplatesTracking;
wfProfileIn( __METHOD__ );
wfProfileIn( __METHOD__.'-setup' );
$found = false; # $text has been filled
$nowiki = false; # wiki markup in $text should be escaped
$isHTML = false; # $text is HTML, armour it against wikitext transformation
- $forceRawInterwiki = false; # Force interwiki transclusion to be done in raw mode not rendered
$isChildObj = false; # $text is a DOM node needing expansion in a child frame
$isLocalObj = false; # $text is a DOM node needing expansion in the current frame
}
$title = Title::newFromText( $part1, $ns );
if ( $title ) {
+ if ( !$title->isExternal() && $piece['interwiki'] !== '' ) {
+ $title->setInterwiki( $piece['interwiki'] );
+ }
$titleText = $title->getPrefixedText();
# Check for language variants if the template is not found
if ( $wgContLang->hasVariants() && $title->getArticleID() == 0 ) {
$text = "[[:$titleText]]";
$found = true;
}
- } elseif ( $title->isTrans() ) {
- # Interwiki transclusion
- if ( $this->ot['html'] && !$forceRawInterwiki ) {
- $text = $this->interwikiTransclude( $title, 'render' );
- $isHTML = true;
- } else {
- $text = $this->interwikiTransclude( $title, 'raw' );
+ } elseif ( $wgEnableInterwikiTranscluding && $title->isTrans() ) {
+
+ $text = Interwiki::interwikiTransclude( $title );
+ $this->registerDistantTemplate( $title );
+
+ if ( $wgEnableInterwikiTemplatesTracking ) {
+ $this->registerDistantTemplate( $title );
+ }
+
+ if ( $text !== false ) {
# Preprocess it like a template
$text = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
+ $found = true;
$isChildObj = true;
}
- $found = true;
+
}
# Do infinite loop check
}
/**
- * Fetch the unparsed text of a template and register a reference to it.
- * @param Title $title
- * @return mixed string or false
+ * Register a distant template as used
*/
+ function registerDistantTemplate( $title ) {
+ $stuff = Parser::distantTemplateCallback( $title, $this );
+ $text = $stuff['text'];
+ $finalTitle = isset( $stuff['finalTitle'] ) ? $stuff['finalTitle'] : $title;
+ if ( isset( $stuff['deps'] ) ) {
+ foreach ( $stuff['deps'] as $dep ) {
+ $this->mOutput->addDistantTemplate( $dep['title'], $dep['page_id'], $dep['rev_id'] );
+ }
+ }
+ }
+
function fetchTemplate( $title ) {
$rv = $this->fetchTemplateAndTitle( $title );
return $rv[0];
return array( $file, $title );
}
- /**
- * Transclude an interwiki link.
- *
- * @param $title Title
- * @param $action
- *
- * @return string
- */
- function interwikiTransclude( $title, $action ) {
- global $wgEnableScaryTranscluding;
-
- if ( !$wgEnableScaryTranscluding ) {
- return wfMsgForContent('scarytranscludedisabled');
- }
-
- $url = $title->getFullUrl( "action=$action" );
+ static function distantTemplateCallback( $title, $parser=false ) {
+ $text = '';
+ $rev_id = null;
+ $deps[] = array(
+ 'title' => $title,
+ 'page_id' => $title->getArticleID(),
+ 'rev_id' => $rev_id );
- if ( strlen( $url ) > 255 ) {
- return wfMsgForContent( 'scarytranscludetoolong' );
- }
- return $this->fetchScaryTemplateMaybeFromCache( $url );
- }
-
- /**
- * @param $url string
- * @return Mixed|String
- */
- function fetchScaryTemplateMaybeFromCache( $url ) {
- global $wgTranscludeCacheExpiry;
- $dbr = wfGetDB( DB_SLAVE );
- $tsCond = $dbr->timestamp( time() - $wgTranscludeCacheExpiry );
- $obj = $dbr->selectRow( 'transcache', array('tc_time', 'tc_contents' ),
- array( 'tc_url' => $url, "tc_time >= " . $dbr->addQuotes( $tsCond ) ) );
- if ( $obj ) {
- return $obj->tc_contents;
- }
+ $finalTitle = $title;
- $text = Http::get( $url );
- if ( !$text ) {
- return wfMsgForContent( 'scarytranscludefailed', $url );
+ return array(
+ 'text' => $text,
+ 'finalTitle' => $finalTitle,
+ 'deps' => $deps );
}
- $dbw = wfGetDB( DB_MASTER );
- $dbw->replace( 'transcache', array('tc_url'), array(
- 'tc_url' => $url,
- 'tc_time' => $dbw->timestamp( time() ),
- 'tc_contents' => $text)
- );
- return $text;
- }
-
/**
* Triple brace replacement -- used for template arguments
* @private
$mLinks = array(), # 2-D map of NS/DBK to ID for the links in the document. ID=zero for broken.
$mTemplates = array(), # 2-D map of NS/DBK to ID for the template references. ID=zero for broken.
$mTemplateIds = array(), # 2-D map of NS/DBK to rev ID for the template references. ID=zero for broken.
+ $mDistantTemplates = array(), # 3-D map of WIKIID/NS/DBK to ID for the template references. ID=zero for broken.
+ $mDistantTemplateIds = array(), # 3-D map of WIKIID/NS/DBK to rev ID for the template references. ID=zero for broken.
$mImages = array(), # DB keys of the images used, in the array key only
$mImageTimeKeys = array(), # DB keys of the images used mapped to sha1 and MW timestamp
$mExternalLinks = array(), # External link URLs, in the key only
function getEditSectionTokens() { return $this->mEditSectionTokens; }
function &getLinks() { return $this->mLinks; }
function &getTemplates() { return $this->mTemplates; }
+ function &getDistantTemplates() { return $this->mDistantTemplates; }
+ function &getDistantTemplateIds() { return $this->mDistantTemplateIds; }
function &getTemplateIds() { return $this->mTemplateIds; }
function &getImages() { return $this->mImages; }
function &getImageTimeKeys() { return $this->mImageTimeKeys; }
$this->mTemplateIds[$ns][$dbk] = $rev_id; // For versioning
}
+ function addDistantTemplate( $title, $page_id, $rev_id ) {
+ $prefix = $title->getInterwiki();
+ if ( $prefix !=='' ) {
+ $ns = $title->getNamespace();
+ $dbk = $title->getDBkey();
+
+ if ( !isset( $this->mDistantTemplates[$prefix] ) ) {
+ $this->mDistantTemplates[$prefix] = array();
+ }
+ if ( !isset( $this->mDistantTemplates[$prefix][$ns] ) ) {
+ $this->mDistantTemplates[$prefix][$ns] = array();
+ }
+ $this->mDistantTemplates[$prefix][$ns][$dbk] = $page_id;
+
+ // For versioning
+ if ( !isset( $this->mDistantTemplateIds[$prefix] ) ) {
+ $this->mDistantTemplateIds[$prefix] = array();
+ }
+ if ( !isset( $this->mDistantTemplateIds[$prefix][$ns] ) ) {
+ $this->mDistantTemplateIds[$prefix][$ns] = array();
+ }
+ $this->mDistantTemplateIds[$prefix][$ns][$dbk] = $rev_id;
+ }
+ }
+
/**
* @param $title Title object, must be an interwiki link
* @throws MWException if given invalid input
$params = array(
'title' => new PPNode_DOM( $title ),
'parts' => new PPNode_DOM( $parts ),
- 'lineStart' => $lineStart );
+ 'lineStart' => $lineStart,
+ 'interwiki' => $this->title->getInterwiki( ) );
$ret = $this->parser->braceSubstitution( $params, $this );
if ( isset( $ret['object'] ) ) {
$newIterator = $ret['object'];
if ( $flags & PPFrame::NO_TEMPLATES ) {
$newIterator = $this->virtualBracketedImplode( '{{', '|', '}}', $bits['title'], $bits['parts'] );
} else {
+ $bits['interwiki'] = $this->title->getInterwiki( );
$ret = $this->parser->braceSubstitution( $bits, $this );
if ( isset( $ret['object'] ) ) {
$newIterator = $ret['object'];
--- /dev/null
+<?php
+/**
+ * Special page to show global file usage. Also contains hook functions for
+ * showing usage on an image page.
+ */
+
+class SpecialGlobalFileUsage extends SpecialPage {
+ public function __construct() {
+ parent::__construct( 'GlobalFileUsage' );
+ }
+
+ /**
+ * Entry point
+ */
+ public function execute( $par ) {
+ global $wgOut, $wgRequest;
+
+ $target = $par ? $par : $wgRequest->getVal( 'target' );
+ $this->target = Title::makeTitleSafe( NS_FILE, $target );
+
+ $this->filterLocal = $wgRequest->getCheck( 'filterlocal' );
+
+ $this->setHeaders();
+
+ $this->showForm();
+
+ if ( is_null( $this->target ) ) {
+ $wgOut->setPageTitle( wfMsg( 'globalfileusage' ) );
+ return;
+ }
+
+ $wgOut->setPageTitle( wfMsg( 'globalfileusage-for', $this->target->getPrefixedText() ) );
+
+ $this->showResult();
+ }
+
+ /**
+ * Shows the search form
+ */
+ private function showForm() {
+ global $wgScript, $wgOut, $wgRequest;
+
+ /* Build form */
+ $html = Xml::openElement( 'form', array( 'action' => $wgScript ) ) . "\n";
+ // Name of SpecialPage
+ $html .= Xml::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n";
+ // Limit
+ $html .= Xml::hidden( 'limit', $wgRequest->getInt( 'limit', 50 ) );
+ // Input box with target prefilled if available
+ $formContent = "\t" . Xml::input( 'target', 40, is_null( $this->target ) ? ''
+ : $this->target->getText() )
+ // Submit button
+ . "\n\t" . Xml::element( 'input', array(
+ 'type' => 'submit',
+ 'value' => wfMsg( 'globalfileusage-ok' )
+ ) )
+ // Filter local checkbox
+ . "\n\t<p>" . Xml::checkLabel( wfMsg( 'globalfileusage-filterlocal' ),
+ 'filterlocal', 'mw-filterlocal', $this->filterLocal ) . '</p>';
+
+ if ( !is_null( $this->target ) && wfFindFile( $this->target ) ) {
+ // Show the image if it exists
+ global $wgUser, $wgContLang;
+ $skin = $wgUser->getSkin();
+
+ $html .= $skin->makeImageLinkObj( $this->target,
+ $this->target->getPrefixedText(),
+ /* $alt */ '', /* $align */ $wgContLang->alignEnd(),
+ /* $handlerParams */ array(), /* $framed */ false,
+ /* $thumb */ true );
+ }
+
+ // Wrap the entire form in a nice fieldset
+ $html .= Xml::fieldSet( wfMsg( 'globalfileusage-text' ), $formContent ) . "\n</form>";
+
+ $wgOut->addHtml( $html );
+ }
+
+ /**
+ * Creates as queryer and executes it based on $wgRequest
+ */
+ private function showResult() {
+ global $wgRequest;
+
+ $query = new GlobalUsageQuery( $this->target );
+
+ // Extract params from $wgRequest
+ if ( $wgRequest->getText( 'from' ) ) {
+ $query->setOffset( $wgRequest->getText( 'from' ) );
+ } elseif ( $wgRequest->getText( 'to' ) ) {
+ $query->setOffset( $wgRequest->getText( 'to' ), true );
+ }
+ $query->setLimit( $wgRequest->getInt( 'limit', 50 ) );
+ $query->filterLocal( $this->filterLocal );
+
+ // Perform query
+ $query->searchFile();
+
+ // Show result
+ global $wgOut;
+
+ // Don't show form element if there is no data
+ if ( $query->count() == 0 ) {
+ $wgOut->addWikiMsg( 'globalfileusage-no-results', $this->target->getPrefixedText() );
+ return;
+ }
+
+ $offset = $query->getOffsetString();
+ $navbar = $this->getNavBar( $query );
+ $targetName = $this->target->getText();
+
+ // Top navbar
+ $wgOut->addHtml( $navbar );
+
+ $wgOut->addHtml( '<div id="mw-globalfileusage-result">' );
+ foreach ( $query->getSingleResult() as $wiki => $result ) {
+ $wgOut->addHtml(
+ '<h2>' . wfMsgExt(
+ 'globalfileusage-on-wiki', 'parseinline',
+ $targetName, WikiMap::getWikiName( $wiki ) )
+ . "</h2><ul>\n" );
+ foreach ( $result as $item ) {
+ $wgOut->addHtml( "\t<li>" . self::formatItem( $item ) . "</li>\n" );
+ }
+ $wgOut->addHtml( "</ul>\n" );
+ }
+ $wgOut->addHtml( '</div>' );
+
+ // Bottom navbar
+ $wgOut->addHtml( $navbar );
+ }
+ /**
+ * Helper to format a specific item
+ */
+ public static function formatItem( $item ) {
+ if ( !$item['namespace'] ) {
+ $page = $item['title'];
+ } else {
+ $page = "{$item['namespace']}:{$item['title']}";
+ }
+
+ $link = WikiMap::makeForeignLink( $item['wiki'], $page,
+ str_replace( '_', ' ', $page ) );
+ // Return only the title if no link can be constructed
+ return $link === false ? $page : $link;
+ }
+
+ /**
+ * Helper function to create the navbar, stolen from wfViewPrevNext
+ *
+ * @param $query GlobalUsageQuery An executed GlobalUsageQuery object
+ * @return string Navbar HTML
+ */
+ protected function getNavBar( $query ) {
+ global $wgLang, $wgUser;
+
+ $skin = $wgUser->getSkin();
+
+ $target = $this->target->getText();
+ $limit = $query->getLimit();
+ $fmtLimit = $wgLang->formatNum( $limit );
+
+ # Find out which strings are for the prev and which for the next links
+ $offset = $query->getOffsetString();
+ $continue = $query->getContinueFileString();
+ if ( $query->isReversed() ) {
+ $from = $offset;
+ $to = $continue;
+ } else {
+ $from = $continue;
+ $to = $offset;
+ }
+
+ # Get prev/next link display text
+ $prev = wfMsgExt( 'prevn', array( 'parsemag', 'escape' ), $fmtLimit );
+ $next = wfMsgExt( 'nextn', array( 'parsemag', 'escape' ), $fmtLimit );
+ # Get prev/next link title text
+ $pTitle = wfMsgExt( 'prevn-title', array( 'parsemag', 'escape' ), $fmtLimit );
+ $nTitle = wfMsgExt( 'nextn-title', array( 'parsemag', 'escape' ), $fmtLimit );
+
+ # Fetch the title object
+ $title = $this->getTitle();
+
+ # Make 'previous' link
+ if ( $to ) {
+ $attr = array( 'title' => $pTitle, 'class' => 'mw-prevlink' );
+ $q = array( 'limit' => $limit, 'to' => $to, 'target' => $target );
+ if ( $this->filterLocal )
+ $q['filterlocal'] = '1';
+ $plink = $skin->link( $title, $prev, $attr, $q );
+ } else {
+ $plink = $prev;
+ }
+
+ # Make 'next' link
+ if ( $from ) {
+ $attr = array( 'title' => $nTitle, 'class' => 'mw-nextlink' );
+ $q = array( 'limit' => $limit, 'from' => $from, 'target' => $target );
+ if ( $this->filterLocal )
+ $q['filterlocal'] = '1';
+ $nlink = $skin->link( $title, $next, $attr, $q );
+ } else {
+ $nlink = $next;
+ }
+
+ # Make links to set number of items per page
+ $numLinks = array();
+ foreach ( array( 20, 50, 100, 250, 500 ) as $num ) {
+ $fmtLimit = $wgLang->formatNum( $num );
+
+ $q = array( 'offset' => $offset, 'limit' => $num, 'target' => $target );
+ if ( $this->filterLocal )
+ $q['filterlocal'] = '1';
+ $lTitle = wfMsgExt( 'shown-title', array( 'parsemag', 'escape' ), $num );
+ $attr = array( 'title' => $lTitle, 'class' => 'mw-numlink' );
+
+ $numLinks[] = $skin->link( $title, $fmtLimit, $attr, $q );
+ }
+ $nums = $wgLang->pipeList( $numLinks );
+
+ return wfMsgHtml( 'viewprevnext', $plink, $nlink, $nums );
+ }
+}
+
--- /dev/null
+<?php
+/**
+ * This file has been copied from Extension:GlobalUsage and adapted
+ * to show the usage of a template instead of a file.
+ * Special page to show global template usage. Also contains hook functions for
+ * showing usage on an template page.
+ *
+ * @author Bryan Tong Minh <bryan.tongminh@gmail.com>
+ * @author Peter Potrowl <peter017@gmail.com>
+ */
+
+class SpecialGlobalTemplateUsage extends SpecialPage {
+ public function __construct() {
+ parent::__construct( 'GlobalTemplateUsage' );
+ }
+
+ /**
+ * Entry point
+ */
+ public function execute( $par ) {
+ global $wgOut, $wgRequest;
+
+ $target = $par ? $par : $wgRequest->getVal( 'target' );
+ $this->target = Title::newFromText( $target );
+
+ $this->setHeaders();
+
+ $this->showForm();
+
+ if ( is_null( $this->target ) ) {
+ $wgOut->setPageTitle( wfMsg( 'globaltemplateusage' ) );
+ return;
+ }
+
+ $wgOut->setPageTitle( wfMsg( 'globaltemplateusage-for', $this->target->getPrefixedText() ) );
+
+ $this->showResult();
+ }
+
+ /**
+ * Shows the search form
+ */
+ private function showForm() {
+ global $wgScript, $wgOut, $wgRequest;
+
+ /* Build form */
+ $html = Xml::openElement( 'form', array( 'action' => $wgScript ) ) . "\n";
+ // Name of SpecialPage
+ $html .= Xml::hidden( 'title', $this->getTitle( )->getPrefixedText( ) ) . "\n";
+ // Limit
+ $html .= Xml::hidden( 'limit', $wgRequest->getInt( 'limit', 50 ) );
+ // Input box with target prefilled if available
+ $formContent = "\t" . Xml::input( 'target', 40, is_null( $this->target ) ? ''
+ : $this->target->getPrefixedText( ) )
+ // Submit button
+ . "\n\t" . Xml::element( 'input', array(
+ 'type' => 'submit',
+ 'value' => wfMsg( 'globaltemplateusage-ok' )
+ ) );
+
+ // Wrap the entire form in a nice fieldset
+ $html .= Xml::fieldSet( wfMsg( 'globaltemplateusage-text' ), $formContent ) . "\n</form>";
+
+ $wgOut->addHtml( $html );
+ }
+
+ /**
+ * Creates as queryer and executes it based on $wgRequest
+ */
+ private function showResult() {
+ global $wgRequest;
+
+ $query = new GlobalUsageQuery( $this->target );
+
+ // Extract params from $wgRequest
+ if ( $wgRequest->getText( 'from' ) ) {
+ $query->setOffset( $wgRequest->getText( 'from' ) );
+ } elseif ( $wgRequest->getText( 'to' ) ) {
+ $query->setOffset( $wgRequest->getText( 'to' ), true );
+ }
+ $query->setLimit( $wgRequest->getInt( 'limit', 50 ) );
+
+ // Perform query
+ $query->searchTemplate();
+
+ // Show result
+ global $wgOut;
+
+ // Don't show form element if there is no data
+ if ( $query->count() == 0 ) {
+ $wgOut->addWikiMsg( 'globaltemplateusage-no-results', $this->target->getPrefixedText( ) );
+ return;
+ }
+
+ $offset = $query->getOffsetString( );
+ $navbar = $this->getNavBar( $query );
+ $targetName = $this->target->getPrefixedText( );
+
+ // Top navbar
+ $wgOut->addHtml( $navbar );
+
+ $wgOut->addHtml( '<div id="mw-globaltemplateusage-result">' );
+ foreach ( $query->getSingleResult() as $wiki => $result ) {
+ $wgOut->addHtml(
+ '<h2>' . wfMsgExt(
+ 'globaltemplateusage-on-wiki', 'parseinline',
+ $targetName, WikiMap::getWikiName( $wiki ) )
+ . "</h2><ul>\n" );
+ foreach ( $result as $item ) {
+ $wgOut->addHtml( "\t<li>" . self::formatItem( $item ) . "</li>\n" );
+ }
+ $wgOut->addHtml( "</ul>\n" );
+ }
+ $wgOut->addHtml( '</div>' );
+
+ // Bottom navbar
+ $wgOut->addHtml( $navbar );
+ }
+
+ /**
+ * Helper to format a specific item
+ */
+ public static function formatItem( $item ) {
+ if ( !$item['namespace'] ) {
+ $page = $item['title'];
+ } else {
+ $page = "{$item['namespace']}:{$item['title']}";
+ }
+
+ $link = WikiMap::makeForeignLink( $item['wiki'], $page,
+ str_replace( '_', ' ', $page ) );
+ // Return only the title if no link can be constructed
+ return $link === false ? $page : $link;
+ }
+
+ /**
+ * Helper function to create the navbar, stolen from wfViewPrevNext
+ *
+ * @param $query GlobalTemplateUsageQuery An executed GlobalTemplateUsageQuery object
+ * @return string Navbar HTML
+ */
+ protected function getNavBar( $query ) {
+ global $wgLang, $wgUser;
+
+ $skin = $wgUser->getSkin();
+
+ $target = $this->target->getPrefixedText();
+ $limit = $query->getLimit();
+ $fmtLimit = $wgLang->formatNum( $limit );
+
+ # Find out which strings are for the prev and which for the next links
+ $offset = $query->getOffsetString();
+ $continue = $query->getContinueTemplateString();
+ if ( $query->isReversed() ) {
+ $from = $offset;
+ $to = $continue;
+ } else {
+ $from = $continue;
+ $to = $offset;
+ }
+
+ # Get prev/next link display text
+ $prev = wfMsgExt( 'prevn', array( 'parsemag', 'escape' ), $fmtLimit );
+ $next = wfMsgExt( 'nextn', array( 'parsemag', 'escape' ), $fmtLimit );
+ # Get prev/next link title text
+ $pTitle = wfMsgExt( 'prevn-title', array( 'parsemag', 'escape' ), $fmtLimit );
+ $nTitle = wfMsgExt( 'nextn-title', array( 'parsemag', 'escape' ), $fmtLimit );
+
+ # Fetch the title object
+ $title = $this->getTitle();
+
+ # Make 'previous' link
+ if ( $to ) {
+ $attr = array( 'title' => $pTitle, 'class' => 'mw-prevlink' );
+ $q = array( 'limit' => $limit, 'to' => $to, 'target' => $target );
+ $plink = $skin->link( $title, $prev, $attr, $q );
+ } else {
+ $plink = $prev;
+ }
+
+ # Make 'next' link
+ if ( $from ) {
+ $attr = array( 'title' => $nTitle, 'class' => 'mw-nextlink' );
+ $q = array( 'limit' => $limit, 'from' => $from, 'target' => $target );
+ $nlink = $skin->link( $title, $next, $attr, $q );
+ } else {
+ $nlink = $next;
+ }
+
+ # Make links to set number of items per page
+ $numLinks = array();
+ foreach ( array( 20, 50, 100, 250, 500 ) as $num ) {
+ $fmtLimit = $wgLang->formatNum( $num );
+
+ $q = array( 'offset' => $offset, 'limit' => $num, 'target' => $target );
+ $lTitle = wfMsgExt( 'shown-title', array( 'parsemag', 'escape' ), $num );
+ $attr = array( 'title' => $lTitle, 'class' => 'mw-numlink' );
+
+ $numLinks[] = $skin->link( $title, $fmtLimit, $attr, $q );
+ }
+ $nums = $wgLang->pipeList( $numLinks );
+
+ return wfMsgHtml( 'viewprevnext', $plink, $nlink, $nums );
+ }
+}
+
+
'Watchlist' => array( 'Watchlist' ),
'Whatlinkshere' => array( 'WhatLinksHere' ),
'Withoutinterwiki' => array( 'WithoutInterwiki' ),
+ 'Globalfileusage' => array( 'GlobalFileUsage' ),
+ 'Globaltemplateusage' => array( 'GlobalTemplateUsage' ),
);
/**
'templatesused' => '{{PLURAL:$1|Template|Templates}} used on this page:',
'templatesusedpreview' => '{{PLURAL:$1|Template|Templates}} used in this preview:',
'templatesusedsection' => '{{PLURAL:$1|Template|Templates}} used in this section:',
+'distanttemplatesused' => 'Distant {{PLURAL:$1|template|templates}} used on this page:',
+'distanttemplatesusedpreview' => 'Distant {{PLURAL:$1|template|templates}} used in this preview:',
+'distanttemplatesusedsection' => 'Distant {{PLURAL:$1|template|templates}} used in this section:',
'template-protected' => '(protected)',
'template-semiprotected' => '(semi-protected)',
'hiddencategories' => 'This page is a member of {{PLURAL:$1|1 hidden category|$1 hidden categories}}:',
'compare-title-not-exists' => 'The title you specified does not exist.',
'compare-revision-not-exists' => 'The revision you specified does not exist.',
+# Special:GlobalFileUsage
+'globalfileusage' => 'Global file usage',
+'globalfileusage-for' => 'Global file usage for "$1"',
+'globalfileusage-desc' => '[[Special:GlobalFileUsage|Special page]] to view global file usage',
+'globalfileusage-ok' => 'Search',
+'globalfileusage-text' => 'Search global file usage',
+'globalfileusage-no-results' => '[[$1]] is not used on other wikis.',
+'globalfileusage-on-wiki' => 'Usage on $2',
+'globalfileusage-of-file' => 'The following other wikis use this file:',
+'globalfileusage-more' => 'View [[{{#Special:GlobalUsage}}/$1|more global usage]] of this file.',
+'globalfileusage-filterlocal' => 'Do not show local usage',
+
+# Special:GlobalTemplateUsage
+'globaltemplateusage' => 'Global template usage',
+'globaltemplateusage-for' => 'Global template usage for "$1"',
+'globaltemplateusage-desc' => '[[Special:GlobalTemplateUsage|Special page]] to view global template usage',
+'globaltemplateusage-ok' => 'Search',
+'globaltemplateusage-text' => 'Search global template usage',
+'globaltemplateusage-no-results' => '[[$1]] is not used on other wikis.',
+'globaltemplateusage-on-wiki' => 'Usage on $2',
+'globaltemplateusage-of-file' => 'The following other wikis use this template:',
+'globaltemplateusage-more' => 'View [[{{#Special:GlobalUsage}}/$1|more global usage]] of this template.',
+'globaltemplateusage-filterlocal' => 'Do not show local usage',
+
# Database error messages
'dberr-header' => 'This wiki has a problem',
'dberr-problems' => 'Sorry!
{{Identical|Other}}',
+# Special:GlobalFileUsage
+'globalfileusage-for' => '$1 is a file name',
+'globalfileusage-no-results' => '$1 is a file name',
+'globalfileusage-on-wiki' => '$2 is a wiki name',
+
+# Special:GlobalTemplateUsage
+'globaltemplateusage-for' => '$1 is a template name',
+'globaltemplateusage-no-results' => '$1 is a template name',
+'globaltemplateusage-on-wiki' => '$2 is a wiki name',
+
# SQLite database support
'sqlite-has-fts' => 'Shown on Special:Version, $1 is version',
'sqlite-no-fts' => 'Shown on Special:Version, $1 is version',
--- /dev/null
+-- Table associating distant wiki IDs with their interwiki prefixes.
+CREATE TABLE /*_*/globalinterwiki (
+ -- The wiki ID of the wiki
+ giw_wikiid varchar(64) NOT NULL,
+
+ -- The interwiki prefix of that wiki
+ giw_prefix varchar(32) NOT NULL
+
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/giw_index ON /*_*/globalinterwiki (giw_wikiid, giw_prefix);
--- /dev/null
+-- Table listing distant wiki namespace texts.
+CREATE TABLE /*_*/globalnamespaces (
+ -- The wiki ID of the remote wiki
+ gn_wiki varchar(64) NOT NULL,
+
+ -- The namespace ID of the transcluded page on that wiki
+ gn_namespace int NOT NULL,
+
+ -- The namespace text of transcluded page
+ -- Needed for display purposes, since the local namespace ID doesn't necessarily match a distant one
+ gn_namespacetext varchar(255) NOT NULL
+
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/gn_index ON /*_*/globalnamespaces (gn_wiki, gn_namespace, gn_namespacetext);
--- /dev/null
+-- Table tracking interwiki transclusions in the spirit of templatelinks.
+-- This table tracks transclusions of this wiki's templates on another wiki
+-- The gtl_from_* fields describe the (remote) page the template is transcluded from
+-- The gtl_to_* fields describe the (local) template being transcluded
+CREATE TABLE /*_*/globaltemplatelinks (
+ -- The wiki ID of the remote wiki
+ gtl_from_wiki varchar(64) NOT NULL,
+
+ -- The page ID of the calling page on the remote wiki
+ gtl_from_page int unsigned NOT NULL,
+
+ -- The namespace of the calling page on the remote wiki
+ -- Needed for display purposes, since the foreign namespace ID doesn't necessarily match a local one
+ -- The link between the namespace and the namespace name is made by the globalnamespaces table
+ gtl_from_namespace int NOT NULL,
+
+ -- The title of the calling page on the remote wiki
+ -- Needed for display purposes
+ gtl_from_title varchar(255) binary NOT NULL,
+
+ -- The interwiki prefix of the wiki that hosts the transcluded page
+ gtl_to_prefix varchar(32) NOT NULL,
+
+ -- The namespace of the transcluded page on that wiki
+ gtl_to_namespace int NOT NULL,
+
+ -- The namespace name of transcluded page
+ -- Needed for display purposes, since the local namespace ID doesn't necessarily match a distant one
+ gtl_to_namespacetext varchar(255) NOT NULL,
+
+ -- The title of the transcluded page on that wiki
+ gtl_to_title varchar(255) binary NOT NULL
+) /*$wgDBTableOptions*/;
+
+CREATE UNIQUE INDEX /*i*/gtl_to_from ON /*_*/globaltemplatelinks (gtl_to_prefix, gtl_to_namespace, gtl_to_title, gtl_from_wiki, gtl_from_page);
+CREATE UNIQUE INDEX /*i*/gtl_from_to ON /*_*/globaltemplatelinks (gtl_from_wiki, gtl_from_page, gtl_to_prefix, gtl_to_namespace, gtl_to_title);
'templatesused',
'templatesusedpreview',
'templatesusedsection',
+ 'distanttemplatesused',
+ 'distanttemplatesusedpreview',
+ 'distanttemplatesusedsection',
'template-protected',
'template-semiprotected',
'hiddencategories',
-- Should cover *most* configuration - strings, ints, bools, etc.
CREATE INDEX /*i*/cf_name_value ON /*_*/config (cf_name,cf_value(255));
+-- Table tracking interwiki transclusions in the spirit of templatelinks.
+-- This table tracks transclusions of this wiki's templates on another wiki
+-- The gtl_from_* fields describe the (remote) page the template is transcluded from
+-- The gtl_to_* fields describe the (local) template being transcluded
+CREATE TABLE /*_*/globaltemplatelinks (
+ -- The wiki ID of the remote wiki
+ gtl_from_wiki varchar(64) NOT NULL,
+
+ -- The page ID of the calling page on the remote wiki
+ gtl_from_page int unsigned NOT NULL,
+
+ -- The namespace of the calling page on the remote wiki
+ -- Needed for display purposes, since the foreign namespace ID doesn't necessarily match a local one
+ -- The link between the namespace and the namespace name is made by the globalnamespaces table
+ gtl_from_namespace int NOT NULL,
+
+ -- The title of the calling page on the remote wiki
+ -- Needed for display purposes
+ gtl_from_title varchar(255) binary NOT NULL,
+
+ -- The interwiki prefix of the wiki that hosts the transcluded page
+ gtl_to_prefix varchar(32) NOT NULL,
+
+ -- The namespace of the transcluded page on that wiki
+ gtl_to_namespace int NOT NULL,
+
+ -- The namespace name of transcluded page
+ -- Needed for display purposes, since the local namespace ID doesn't necessarily match a distant one
+ gtl_to_namespacetext varchar(255) NOT NULL,
+
+ -- The title of the transcluded page on that wiki
+ gtl_to_title varchar(255) binary NOT NULL
+) /*$wgDBTableOptions*/;
+
+CREATE UNIQUE INDEX /*i*/gtl_to_from ON /*_*/globaltemplatelinks (gtl_to_prefix, gtl_to_namespace, gtl_to_title, gtl_from_wiki, gtl_from_page);
+CREATE UNIQUE INDEX /*i*/gtl_from_to ON /*_*/globaltemplatelinks (gtl_from_wiki, gtl_from_page, gtl_to_prefix, gtl_to_namespace, gtl_to_title);
+
+-- Table listing distant wiki namespace texts.
+CREATE TABLE /*_*/globalnamespaces (
+ -- The wiki ID of the remote wiki
+ gn_wiki varchar(64) NOT NULL,
+
+ -- The namespace ID of the transcluded page on that wiki
+ gn_namespace int NOT NULL,
+
+ -- The namespace text of transcluded page
+ -- Needed for display purposes, since the local namespace ID doesn't necessarily match a distant one
+ gn_namespacetext varchar(255) NOT NULL
+
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/gn_index ON /*_*/globalnamespaces (gn_wiki, gn_namespace, gn_namespacetext);
+
+-- Table associating distant wiki IDs with their interwiki prefixes.
+CREATE TABLE /*_*/globalinterwiki (
+ -- The wiki ID of the wiki
+ giw_wikiid varchar(64) NOT NULL,
+
+ -- The interwiki prefix of that wiki
+ giw_prefix varchar(32) NOT NULL
+
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/giw_index ON /*_*/globalinterwiki (giw_wikiid, giw_prefix);
+
+
-- vim: sw=2 sts=2 et