Current File : /home/jvzmxxx/wiki/extensions/TimedMediaHandler/TimedMediaHandler.hooks.php
<?php

/**
 * Hooks for TimedMediaHandler extension
 *
 * @file
 * @ingroup Extensions
 */

class TimedMediaHandlerHooks {

	// Register TimedMediaHandler namespace IDs
	// These are configurable due to Commons history: T123823
	// These need to be before registerhooks due to: T123695
	public static function addCanonicalNamespaces( array &$list ) {
		global $wgEnableLocalTimedText, $wgTimedTextNS;
		if ( $wgEnableLocalTimedText ) {
			if ( !defined( 'NS_TIMEDTEXT' ) ) {
				define( 'NS_TIMEDTEXT', $wgTimedTextNS );
				define( 'NS_TIMEDTEXT_TALK', $wgTimedTextNS +1 );
			}

			$list[NS_TIMEDTEXT] = 'TimedText';
			$list[NS_TIMEDTEXT_TALK] = 'TimedText_talk';
		} else {
			$wgTimedTextNS = false;
		}
		return true;
	}

	// At some point these should be registered in extension.json
	// But for now we register them dynamically, because they are config dependent,
	// while we have two players
	public static function resourceLoaderRegisterModules( &$resourceLoader ) {
		$baseExtensionResource = [
			'localBasePath' => __DIR__,
			'remoteExtPath' => 'TimedMediaHandler',
		];

		$resourceModules = [
			'mw.PopUpMediaTransform' => $baseExtensionResource + [
					'scripts' => 'resources/mw.PopUpThumbVideo.js',
					'dependencies' => [
						'mw.MwEmbedSupport',
						'mediawiki.Title',
						'mw.PopUpMediaTransform.styles'
					],
					'position' => 'top',
				],
			'mw.PopUpMediaTransform.styles' => $baseExtensionResource + [
					'position' => 'top',
					'styles' => 'resources/PopUpThumbVideo.css',
				],
			'mw.TMHGalleryHook.js' => $baseExtensionResource + [
					'scripts' => 'resources/mw.TMHGalleryHook.js',
					// position top needed as it needs to load before mediawiki.page.gallery
					'position' => 'top',
				],
			'ext.tmh.embedPlayerIframe' => $baseExtensionResource + [
					'scripts' => 'resources/ext.tmh.embedPlayerIframe.js',
					'dependencies' => [
						'jquery.embedPlayer',
						'mw.MwEmbedSupport',
					],
				],
			"mw.MediaWikiPlayerSupport" =>  $baseExtensionResource + [
					'scripts' => 'resources/mw.MediaWikiPlayerSupport.js',
					'dependencies'=> [
						'mw.Api',
						'mw.MwEmbedSupport',
					],
				],
			// adds support MediaWikiPlayerSupport player bindings
			"mw.MediaWikiPlayer.loader" => $baseExtensionResource + [
					'scripts' => 'resources/mw.MediaWikiPlayer.loader.js',
					'dependencies' => [
						"mw.EmbedPlayer.loader",
						"mw.TimedText.loader",
					],
					'position' => 'top',
				],
			'ext.tmh.video-js' => $baseExtensionResource + [
					'scripts' => 'resources/videojs/video.js',
					'styles' => 'resources/videojs/video-js.css',
					'noflip' => true,
					'targets' => [ 'mobile', 'desktop' ],
					'languageScripts' => [
						'ar' => 'resources/videojs/lang/ar.js',
						'ba' => 'resources/videojs/lang/ba.js',
						'bg' => 'resources/videojs/lang/bg.js',
						'ca' => 'resources/videojs/lang/ca.js',
						'cs' => 'resources/videojs/lang/cs.js',
						'da' => 'resources/videojs/lang/da.js',
						'de' => 'resources/videojs/lang/de.js',
						'el' => 'resources/videojs/lang/el.js',
						'en' => 'resources/videojs/lang/en.js',
						'es' => 'resources/videojs/lang/es.js',
						'fa' => 'resources/videojs/lang/fa.js',
						'fi' => 'resources/videojs/lang/fi.js',
						'fr' => 'resources/videojs/lang/fr.js',
						'hr' => 'resources/videojs/lang/hr.js',
						'hu' => 'resources/videojs/lang/hu.js',
						'it' => 'resources/videojs/lang/it.js',
						'ja' => 'resources/videojs/lang/ja.js',
						'ko' => 'resources/videojs/lang/ko.js',
						'nb' => 'resources/videojs/lang/nb.js',
						'nl' => 'resources/videojs/lang/nl.js',
						'nn' => 'resources/videojs/lang/nn.js',
						'pl' => 'resources/videojs/lang/pl.js',
						'pt-BR' => 'resources/videojs/lang/pt-BR.js',
						'ru' => 'resources/videojs/lang/ru.js',
						'sr' => 'resources/videojs/lang/sr.js',
						'sv' => 'resources/videojs/lang/sv.js',
						'tr' => 'resources/videojs/lang/tr.js',
						'uk' => 'resources/videojs/lang/uk.js',
						'vi' => 'resources/videojs/lang/vi.js',
						'zh-CN' => 'resources/videojs/lang/zh-CN.js',
						'zh-TW' => 'resources/videojs/lang/zh-TW.js',
					],
				],
			'ext.tmh.videojs-ogvjs' => $baseExtensionResource + [
					'scripts' => 'resources/videojs-ogvjs/videojs-ogvjs.js',
					'targets' => [ 'mobile', 'desktop' ],
					'dependencies' => [
						'ext.tmh.video-js',
						'ext.tmh.OgvJs',
					],
				],
			'ext.tmh.videojs-resolution-switcher' => $baseExtensionResource + [
					'scripts' => 'resources/videojs-resolution-switcher/videojs-resolution-switcher.js',
					'styles' => 'resources/videojs-resolution-switcher/videojs-resolution-switcher.css',
					'targets' => [ 'mobile', 'desktop' ],
					'dependencies' => [
						'ext.tmh.video-js',
					],
				],
			'ext.tmh.videojs-responsive-layout' => $baseExtensionResource + [
					'scripts' => 'resources/videojs-responsive-layout/videojs-responsive-layout.js',
					'targets' => [ 'mobile', 'desktop' ],
					'dependencies' => [
						'ext.tmh.video-js',
					],
				],
			'ext.tmh.videojs-replay' => $baseExtensionResource + [
				'scripts' => 'resources/videojs-replay/videojs-replay.js',
				'styles' => 'resources/videojs-replay/videojs-replay.css',
				'targets' => [ 'mobile', 'desktop' ],
				'dependencies' => [
					'ext.tmh.video-js',
				],
			],
			'ext.tmh.mw-info-button' => $baseExtensionResource + [
				'scripts' => 'resources/mw-info-button/mw-info-button.js',
				'styles' => 'resources/mw-info-button/mw-info-button.css',
				'targets' => [ 'mobile', 'desktop' ],
				'dependencies' => [
					'ext.tmh.video-js',
				],
			],
			'ext.tmh.player' => $baseExtensionResource + [
					'scripts' => 'resources/ext.tmh.player.js',
					'targets' => [ 'mobile', 'desktop' ],
					'dependencies' => [
						'ext.tmh.video-js',
						'ext.tmh.videojs-resolution-switcher',
						'ext.tmh.videojs-responsive-layout',
						'ext.tmh.videojs-replay',
						'ext.tmh.mw-info-button',
						'ext.tmh.OgvJsSupport',
					],
					'messages' => [
						'timedmedia-resolution-160',
						'timedmedia-resolution-240',
						'timedmedia-resolution-360',
						'timedmedia-resolution-480',
						'timedmedia-resolution-720',
						'timedmedia-resolution-1080',
						'timedmedia-resolution-1440',
						'timedmedia-resolution-2160',
					],
				],
			'ext.tmh.player.styles' => $baseExtensionResource + [
					'styles' => 'resources/ext.tmh.player.styles.less',
					'targets' => [ 'mobile', 'desktop' ],
				],
		];

		$resourceLoader->register( $resourceModules );
		return true;
	}

