summaryrefslogtreecommitdiff
path: root/extensions/TimedMediaHandler/SpecialOrphanedTimedText.php
diff options
context:
space:
mode:
Diffstat (limited to 'extensions/TimedMediaHandler/SpecialOrphanedTimedText.php')
-rw-r--r--extensions/TimedMediaHandler/SpecialOrphanedTimedText.php258
1 files changed, 258 insertions, 0 deletions
diff --git a/extensions/TimedMediaHandler/SpecialOrphanedTimedText.php b/extensions/TimedMediaHandler/SpecialOrphanedTimedText.php
new file mode 100644
index 00000000..52e86bf4
--- /dev/null
+++ b/extensions/TimedMediaHandler/SpecialOrphanedTimedText.php
@@ -0,0 +1,258 @@
+<?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 = array( 'page', 'image' );
+ $fields = array(
+ 'namespace' => 'page_namespace',
+ 'title' => 'page_title',
+ 'value' => 0,
+ );
+ $conds = array(
+ '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 = array(
+ 'image' => array(
+ 'LEFT OUTER JOIN',
+ $baseCond . ' = img_name'
+ )
+ );
+ return array(
+ 'tables' => $tables,
+ 'fields' => $fields,
+ 'conds' => $conds,
+ 'join_conds' => $joinConds
+ );
+ }
+
+ public function getOrderFields() {
+ return array( '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 = array();
+ foreach( $res as $row ) {
+ $title = Title::makeTitle( $row->namespace, $row->title );
+ $fileTitle = $this->getCorrespondingFile( $title );
+ if ( !$fileTitle ) {
+ continue;
+ }
+ $filesToLookFor[] = array( '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', array( 'class' => 'mw-invalidtitle' ),
+ Linker::getInvalidTitleDescription( $this->getContext(), $row->namespace, $row->title ) );
+ }
+ }
+}