Current File : /home/jvzmxxx/wiki1/extensions/TimedMediaHandler/handlers/TextHandler/TextHandler.php
<?php
/**
 * Timed Text handling for mediaWiki
 *
 * Timed text support is presently fairly limited. Unlike Ogg and WebM handlers,
 * timed text does not extend the TimedMediaHandler class.
 *
 * TODO On "new" timedtext language save purge all pages where file exists
 */

class TextHandler {
	// lazy init remote Namespace number
	public $remoteNs = null;
	public $remoteNsName = null;

	/**
	 * @var File
	 */
	protected $file;

	function __construct( $file ) {
		$this->file = $file;
	}

	/**
	 * Get the timed text tracks elements as an associative array
	 * @return array|mixed
	 */
	function getTracks() {
		if ( $this->file->isLocal() ) {
			return $this->getLocalTextSources();
		} elseif ( $this->file->getRepo() instanceof ForeignDBViaLBRepo ) {
			return $this->getForeignDBTextSources();
		} else {
			return $this->getRemoteTextSources();
		}
	}

	/**
	 * @return bool|int|null
	 */
	function getTimedTextNamespace() {
		global $wgEnableLocalTimedText;
		if ( $this->file->isLocal() ) {
			if ( $wgEnableLocalTimedText ) {
				return NS_TIMEDTEXT;
			} else {
				return false;
			}
		} elseif ( $this->file->repo instanceof ForeignDBViaLBRepo ){
			global $wgTimedTextForeignNamespaces;
			$wikiID = $this->file->getRepo()->getSlaveDB()->getWikiID();
			if ( isset( $wgTimedTextForeignNamespaces[ $wikiID ] ) ) {
				return $wgTimedTextForeignNamespaces[ $wikiID ];
			}
			// failed to get namespace via ForeignDBViaLBRepo, return NS_TIMEDTEXT
			if ( $wgEnableLocalTimedText ) {
				return NS_TIMEDTEXT;
			} else {
				return false;
			}
		} else {
			if ( $this->remoteNs !== null ) {
				return $this->remoteNs;
			}
			// Get the namespace data from the image api repo:
			// fetchImageQuery query caches results
			$data = $this->file->getRepo()->fetchImageQuery( [
				'meta' =>'siteinfo',
				'siprop' => 'namespaces'
			] );

			if ( isset( $data['query'] ) && isset( $data['query']['namespaces'] ) ) {
				// get the ~last~ timed text namespace defined
				foreach ( $data['query']['namespaces'] as $ns ) {
					if ( isset( $ns['canonical'] ) && $ns['canonical'] === 'TimedText' ) {
						$this->remoteNs = $ns['id'];
						$this->remoteNsName = $ns['*'];
						wfDebug( "Discovered remoteNs: $this->remoteNs and name: $this->remoteNsName \n" );
						break;
					}
				}
			}
			// Return the remote Ns
			return $this->remoteNs;
		}
	}

	/**
	 * Retrieve a list of TimedText pages in the database that start with
	 * the name of the file associated with this handler.
	 *
	 * If the file is on a foreign repo, will query the ForeignDb
	 *
	 * @return ResultWrapper|bool
	 */
	function getTextPages() {
		$ns = $this->getTimedTextNamespace();
		if ( $ns === false ) {
			wfDebug( 'Repo: ' . $this->file->repo->getName() . " does not have a TimedText namespace \n" );
			// No timed text namespace, don't try to look up timed text tracks
			return false;
		}
		$dbr = $this->file->getRepo()->getSlaveDB();
		$prefix = $this->file->getTitle()->getDBkey();
		return $dbr->select(
			'page',
			[ 'page_namespace', 'page_title' ],
			[
				'page_namespace' => $ns,
				'page_title ' . $dbr->buildLike( $prefix, $dbr->anyString() )
			],
			__METHOD__,
			[
				'LIMIT' => 300,
				'ORDER BY' => 'page_title'
			]
		);
	}

	/**
	 * Build the api query to find TimedText pages belonging to a remote file
	 * @return array|bool
	 */
	function getRemoteTextPagesQuery() {
		$ns = $this->getTimedTextNamespace();
		if ( $ns === false ) {
			wfDebug( 'Repo: ' . $this->file->repo->getName() . " does not have a TimedText namespace \n" );
			// No timed text namespace, don't try to look up timed text tracks
			return false;
		}
		return [
			'action' => 'query',
			'titles' => $this->file->getTitle()->getPrefixedDBkey(),
			'prop' => 'videoinfo',
			'viprop' => 'timedtext',
			'formatversion' => '2',
		];
	}

	/**
	 * Retrieve the text sources belonging to a remote file
	 * @return array|mixed
	 */
	function getRemoteTextSources() {
		global $wgMemc;
		// Use descriptionCacheExpiry as our expire for timed text tracks info
		if ( $this->file->getRepo()->descriptionCacheExpiry > 0 ) {
			wfDebug( "Attempting to get text tracks from cache..." );
			$key = $this->file->getRepo()->getLocalCacheKey(
				'RemoteTextTracks', 'url', $this->file->getName()
			);
			$obj = $wgMemc->get( $key );
			if ( $obj ) {
				wfDebug( "success!\n" );
				return $obj;
			}
			wfDebug( "miss\n" );
		}
		wfDebug( "Get text tracks from remote api \n" );
		$query = $this->getRemoteTextPagesQuery();

		// Error in getting timed text namespace return empty array;
		if ( $query === false ) {
			return [];
		}
		$data = $this->file->getRepo()->fetchImageQuery( $query );
		$textTracks = $this->getTextTracksFromData( $data );
		if ( $data && $this->file->repo->descriptionCacheExpiry > 0 ) {
			$wgMemc->set( $key, $textTracks, $this->file->repo->descriptionCacheExpiry );
		}
		return $textTracks;
	}