	// Register TimedMediaHandler Hooks
	public static function register() {
		global $wgHooks, $wgJobClasses, $wgJobTypesExcludedFromDefaultQueue, $wgMediaHandlers,
		$wgResourceModules, $wgExcludeFromThumbnailPurge,
		$wgFileExtensions, $wgTmhEnableMp4Uploads, $wgExtensionAssetsPath,
		$wgMwEmbedModuleConfig, $wgEnableLocalTimedText, $wgTmhFileExtensions,
		$wgTmhTheoraTwoPassEncoding, $wgTmhWebPlayer, $wgWikimediaJenkinsCI;

		// set config for parser tests
		if ( isset( $wgWikimediaJenkinsCI ) && $wgWikimediaJenkinsCI  === true ) {
			global $wgEnableTranscode, $wgFFmpegLocation;
			$wgEnableTranscode = false;
			$wgFFmpegLocation = '/usr/bin/ffmpeg';
		}

		// Remove mp4 if not enabled:
		if ( $wgTmhEnableMp4Uploads === false ) {
			$index = array_search( 'mp4', $wgFileExtensions );
			if ( $index !== false ) {
				array_splice( $wgFileExtensions, $index, 1 );
			}
		}

		// Enable experimental 2-pass Theora encoding if enabled:
		if ( $wgTmhTheoraTwoPassEncoding ) {
			foreach ( WebVideoTranscode::$derivativeSettings as $key => &$settings ) {
				if ( isset( $settings['videoCodec'] ) && $settings['videoCodec'] === 'theora' ) {
					$settings['twopass'] = 'true';
				}
			}
		}

		if ( self::activePlayerMode() === 'mwembed' ) {
			if ( !class_exists( 'MwEmbedResourceManager' ) ) {
				echo "TimedMediaHandler requires the MwEmbedSupport extension.\n";
				exit( 1 );
			}

			// Register the Timed Media Handler javascript resources ( MwEmbed modules )
			MwEmbedResourceManager::register( 'extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer' );
			MwEmbedResourceManager::register( 'extensions/TimedMediaHandler/MwEmbedModules/TimedText' );

			// Set the default webPath for this embed player extension
			$wgMwEmbedModuleConfig['EmbedPlayer.WebPath'] = $wgExtensionAssetsPath .
				'/' . basename( __DIR__ ) . '/MwEmbedModules/EmbedPlayer';
		}

		// Setup media Handlers:
		$wgMediaHandlers['application/ogg'] = 'OggHandlerTMH';
		$wgMediaHandlers['audio/webm'] = 'WebMHandler';
		$wgMediaHandlers['video/webm'] = 'WebMHandler';
		$wgMediaHandlers['video/mp4'] = 'Mp4Handler';
		$wgMediaHandlers['audio/x-flac'] = 'FLACHandler';
		$wgMediaHandlers['audio/flac'] = 'FLACHandler';
		$wgMediaHandlers['audio/wav'] = 'WAVHandler';

		// Add transcode job class:
		$wgJobClasses['webVideoTranscode'] = 'WebVideoTranscodeJob';

		// Transcode jobs must be explicitly requested from the job queue:
		$wgJobTypesExcludedFromDefaultQueue[] = 'webVideoTranscode';

		$baseExtensionResource = [
			'localBasePath' => __DIR__,
			'remoteExtPath' => 'TimedMediaHandler',
		];

		$wgResourceModules += [
			'ext.tmh.thumbnail.styles' => $baseExtensionResource + [
				'styles' => 'resources/ext.tmh.thumbnail.css',
				'position' => 'top',
			],
			'ext.tmh.transcodetable' => $baseExtensionResource + [
				'scripts' => 'resources/ext.tmh.transcodetable.js',
				'styles' => 'resources/transcodeTable.css',
				'dependencies' => [
					'mediawiki.api.edit',
					'oojs-ui',
				],
				'messages'=> [
					'timedmedia-reset-button-cancel',
					'timedmedia-reset-button-dismiss',
					'timedmedia-reset-button-reset',
					'timedmedia-reset-error',
					'timedmedia-reset',
					'timedmedia-reset-areyousure',
					'timedmedia-reset-explanation',
				]
			],
			'ext.tmh.TimedTextSelector' =>  $baseExtensionResource + [
				'scripts' => 'resources/ext.tmh.TimedTextSelector.js',
			],
			// Add OgvJs-related modules for Safari/IE/Edge Ogg playback
			'ext.tmh.OgvJsSupport' => $baseExtensionResource + [
				'scripts' => [
					'MwEmbedModules/EmbedPlayer/binPlayers/ogv.js/ogv-support.js',
					'resources/ext.tmh.OgvJsSupport.js',
				],
				'targets' => [ 'mobile', 'desktop' ],
			],
			'ext.tmh.OgvJs' => $baseExtensionResource + [
				'scripts' => [
					'MwEmbedModules/EmbedPlayer/binPlayers/ogv.js/ogv.js',
				],
				'dependencies' => 'ext.tmh.OgvJsSupport',
				'targets' => [ 'mobile', 'desktop' ],
			],
			'embedPlayerIframeStyle'=> $baseExtensionResource + [
				'styles' => 'resources/embedPlayerIframe.css',
				'targets' => [ 'mobile', 'desktop' ],
			],
		];

		// Setup a hook for iframe embed handling:
		$wgHooks['ArticleFromTitle'][] = 'TimedMediaIframeOutput::iframeHook';

		// When an upload completes ( check clear any existing transcodes )
		$wgHooks['FileUpload'][] = 'TimedMediaHandlerHooks::onFileUpload';

		// When an image page is moved:
		$wgHooks['TitleMove'][] = 'TimedMediaHandlerHooks::checkTitleMove';

		// When image page is deleted so that we remove transcode settings / files.
		$wgHooks['FileDeleteComplete'][] = 'TimedMediaHandlerHooks::onFileDeleteComplete';

		// Use a BeforePageDisplay hook to load the styles in pages that pull in media dynamically.
		// (Special:Upload, for example, when there is an "existing file" warning.)
		$wgHooks['BeforePageDisplay'][] = 'TimedMediaHandlerHooks::pageOutputHook';

		// Make sure modules are loaded on image pages that don't have a media file in the wikitext.
		$wgHooks['ImageOpenShowImageInlineBefore'][] =
			'TimedMediaHandlerHooks::onImageOpenShowImageInlineBefore';

		// Bug T63923: Make sure modules are loaded for the image history of image pages.
		// This is needed when ImageOpenShowImageInlineBefore is not triggered (diff previews).
		$wgHooks['ImagePageFileHistoryLine'][] = 'TimedMediaHandlerHooks::onImagePageFileHistoryLine';

		// Exclude transcoded assets from normal thumbnail purging
		// ( a maintenance script could handle transcode asset purging)
		if ( isset( $wgExcludeFromThumbnailPurge ) ) {
			$wgExcludeFromThumbnailPurge = array_merge( $wgExcludeFromThumbnailPurge, $wgTmhFileExtensions );
			// Also add the .log file ( used in two pass encoding )
			// ( probably should move in-progress encodes out of web accessible directory )
			$wgExcludeFromThumbnailPurge[] = 'log';
		}

		$wgHooks['LoadExtensionSchemaUpdates'][] = 'TimedMediaHandlerHooks::loadExtensionSchemaUpdates';

		// Add unit tests
		$wgHooks['UnitTestsList'][] = 'TimedMediaHandlerHooks::registerUnitTests';
		$wgHooks['ParserTestTables'][] = 'TimedMediaHandlerHooks::onParserTestTables';
		$wgHooks['ParserTestGlobals'][] = 'TimedMediaHandlerHooks::onParserTestGlobals';

		/**
		 * Add support for the "TimedText" NameSpace
		 */
		if ( $wgEnableLocalTimedText ) {
			// Check for timed text page:
			$wgHooks[ 'ArticleFromTitle' ][] = 'TimedMediaHandlerHooks::checkForTimedTextPage';
			$wgHooks[ 'ArticleContentOnDiff' ][] = 'TimedMediaHandlerHooks::checkForTimedTextDiff';

			$wgHooks[ 'SkinTemplateNavigation' ][] = 'TimedMediaHandlerHooks::onSkinTemplateNavigation';
		} else {
			// overwrite TimedText.ShowInterface for video with mw-provider=local
			$wgMwEmbedModuleConfig['TimedText.ShowInterface.local'] = 'off';
		}

		// Add transcode status to video asset pages:
		$wgHooks['ImagePageAfterImageLinks'][] = 'TimedMediaHandlerHooks::checkForTranscodeStatus';
		$wgHooks['NewRevisionFromEditComplete'][] =
			'TimedMediaHandlerHooks::onNewRevisionFromEditComplete';
		$wgHooks['ArticlePurge'][] = 'TimedMediaHandlerHooks::onArticlePurge';

		$wgHooks['LoadExtensionSchemaUpdates'][] = 'TimedMediaHandlerHooks::checkSchemaUpdates';
		$wgHooks['wgQueryPages'][] = 'TimedMediaHandlerHooks::onwgQueryPages';
		$wgHooks['RejectParserCacheValue'][] = 'TimedMediaHandlerHooks::rejectParserCacheValue';
		return true;
	}

