summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'MLEB/Translate/utils/MessageGroupCache.php')
-rw-r--r--MLEB/Translate/utils/MessageGroupCache.php162
1 files changed, 93 insertions, 69 deletions
diff --git a/MLEB/Translate/utils/MessageGroupCache.php b/MLEB/Translate/utils/MessageGroupCache.php
index f0b9c4bd..fdb1fc01 100644
--- a/MLEB/Translate/utils/MessageGroupCache.php
+++ b/MLEB/Translate/utils/MessageGroupCache.php
@@ -1,9 +1,7 @@
<?php
/**
- * Code for caching the messages of file based message groups.
* @file
* @author Niklas Laxström
- * @copyright Copyright © 2009-2013 Niklas Laxström
* @license GPL-2.0-or-later
*/
@@ -11,17 +9,18 @@
* Caches messages of file based message group source file. Can also track
* that the cache is up to date. Parsing the source files can be slow, so
* constructing CDB cache makes accessing that data constant speed regardless
- * of the actual format.
+ * of the actual format. This also avoid having to deal with potentially unsafe
+ * external files during web requests.
*
* @ingroup MessageGroups
*/
class MessageGroupCache {
- const NO_SOURCE = 1;
- const NO_CACHE = 2;
- const CHANGED = 3;
+ public const NO_SOURCE = 1;
+ public const NO_CACHE = 2;
+ public const CHANGED = 3;
/**
- * @var MessageGroup
+ * @var FileBasedMessageGroup
*/
protected $group;
@@ -36,17 +35,24 @@ class MessageGroupCache {
protected $code;
/**
+ * @var string
+ */
+ private $cacheFilePath;
+
+ /**
* Contructs a new cache object for given group and language code.
- * @param string|FileBasedMessageGroup $group Group object or id.
- * @param string $code Language code. Default value 'en'.
+ * @param FileBasedMessageGroup $group
+ * @param string $code Language code.
+ * @param string $cacheFilePath
*/
- public function __construct( $group, $code = 'en' ) {
- if ( is_object( $group ) ) {
- $this->group = $group;
- } else {
- $this->group = MessageGroups::getGroup( $group );
- }
+ public function __construct(
+ FileBasedMessageGroup $group,
+ string $code,
+ string $cacheFilePath
+ ) {
+ $this->group = $group;
$this->code = $code;
+ $this->cacheFilePath = $cacheFilePath;
}
/**
@@ -54,22 +60,7 @@ class MessageGroupCache {
* @return bool
*/
public function exists() {
- $old = $this->getOldCacheFileName();
- $new = $this->getCacheFileName();
- $exists = file_exists( $new );
-
- if ( $exists ) {
- return true;
- }
-
- // Perform migration if possible
- if ( file_exists( $old ) ) {
- wfMkdirParents( dirname( $new ) );
- rename( $old, $new );
- return true;
- }
-
- return false;
+ return file_exists( $this->getCacheFilePath() );
}
/**
@@ -77,10 +68,19 @@ class MessageGroupCache {
* @return string[] Message keys that can be passed one-by-one to get() method.
*/
public function getKeys() {
- $value = $this->open()->get( '#keys' );
- $array = unserialize( $value );
+ $reader = $this->open();
+ $keys = [];
- return $array;
+ $key = $reader->firstkey();
+ while ( $key !== false ) {
+ if ( ( $key[0] ?? '' ) !== '#' ) {
+ $keys[] = $key;
+ }
+
+ $key = $reader->nextkey();
+ }
+
+ return $keys;
}
/**
@@ -109,32 +109,53 @@ class MessageGroupCache {
}
/**
+ * Get a list of authors.
+ * @return string[]
+ * @since 2020.04
+ */
+ public function getAuthors(): array {
+ $cache = $this->open();
+ return $cache->exists( '#authors' ) ?
+ $this->unserialize( $cache->get( '#authors' ) ) : [];
+ }
+
+ /**
+ * Get other data cached from the FFS class.
+ * @return array
+ * @since 2020.04
+ */
+ public function getExtra(): array {
+ $cache = $this->open();
+ return $cache->exists( '#extra' ) ? $this->unserialize( $cache->get( '#extra' ) ) : [];
+ }
+
+ /**
* Populates the cache from current state of the source file.
* @param bool|string $created Unix timestamp when the cache is created (for automatic updates).
*/
public function create( $created = false ) {
$this->close(); // Close the reader instance just to be sure
- $messages = $this->group->load( $this->code );
+ $parseOutput = $this->group->parseExternal( $this->code );
+ $messages = $parseOutput['MESSAGES'];
if ( $messages === [] ) {
if ( $this->exists() ) {
// Delete stale cache files
- unlink( $this->getCacheFileName() );
+ unlink( $this->getCacheFilePath() );
}
return; // Don't create empty caches
}
$hash = md5( file_get_contents( $this->group->getSourceFilePath( $this->code ) ) );
- wfMkdirParents( dirname( $this->getCacheFileName() ) );
- $cache = \Cdb\Writer::open( $this->getCacheFileName() );
- $keys = array_keys( $messages );
- $cache->set( '#keys', serialize( $keys ) );
+ wfMkdirParents( dirname( $this->getCacheFilePath() ) );
+ $cache = \Cdb\Writer::open( $this->getCacheFilePath() );
foreach ( $messages as $key => $value ) {
$cache->set( $key, $value );
}
-
+ $cache->set( '#authors', $this->serialize( $parseOutput['AUTHORS'] ) );
+ $cache->set( '#extra', $this->serialize( $parseOutput['EXTRA'] ) );
$cache->set( '#created', $created ?: wfTimestamp() );
$cache->set( '#updated', wfTimestamp() );
$cache->set( '#filehash', $hash );
@@ -154,24 +175,27 @@ class MessageGroupCache {
*/
public function isValid( &$reason ) {
$group = $this->group;
- $groupId = $group->getId();
+ $uniqueId = $this->getCacheFilePath();
$pattern = $group->getSourceFilePath( '*' );
$filename = $group->getSourceFilePath( $this->code );
+ $parseOutput = null;
+
// If the file pattern is not dependent on the language, we will assume
// that all translations are stored in one file. This means we need to
// actually parse the file to know if a language is present.
if ( strpos( $pattern, '*' ) === false ) {
- $source = $group->getFFS()->read( $this->code ) !== false;
+ $parseOutput = $group->parseExternal( $this->code );
+ $source = $parseOutput['MESSAGES'] !== [];
} else {
- static $globCache = null;
- if ( !isset( $globCache[$groupId] ) ) {
- $globCache[$groupId] = array_flip( glob( $pattern, GLOB_NOESCAPE ) );
+ static $globCache = [];
+ if ( !isset( $globCache[$uniqueId] ) ) {
+ $globCache[$uniqueId] = array_flip( glob( $pattern, GLOB_NOESCAPE ) );
// Definition file might not match the above pattern
- $globCache[$groupId][$group->getSourceFilePath( 'en' )] = true;
+ $globCache[$uniqueId][$group->getSourceFilePath( 'en' )] = true;
}
- $source = isset( $globCache[$groupId][$filename] );
+ $source = isset( $globCache[$uniqueId][$filename] );
}
$cache = $this->exists();
@@ -204,7 +228,8 @@ class MessageGroupCache {
}
// Message count check
- $messages = $group->load( $this->code );
+ $parseOutput = $parseOutput ?? $group->parseExternal( $this->code );
+ $messages = $parseOutput['MESSAGES'];
// CDB converts numbers to strings
$count = (int)( $this->get( '#msgcount' ) );
if ( $count !== count( $messages ) ) {
@@ -228,17 +253,28 @@ class MessageGroupCache {
return false;
}
+ private function serialize( array $data ): string {
+ // Using simple prefix for easy future extension
+ return 'J' . json_encode( $data );
+ }
+
+ private function unserialize( string $serialized ): array {
+ $type = $serialized[0];
+
+ if ( $type !== 'J' ) {
+ throw new RuntimeException( 'Unknown serialization format' );
+ }
+
+ return json_decode( substr( $serialized, 1 ), true );
+ }
+
/**
* Open the cache for reading.
- * @return self
+ * @return \Cdb\Reader
*/
protected function open() {
if ( $this->cache === null ) {
- $this->cache = \Cdb\Reader::open( $this->getCacheFileName() );
- if ( $this->cache->get( '#version' ) !== '3' ) {
- $this->close();
- unlink( $this->getCacheFileName() );
- }
+ $this->cache = \Cdb\Reader::open( $this->getCacheFilePath() );
}
return $this->cache;
@@ -258,19 +294,7 @@ class MessageGroupCache {
* Returns full path to the cache file.
* @return string
*/
- protected function getCacheFileName() {
- $cacheFileName = "translate_groupcache-{$this->group->getId()}/{$this->code}.cdb";
-
- return TranslateUtils::cacheFile( $cacheFileName );
- }
-
- /**
- * Returns full path to the old cache file location.
- * @return string
- */
- protected function getOldCacheFileName() {
- $cacheFileName = "translate_groupcache-{$this->group->getId()}-{$this->code}.cdb";
-
- return TranslateUtils::cacheFile( $cacheFileName );
+ protected function getCacheFilePath(): string {
+ return $this->cacheFilePath;
}
}