250 行
7.8 KiB
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 );
|
|
}
|
|
}
|