	/**
	 * @param ImagePage $imagePage the imagepage that is being rendered
	 * @param OutputPage $out the output for this imagepage
	 * @return bool
	 */
	public static function onImageOpenShowImageInlineBefore( &$imagePage, &$out ) {
		$file = $imagePage->getDisplayedFile();
		return TimedMediaHandlerHooks::onImagePageHooks( $file, $out );
	}

	/**
	 * @param ImagePage $imagePage that is being rendered
	 * @param File $file the (old) file added in this history entry
	 * @param string &$line the HTML of the history line
	 * @param string &$css the CSS class of the history line
	 * @return bool
	 */
	public static function onImagePageFileHistoryLine( $imagePage, $file, &$line, &$css ) {
		$out = $imagePage->getContext()->getOutput();
		return TimedMediaHandlerHooks::onImagePageHooks( $file, $out );
	}

	/**
	 * @param File $file the file that is being rendered
	 * @param OutputPage $wgOut the output to which this file is being rendered
	 * @return bool
	 */
	private static function onImagePageHooks( $file, $out ) {
		$handler = $file->getHandler();
		if ( $handler !== false && $handler instanceof TimedMediaHandler ) {
			if ( self::activePlayerMode() === 'mwembed' ) {
				$out->addModuleStyles( 'ext.tmh.thumbnail.styles' );
				$out->addModules( [
					'mw.MediaWikiPlayer.loader',
					'mw.PopUpMediaTransform',
					'mw.TMHGalleryHook.js',
				] );
			}

			if ( self::activePlayerMode() === 'videojs' ) {
				$out->addModuleStyles( 'ext.tmh.player.styles' );
				$out->addModules( 'ext.tmh.player' );
			}
		}
		return true;
	}

