mediawiki-extensions-Common.../src/HookHandler.php

250 行
7.8 KiB
PHP

<?php
namespace CommonsMetadata;
use CommonsMetadata\Hooks\SkinAfterBottomScriptsHandler;
use Content;
use DerivativeContext;
use File;
use FormatMetadata;
use IContextSource;
use Language;
use LocalRepo;
use MediaWiki\Content\Hook\ContentAlterParserOutputHook;
use MediaWiki\Hook\GetExtendedMetadataHook;
use MediaWiki\Hook\SkinAfterBottomScriptsHook;
use MediaWiki\Hook\ValidateExtendedMetadataCacheHook;
use MediaWiki\MediaWikiServices;
use MediaWiki\Title\Title;
use ParserOutput;
use Skin;
/**
* Hook handler
*/
class HookHandler implements
GetExtendedMetadataHook,
ValidateExtendedMetadataCacheHook,
ContentAlterParserOutputHook,
SkinAfterBottomScriptsHook
{
/**
* Metadata version. When getting metadata of a remote file via the API, sometimes
* we get the data generated by a CommonsMetadata extension installed at the remote,
* as well. We use this version number to keep track of whether that data is different
* from what would be generated here.
* @var float
*/
private const VERSION = 1.2;
/**
* Hook handler for extended metadata
*
* @param array &$combinedMeta Metadata so far
* @param File $file The file object in question
* @param IContextSource $context Context. Used to select language
* @param bool $singleLang Get only target language, or all translations
* @param int &$maxCache How many seconds to cache the result
*/
public function onGetExtendedMetadata(
&$combinedMeta, $file, $context, $singleLang, &$maxCache
) {
global $wgCommonsMetadataForceRecalculate;
if (
isset( $combinedMeta['CommonsMetadataExtension']['value'] )
&& $combinedMeta['CommonsMetadataExtension']['value'] == self::VERSION
&& !$wgCommonsMetadataForceRecalculate
) {
// This is a file from a remote API repo, and CommonsMetadata is installed on
// the remote as well, and generates the same metadata format. We have nothing to do.
return;
} else {
$combinedMeta['CommonsMetadataExtension'] = [
'value' => self::VERSION,
'source' => 'extension',
];
}
$lang = $context->getLanguage();
$templateParser = new TemplateParser();
$templateParser->setMultiLanguage( !$singleLang );
$fallbacks = MediaWikiServices::getInstance()->getLanguageFallback()->getAll( $lang->getCode() );
array_unshift( $fallbacks, $lang->getCode() );
$templateParser->setPriorityLanguages( $fallbacks );
$dataCollector = new DataCollector();
$dataCollector->setLanguage( $lang );
$dataCollector->setMultiLang( !$singleLang );
$dataCollector->setTemplateParser( $templateParser );
$dataCollector->setLicenseParser( new LicenseParser() );
$dataCollector->collect( $combinedMeta, $file );
if ( !$file->getDescriptionTouched() ) {
// Not all files provide the last update time of the description.
// If that's the case, just cache blindly for a shorter period.
$maxCache = 60 * 60 * 12;
}
}
/**
* Hook to check if cache is stale
*
* @param string $timestamp Timestamp of when cache taken
* @param File $file The file metadata is for
* @return bool Is metadata still valid
*/
public function onValidateExtendedMetadataCache( $timestamp, $file ) {
return // use cached value if...
// we don't know when the file was last updated
!$file->getDescriptionTouched()
// or last update was before we cached it
|| wfTimestamp( TS_UNIX, $file->getDescriptionTouched() )
<= wfTimestamp( TS_UNIX, $timestamp );
}
/**
* Check HTML output of a file page to see if it has all the basic metadata, and
* add tracking categories if it does not.
* @param Content $content
* @param Title $title
* @param ParserOutput $parserOutput
*/
public function onContentAlterParserOutput(
$content, $title, $parserOutput
) {
global $wgCommonsMetadataSetTrackingCategories;
if (
!$wgCommonsMetadataSetTrackingCategories
|| !$title->inNamespace( NS_FILE )
|| !$parserOutput->hasText()
|| $content->getModel() !== CONTENT_MODEL_WIKITEXT
) {
return;
}
/*
* We also need to check if the file can be found. This is pretty straightforward, except
* for when a file gets moved: the old & new file details are cached, and cache is purged
* later on, in a DeferredUpdate.
* We could just `$repo->findFile( $title, [ 'ignoreRedirect' => true, 'latest' => true ] )`
* to force it to always check the database, but apart from file moves, the data in cache
* (if any) is usually just fine.
* Instead, we'll:
* * first test if `$title->isRedirect()`, to weed out the old (now renamed) title
* * attempt to fetch from cache, which should usually be fine
* * then fallback to DB, for files that have just been renamed
*/
$services = MediaWikiServices::getInstance();
$trackingCategories = $services->getTrackingCategories();
$repo = $services->getRepoGroup()->getLocalRepo();
if ( $title->isRedirect() ) {
return;
}
$file = $repo->findFile( $title, [ 'ignoreRedirect' => true ] );
if ( $file === false ) {
$file = $repo->findFile( $title, [ 'ignoreRedirect' => true, 'latest' => true ] );
if ( $file === false ) {
return;
}
}
$language = $content->getContentHandler()->getPageViewLanguage( $title, $content );
$dataCollector = self::getDataCollector( $language, true );
$categoryKeys = $dataCollector->verifyAttributionMetadata( $parserOutput, $file );
foreach ( $categoryKeys as $key ) {
$trackingCategories->addTrackingCategory(
$parserOutput,
'commonsmetadata-trackingcategory-' . $key,
$title
);
}
}
/**
* @param Language $lang
* @param bool $singleLang
* @return DataCollector
*/
private static function getDataCollector( Language $lang, $singleLang ) {
$templateParser = new TemplateParser();
$templateParser->setMultiLanguage( !$singleLang );
$fallbacks = MediaWikiServices::getInstance()->getLanguageFallback()->getAll( $lang->getCode() );
array_unshift( $fallbacks, $lang->getCode() );
$templateParser->setPriorityLanguages( $fallbacks );
$dataCollector = new DataCollector();
$dataCollector->setLanguage( $lang );
$dataCollector->setMultiLang( !$singleLang );
$dataCollector->setTemplateParser( $templateParser );
$dataCollector->setLicenseParser( new LicenseParser() );
return $dataCollector;
}
/**
* Injects an inline JSON-LD script schema with image license info.
*
* See https://phabricator.wikimedia.org/T254039. This only adds the script
* to File pages.
*
* @param Skin $skin
* @param string &$html
*/
public function onSkinAfterBottomScripts( $skin, &$html ) {
$title = $skin->getOutput()->getTitle();
$isFilePage = $title->inNamespace( NS_FILE );
if (
!$title ||
!$title->exists() ||
!$isFilePage
) {
return;
}
$localRepo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
// Get and prepare FormatMetadata object.
$format = new FormatMetadata;
$context = new DerivativeContext( $format->getContext() );
// Language doesn't matter so just use en to improve performance.
$format->setSingleLanguage( true );
$context->setLanguage( 'en' );
$format->setContext( $context );
// Get URL for public domain page from config.
$config = MediaWikiServices::getInstance()->getConfigFactory()->makeConfig( 'CommonsMetadata' );
$publicDomainPageUrl = $config->get( 'CommonsMetadataPublicDomainPageUrl' );
$handler = new SkinAfterBottomScriptsHandler( $format, $publicDomainPageUrl );
$html .= $this->doSkinAfterBottomScripts(
$localRepo,
$handler,
$title
);
}
/**
* Get schema script html (or empty string).
*
* @param LocalRepo $localRepo
* @param SkinAfterBottomScriptsHandler $handler
* @param Title $title
* @return string
*/
public function doSkinAfterBottomScripts(
LocalRepo $localRepo,
SkinAfterBottomScriptsHandler $handler,
Title $title
) {
$file = $localRepo->newFile( $title );
return $handler->getSchemaElement( $title, $file );
}
}