	/**
	 * Retrieve the text sources belonging to a foreign db accessible file
	 * @return array
	 */
	function getForeignDBTextSources() {
		$data = $this->getTextPages();
		if ( $data !== false ) {
			return $this->getTextTracksFromRows( $data );
		}
		return [];
	}

	/**
	 * Retrieve the text sources belonging to a local file
	 * @return array
	 */
	function getLocalTextSources() {
		global $wgEnableLocalTimedText;
		if ( $wgEnableLocalTimedText ) {
			$data = $this->getTextPages();
			if ( $data !== false ) {
				return $this->getTextTracksFromRows( $data );
			}
		}
		return [];
	}

	/**
	 * Build an array of track information using a Database result
	 * Handles both local and foreign Db results
	 *
	 * @param ResultWrapper $data Database result with page titles
	 * @return array
	 */
	function getTextTracksFromRows( ResultWrapper $data ) {
		$textTracks = [];
		$providerName = $this->file->repo->getName();
		// commons is called shared in production. normalize it to wikimediacommons
		if ( $providerName === 'shared' ) {
			$providerName = 'wikimediacommons';
		}
		// Provider name should be the same as the interwiki map

		if ( !$this->file->isLocal() ) {
			$namespaceName = $this->getForeignNamespaceName();
		}
		$langNames = Language::fetchLanguageNames( null, 'mw' );

		foreach ( $data as $row ) {
			// Note, the namespace ID of this title might be 'unknown'
			// to our configuration if this is called in ForeignDb situations
			if ( $this->file->isLocal() ) {
				$subTitle = Title::newFromRow( $row );
			} else {
				$subTitle = new ForeignTitle( $row->page_namespace, $namespaceName, $row->page_title );
			}
			$titleParts = explode( '.', $row->page_title );
			if ( count( $titleParts ) >= 3 ) {
				$timedTextExtension = array_pop( $titleParts );
				$languageKey = array_pop( $titleParts );
				$contentType = $this->getContentType( $timedTextExtension );
			} else {
				continue;
			}
			// If there is no valid language continue:
			if ( !isset( $langNames[ $languageKey ] ) ) {
				continue;
			}

			$textTracks[] = [
				// @todo Should eventually add special entry point and output proper WebVTT format:
				// http://www.whatwg.org/specs/web-apps/current-work/webvtt.html
				'src' => $this->getFullURL( $subTitle, $contentType ),
				'kind' => 'subtitles',
				'type' => $contentType,
				'title' => $this->getPrefixedDBkey( $subTitle ),
				'provider' => $providerName,
				'srclang' =>  $languageKey,
				'dir' => Language::factory( $languageKey )->getDir(),
				'label' => wfMessage( 'timedmedia-subtitle-language',
					$langNames[ $languageKey ],
					$languageKey )->text()
			];
		}
		return $textTracks;
	}

	/**
	 * Build an array of track information using an API result
	 * @param mixed $data JSON decoded result from a query API request
	 * @return array
	 */
	function getTextTracksFromData( $data ) {
		$textTracks = [];
		if ( $data !== null && $data['query'] && $data['query']['pages'] ) {
			foreach ( $data['query']['pages'] as $page ) {
				if ( $page['videoinfo'] ) {
					foreach ( $page['videoinfo'] as $info ) {
						if ( $info['timedtext'] ) {
							foreach ( $info['timedtext'] as $track ) {
								// Add validation ?
								$textTracks[] = $track;
							}
						}
					}
				}
			}
		}
		return $textTracks;
	}

	function getContentType( $timedTextExtension ) {
		if ( $timedTextExtension === 'srt' ) {
			return 'text/x-srt';
		} elseif ( $timedTextExtension === 'vtt' ) {
			return 'text/vtt';
		}
		return '';
	}

	function getForeignNamespaceName() {
		global $wgEnableLocalTimedText;
		if ( $this->remoteNs !== null ) {
			return $this->remoteNsName;
		}
		/* Else, we use the canonical namespace, since we can't look up the actual one */
		if ( $wgEnableLocalTimedText ) {
			return strtr( MWNamespace::getCanonicalName( NS_TIMEDTEXT ), ' ', '_' );
		} else {
			// Assume the default name if no local TimedText.
			return 'TimedText';
		}
	}

	/**
	 * Retrieve a namespace prefixed and underscored title
	 * @param Title|ForeignTitle $pageTitle
	 * @return string
	 */
	function getPrefixedDBkey( $pageTitle ) {
		if ( $pageTitle instanceof Title ) {
			return $pageTitle->getPrefixedDBkey();
		} elseif ( $pageTitle instanceof ForeignTitle ) {
			return $pageTitle->getFullText();
		}
		return null;
	}

	/**
	 * Retrieve a url to the raw subtitle file
	 * Only use for local and foreignDb requests
	 *
	 * @param Title|ForeignTitle $pageTitle
	 * @return string
	 */
	function getFullURL( $pageTitle, $contentType ) {
		if ( $pageTitle instanceof Title ) {
			return $pageTitle->getFullURL( [
				'action' => 'raw',
				'ctype' => $contentType
			] );
		} elseif ( $pageTitle instanceof ForeignTitle ) {
			$query = 'title=' . wfUrlencode( $pageTitle->getFullText() ) . '&';
			$query .= wfArrayToCgi( [
				'action' => 'raw',
				'ctype' => $contentType
			] );
			// Note: This will return false if scriptDirUrl is not set for repo.
			return $this->file->repo->makeUrl( $query );
		}
		return null;
	}
}