	/**
	 * @param $title Title
	 * @param $article Article
	 * @return bool
	 */
	public static function checkForTimedTextPage( &$title, &$article ) {
		global $wgTimedTextNS;
		if ( $title->getNamespace() === $wgTimedTextNS ) {
			$article = new TimedTextPage( $title );
		}
		return true;
	}

	/**
	 * @param $diffEngine DifferenceEngine
	 * @param $output OutputPage
	 * @return bool
	 */
	public static function checkForTimedTextDiff( $diffEngine, $output ) {
		global $wgTimedTextNS;
		if ( $output->getTitle()->getNamespace() === $wgTimedTextNS ) {
			$article = new TimedTextPage( $output->getTitle() );
			$article->renderOutput( $output );
			return false;
		}
		return true;
	}

	public static function onSkinTemplateNavigation( SkinTemplate &$sktemplate, array &$links ) {
		if ( self::isTimedMediaHandlerTitle( $sktemplate->getTitle() ) ) {
			$ttTitle = Title::makeTitleSafe( NS_TIMEDTEXT, $sktemplate->getTitle()->getDBkey() );
			if ( !$ttTitle ) {
				return;
			}
			$links[ 'namespaces' ][ 'timedtext' ] =
				$sktemplate->tabAction( $ttTitle, 'timedtext', false, '', false );
		}
	}

