Current File : /home/jvzmxxx/wiki/extensions/TimedMediaHandler/SpecialOrphanedTimedText.php
<?php
/**
 * Implements Special:OrphanedTimedText
 *
 * @author Brian Wolff
 * @file
 * @ingroup SpecialPage
 */

/**
 * Lists TimedText pages that don't have a corresponding video.
 *
 * @ingroup SpecialPage
 */
class SpecialOrphanedTimedText extends PageQueryPage {

	/** @var Array with keys being names of valid files */
	private $existingFiles;

	public function __construct( $name = 'OrphanedTimedText' ) {
		parent::__construct( $name );
	}

	/**
	 * This is alphabetical, so sort ascending.
	 */
	public function sortDescending() {
		return false;
	}

	/**
	 * Should this be cached?
	 *
	 * This query is actually almost cheap given the current
	 * number of things in TimedText namespace.
	 */
	public function isExpensive() {
		return true;
	}

	/**
	 * Main execution function
	 *
	 * @param $par String subpage
	 */
	public function execute( $par ) {
		global $wgEnableLocalTimedText;

		if ( !$wgEnableLocalTimedText ) {
			$this->setHeaders();
			$this->getOutput()->addWikiMsg( 'orphanedtimedtext-notimedtext' );
			return;
		} elseif ( !$this->canExecuteQuery() ) {
			$this->setHeaders();
			$this->outputHeader();
			$this->getOutput()->addWikiMsg( 'orphanedtimedtext-unsupported' );
			return;
		}
		return parent::execute( $par );
	}

	/**
	 * Can we cache the results of this query?
	 *
	 * Only if we support the query.
	 * @return bool
	 */
	public function isCacheable() {
		return $this->canExecute();
	}

	/**
	 * List in Special:SpecialPages?
	 *
	 * @return bool
	 */
	public function isListed() {
		return $this->canExecute();
	}

	/**
	 * Can we execute this special page?
	 *
	 * The query uses a mysql specific feature (substring_index), so disable on non mysql dbs.
	 *
	 * @return bool
	 */
	private function canExecuteQuery() {
		$dbr = wfGetDB( DB_SLAVE );
		return $dbr->getType() === 'mysql';
	}

	/**
	 * Can we execute this special page
	 *
	 * That is, db is mysql, and TimedText namespace enabled.
	 */
	private function canExecute() {
		global $wgEnableLocalTimedText;

		return $this->canExecuteQuery() && $wgEnableLocalTimedText;
	}

	/**
	 * Get query info
	 *
	 * The query here is meant to retrieve all pages in the TimedText namespace,
	 * such that if you strip the last two extensions (e.g. Foo.bar.baz.en.srt -> Foo.bar.baz)
	 * there is no corresponding img_name in image table. So if there is a page in TimedText
	 * namespace named TimedText:My.Dog.webm.ceb.srt, it will include it in the list provided
	 * that File:My.Dog.webm is not uploaded.
	 *
	 * TimedText does not support file redirects or foreign files, so we don't have
	 * to worry about those.
	 *
	 * Potentially this should maybe also include pages not ending in
	 * .<valid lang code>.srt . However, determining what a valid lang code
	 * is, is pretty hard (although perhaps it could check if its [a-z]{2,3}
	 * however then we've got things like roa-tara, cbk-zam, etc)
	 * and TimedText throws away the final .srt extension and will work with
	 * any extension, so things not ending in .srt arguably aren't oprhaned.
	 *
	 * @note This uses "substring_index" which is a mysql extension.
	 * @return Array Standard query info values.
	 */
	function getQueryInfo() {
		$tables = [ 'page', 'image' ];
		$fields = [
			'namespace' => 'page_namespace',
			'title' => 'page_title',
			'value' => 0,
		];
		$conds = [
			'img_name' => null,
			'page_namespace' => NS_TIMEDTEXT,
		];

		// Now for the complicated bit
		// Note: This bit is mysql specific. Probably could do something
		// equivalent in postgress via split_part or regex substr,
		// but my sql-fu is not good enough to figure out how to do
		// this in standard sql, or in sqlite.
		$baseCond = 'substr( page_title, 1, length( page_title ) - '
			. "length( substring_index( page_title, '.' ,-2 ) ) - 1 )";
		$joinConds = [
			'image' => [
				'LEFT OUTER JOIN',
				 $baseCond . ' = img_name'
			]
		];
		return [
			'tables' => $tables,
			'fields' => $fields,
			'conds' => $conds,
			'join_conds' => $joinConds
		];
	}