	/**
	 * Wraps the isTranscodableFile function
	 * @param $title Title
	 * @return bool
	 */
	public static function isTranscodableTitle( $title ) {
		if ( $title->getNamespace() != NS_FILE ) {
			return false;
		}
		$file = wfFindFile( $title );
		return self::isTranscodableFile( $file );
	}

	/**
	 * Utility function to check if a given file can be "transcoded"
	 * @param $file File object
	 * @return bool
	 */
	public static function isTranscodableFile( & $file ) {
		global $wgEnableTranscode, $wgEnabledAudioTranscodeSet;

		// don't show the transcode table if transcode is disabled
		if ( !$wgEnableTranscode && !$wgEnabledAudioTranscodeSet ) {
			return false;
		}
		// Can't find file
		if ( !$file ) {
			return false;
		}
		// We can only transcode local files
		if ( !$file->isLocal() ) {
			return false;
		}

		$handler = $file->getHandler();
		// Not able to transcode files without handler
		if ( !$handler ) {
			return false;
		}
		$mediaType = $handler->getMetadataType( $file );
		// If ogg or webm format and not audio we can "transcode" this file
		$isAudio = $handler instanceof TimedMediaHandler && $handler->isAudio( $file );
		if ( ( $mediaType == 'webm' || $mediaType == 'ogg' || $mediaType =='mp4' )
			&& !$isAudio
		) {
			return true;
		}
		if ( $isAudio && count( $wgEnabledAudioTranscodeSet ) ) {
			return true;
		}
		return false;
	}

	public static function isTimedMediaHandlerTitle( $title ) {
		if ( !$title->inNamespace( NS_FILE ) ) {
			return false;
		}
		$file = wfFindFile( $title );
		// Can't find file
		if ( !$file ) {
			return false;
		}
		$handler = $file->getHandler();
		if ( !$handler ) {
			return false;
		}
		return $handler instanceof TimedMediaHandler;
	}

	/**
	 * @param $article Article
	 * @param $html string
	 * @return bool
	 */
	public static function checkForTranscodeStatus( $article, &$html ) {
		// load the file:
		$file = wfFindFile( $article->getTitle() );
		if ( self::isTranscodableFile( $file ) ) {
			$html .= TranscodeStatusTable::getHTML( $file );
		}
		return true;
	}

	/**
	 * @param $file LocalFile object
	 * @return bool
	 */
	public static function onFileUpload( $file, $reupload, $hasNewPageContent ) {
		// Check that the file is a transcodable asset:
		if ( $file && self::isTranscodableFile( $file ) ) {
			// Remove all the transcode files and db states for this asset
			WebVideoTranscode::removeTranscodes( $file );
			WebVideoTranscode::startJobQueue( $file );
		}
		return true;
	}
	/**
	 * Handle moved titles
	 *
	 * For now we just remove all the derivatives for the oldTitle. In the future we could
	 * look at moving the files, but right now thumbs are not moved, so I don't want to be
	 * inconsistent.
	 * @param $title Title
	 * @param $newTitle Title
	 * @param $user User
	 * @return bool
	 */
	public static function checkTitleMove( $title, $newTitle, $user ) {
		if ( self::isTranscodableTitle( $title ) ) {
			// Remove all the transcode files and db states for this asset
			// ( will be re-added the first time the asset is displayed with its new title )
			$file = wfFindFile( $title );
			WebVideoTranscode::removeTranscodes( $file );
		}
		return true;
	}