	public function getOrderFields() {
		return [ 'namespace', 'title' ];
	}

	/**
	 * Is the TimedText page really orphaned?
	 *
	 * Given a title like "TimedText:Some bit here.webm.en.srt"
	 * check to see if "File:Some bit here.webm" really exists (locally).
	 * @return bool True if we should cross out the line.
	 */
	protected function existenceCheck( Title $title ) {
		$fileTitle = $this->getCorrespondingFile( $title );
		if ( !$fileTitle ) {
			return $title && !$title->isKnown();
		}
		return !$title->isKnown() ||
			( isset( $this->existingFiles[ $fileTitle->getDBKey() ] )
			&& $this->existingFiles[$fileTitle->getDBKey()]->getHandler()
			&& $this->existingFiles[$fileTitle->getDBKey()]->getHandler() instanceof TimedMediaHandler );
	}

	/**
	 * Given a TimedText title, get the File title
	 *
	 * @return Title|null Title in File namespace. null on error.
	 */
	private function getCorrespondingFile( Title $timedText ) {
		if ( !$timedText ) {
			return false;
		}
		$titleParts = explode( '.', $timedText->getDBkey() );
		array_pop( $titleParts );
		array_pop( $titleParts );
		$fileTitle = Title::makeTitleSafe( NS_FILE, implode( '.', $titleParts ) );
		return $fileTitle;
	}

	/**
	 * What group to include this page in on Special:SpecialPages
	 * @return String
	 */
	protected function getGroupName() {
		return 'media';
	}

	/**
	 * Preprocess result to do existence checks all at once.
	 *
	 * @param $db Database
	 * @param $res ResultWraper
	 */
	public function preprocessResults( $db, $res ) {
		parent::preprocessResults( $db, $res );

		if ( !$res->numRows() ) {
			return;
		}

		$filesToLookFor = [];
		foreach ( $res as $row ) {
			$title = Title::makeTitle( $row->namespace, $row->title );
			$fileTitle = $this->getCorrespondingFile( $title );
			if ( !$fileTitle ) {
				continue;
			}
			$filesToLookFor[] = [ 'title' => $fileTitle, 'ignoreRedirect' => true ];
		}
		$this->existingFiles = RepoGroup::singleton()->getLocalRepo()->findFiles( $filesToLookFor );
		$res->seek( 0 );
	}

	/**
	 * Format the result as a simple link to the page
	 *
	 * Based on parent class but with an existence check added.
	 *
	 * @param Skin $skin
	 * @param object $row Result row
	 * @return string
	 */
	public function formatResult( $skin, $row ) {
		global $wgContLang;

		$title = Title::makeTitleSafe( $row->namespace, $row->title );

		if ( $title instanceof Title ) {
			$text = $wgContLang->convert( $title->getPrefixedText() );
			$link = Linker::link( $title, htmlspecialchars( $text ) );
			if ( $this->existenceCheck( $title ) ) {
				// File got uploaded since this page was cached
				$link = '<del>' . $link . '</del>';
			}
			return $link;
		} else {
			return Html::element( 'span', [ 'class' => 'mw-invalidtitle' ],
				Linker::getInvalidTitleDescription( $this->getContext(), $row->namespace, $row->title ) );
		}
	}
}