	/**
	 * Hook to FileDeleteComplete
	 * remove transcodes on delete
	 * @param $file File
	 * @param $oldimage
	 * @param $article Article
	 * @param $user User
	 * @param $reason string
	 * @return bool
	 */
	public static function onFileDeleteComplete( $file, $oldimage, $article, $user, $reason ) {
		if ( !$oldimage ) {
			if ( self::isTranscodableFile( $file ) ) {
				WebVideoTranscode::removeTranscodes( $file );
			}
		}
		return true;
	}

	/*
	 * If file gets reverted to a previous version, reset transcodes.
	 */
	public static function onNewRevisionFromEditComplete(
		$article, Revision $rev, $baseID, User $user
	) {
		if ( $baseID !== false ) {
			// Check if the article is a file and remove transcode files:
			if ( $article->getTitle()->getNamespace() == NS_FILE ) {
				$file = wfFindFile( $article->getTitle() );
				if ( self::isTranscodableFile( $file ) ) {
					WebVideoTranscode::removeTranscodes( $file );
					WebVideoTranscode::startJobQueue( $file );
				}
			}
		}
		return true;
	}

	/**
	 * When a user asks for a purge, perhaps through our handy "update transcode status"
	 * link, make sure we've got the updated set of transcodes. This'll allow a user or
	 * automated process to see their status and reset them.
	 */
	public static function onArticlePurge( $article ) {
		if ( $article->getTitle()->getNamespace() == NS_FILE ) {
			$file = wfFindFile( $article->getTitle() );
			if ( self::isTranscodableFile( $file ) ) {
				WebVideoTranscode::cleanupTranscodes( $file );
			}
		}
		return true;
	}

	/**
	 * Adds the transcode sql
	 * @param $updater DatabaseUpdater
	 * @return bool
	 */
	public static function loadExtensionSchemaUpdates( $updater ) {
		$updater->addExtensionTable( 'transcode', __DIR__ . '/TimedMediaHandler.sql' );
		return true;
	}

	/**
	 * Hook to add list of PHPUnit test cases.
	 * @param $files Array of files
	 * @return bool
	 */
	public static function registerUnitTests( array &$files ) {
		$testDir = __DIR__ . '/tests/phpunit/';
		$testFiles = [
			'TestTimeParsing.php',
			'TestApiUploadVideo.php',
			'TestVideoThumbnail.php',
			'TestVideoTranscode.php',
			'TestOggHandler.php',
			'TestWebMHandler.php',
			'TestTimedMediaTransformOutput.php',
			'TestTimedMediaHandler.php'
		];
		foreach ( $testFiles as $fileName ) {
			$files[] = $testDir . $fileName;
		}
		return true;
	}

	/**
	 * Hook to add list of DB tables to copy when running parser tests
	 * @param array &$tables
	 * @return bool
	 */
	public static function onParserTestTables( &$tables ) {
		$tables[] = 'transcode';
		return true;
	}

	/**
	 * Hook to reset player serial so that parser tests are not order-dependent
	 */
	public static function onParserTestGlobals( &$globals ) {
		TimedMediaTransformOutput::resetSerialForTest();
	}

	/**
	 * Add JavaScript and CSS for special pages that may include timed media
	 * but which will not fire the parser hook.
	 *
	 * FIXME: There ought to be a better interface for determining whether the
	 * page is liable to contain timed media.
	 *
	 * @param $out OutputPage
	 * @param $sk
	 * @return bool
	 */
	static function pageOutputHook( &$out, &$sk ) {
		global $wgTimedTextNS;

		$title = $out->getTitle();
		$namespace = $title->getNamespace();
		$addModules = false;

		if ( $namespace === NS_CATEGORY || $namespace === $wgTimedTextNS ) {
			$addModules = true;
		}

		if ( $title->isSpecialPage() ) {
			list( $name, /* subpage */ ) = SpecialPageFactory::resolveAlias( $title->getDBkey() );
			if ( stripos( $name, 'file' ) !== false || stripos( $name, 'image' ) !== false
				|| $name === 'Search' || $name === 'GlobalUsage' || $name === 'Upload' ) {
					$addModules = true;
			}
		}

		if ( $addModules ) {
			if ( self::activePlayerMode() === 'mwembed' ) {
				$out->addModuleStyles( 'ext.tmh.thumbnail.styles' );
				$out->addModules( [
					'mw.MediaWikiPlayer.loader',
					'mw.PopUpMediaTransform',
				] );
			}

			if ( self::activePlayerMode() === 'videojs' ) {
				$out->addModuleStyles( 'ext.tmh.player.styles' );
				$out->addModules( 'ext.tmh.player' );
			}
		}

		return true;
	}

	public static function checkSchemaUpdates( DatabaseUpdater $updater ) {
		$base = __DIR__;

		switch ( $updater->getDB()->getType() ) {
		case 'mysql':
		case 'sqlite':
			 // Initial install tables
			$updater->addExtensionTable( 'transcode', "$base/TimedMediaHandler.sql" );
			$updater->addExtensionUpdate( [ 'addIndex', 'transcode', 'transcode_name_key',
				"$base/archives/transcode_name_key.sql", true ] );
			break;
		case 'postgres':
			// TODO
			break;
		}
		return true;
	}

	public static function onwgQueryPages( $qp ) {
		$qp[] = [ 'SpecialOrphanedTimedText', 'OrphanedTimedText' ];
		return true;
	}

	/**
	 * Return false here to evict existing parseroutput cache
	 */
	public static function rejectParserCacheValue( $parserOutput, $wikiPage, $parserOptions ) {
		if (
			$parserOutput->getExtensionData( 'mw_ext_TMH_hasTimedMediaTransform' )
			|| isset( $parserOutput->hasTimedMediaTransform )
		) {
			/* page has old style TMH elements */
			if (
				self::activePlayerMode() === 'mwembed' &&
				!in_array( 'mw.MediaWikiPlayer.loader', $parserOutput->getModules() )
			) {
				wfDebug( 'Bad TMH parsercache value, throw this out.' );
				$wikiPage->getTitle()->purgeSquid();
				return false;
			}
		}
		return true;
	}

	public static function changePageRenderingHash( &$hash, User $user, &$forOptions ) {
		if ( self::activePlayerMode() === 'videojs' ) {
			if ( $user->getOption( 'tmh-videojs' ) === '1' ) {
				$hash .= '!tmh-videojs';
				return true;
			}
		}
	}

	public static function onGetBetaFeaturePreferences( $user, &$prefs ) {
		global $wgTmhUseBetaFeatures, $wgExtensionAssetsPath;

		if ( $wgTmhUseBetaFeatures ) {
			$prefs['tmh-videojs'] = [
				// The first two are message keys
				'label-message' => 'beta-feature-timedmediahandler-message-videojs',
				'desc-message' => 'beta-feature-timedmediahandler-description-videojs',
				// Paths to images that represents the feature.
				// The image is usually different for ltr and rtl languages.
				// Images for specific languages can also specified using the language code.
				'screenshot' => [
					'ltr' => "$wgExtensionAssetsPath/TimedMediaHandler/resources/BetaFeature_TMH_VIDEOJS.svg",
					'rtl' => "$wgExtensionAssetsPath/TimedMediaHandler/resources/BetaFeature_TMH_VIDEOJS.svg",
				],
				// Link to information on the feature
				'info-link' => 'https://www.mediawiki.org/wiki/Extension:TimedMediaHandler',
				// Link to discussion about the feature
				'discussion-link' => 'https://www.mediawiki.org/wiki/Extension_talk:TimedMediaHandler',
			];
		}
		return true;
	}

	public static function activePlayerMode() {
		global $wgTmhWebPlayer, $wgTmhUseBetaFeatures, $wgUser;
		$context = new RequestContext();
		if ( $wgTmhUseBetaFeatures && class_exists( 'BetaFeatures' ) &&
			$wgUser->isSafeToLoad() && BetaFeatures::isFeatureEnabled( $context->getUser(), 'tmh-videojs' )
		) {
			return 'videojs';
		} else {
			return $wgTmhWebPlayer;
		}
	}
}