]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - includes/cache/MessageBlobStore.php
MediaWiki 1.30.2-scripts2
[autoinstalls/mediawiki.git] / includes / cache / MessageBlobStore.php
1 <?php
2 /**
3  * Message blobs storage used by ResourceLoader.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  * http://www.gnu.org/copyleft/gpl.html
19  *
20  * @file
21  * @author Roan Kattouw
22  * @author Trevor Parscal
23  * @author Timo Tijhof
24  */
25
26 use Psr\Log\LoggerAwareInterface;
27 use Psr\Log\LoggerInterface;
28 use Psr\Log\NullLogger;
29 use Wikimedia\Rdbms\Database;
30
31 /**
32  * This class generates message blobs for use by ResourceLoader modules.
33  *
34  * A message blob is a JSON object containing the interface messages for a certain module in
35  * a certain language.
36  */
37 class MessageBlobStore implements LoggerAwareInterface {
38
39         /* @var ResourceLoader|null */
40         private $resourceloader;
41
42         /**
43          * @var LoggerInterface
44          */
45         protected $logger;
46
47         /**
48          * @var WANObjectCache
49          */
50         protected $wanCache;
51
52         /**
53          * @param ResourceLoader $rl
54          * @param LoggerInterface $logger
55          */
56         public function __construct( ResourceLoader $rl = null, LoggerInterface $logger = null ) {
57                 $this->resourceloader = $rl;
58                 $this->logger = $logger ?: new NullLogger();
59                 $this->wanCache = ObjectCache::getMainWANInstance();
60         }
61
62         /**
63          * @since 1.27
64          * @param LoggerInterface $logger
65          */
66         public function setLogger( LoggerInterface $logger ) {
67                 $this->logger = $logger;
68         }
69
70         /**
71          * Get the message blob for a module
72          *
73          * @since 1.27
74          * @param ResourceLoaderModule $module
75          * @param string $lang Language code
76          * @return string JSON
77          */
78         public function getBlob( ResourceLoaderModule $module, $lang ) {
79                 $blobs = $this->getBlobs( [ $module->getName() => $module ], $lang );
80                 return $blobs[$module->getName()];
81         }
82
83         /**
84          * Get the message blobs for a set of modules
85          *
86          * @since 1.27
87          * @param ResourceLoaderModule[] $modules Array of module objects keyed by name
88          * @param string $lang Language code
89          * @return array An array mapping module names to message blobs
90          */
91         public function getBlobs( array $modules, $lang ) {
92                 // Each cache key for a message blob by module name and language code also has a generic
93                 // check key without language code. This is used to invalidate any and all language subkeys
94                 // that exist for a module from the updateMessage() method.
95                 $cache = $this->wanCache;
96                 $checkKeys = [
97                         // Global check key, see clear()
98                         $cache->makeKey( __CLASS__ )
99                 ];
100                 $cacheKeys = [];
101                 foreach ( $modules as $name => $module ) {
102                         $cacheKey = $this->makeCacheKey( $module, $lang );
103                         $cacheKeys[$name] = $cacheKey;
104                         // Per-module check key, see updateMessage()
105                         $checkKeys[$cacheKey][] = $cache->makeKey( __CLASS__, $name );
106                 }
107                 $curTTLs = [];
108                 $result = $cache->getMulti( array_values( $cacheKeys ), $curTTLs, $checkKeys );
109
110                 $blobs = [];
111                 foreach ( $modules as $name => $module ) {
112                         $key = $cacheKeys[$name];
113                         if ( !isset( $result[$key] ) || $curTTLs[$key] === null || $curTTLs[$key] < 0 ) {
114                                 $blobs[$name] = $this->recacheMessageBlob( $key, $module, $lang );
115                         } else {
116                                 // Use unexpired cache
117                                 $blobs[$name] = $result[$key];
118                         }
119                 }
120                 return $blobs;
121         }
122
123         /**
124          * @deprecated since 1.27 Use getBlobs() instead
125          * @return array
126          */
127         public function get( ResourceLoader $resourceLoader, $modules, $lang ) {
128                 return $this->getBlobs( $modules, $lang );
129         }
130
131         /**
132          * @deprecated since 1.27 Obsolete. Used to populate a cache table in the database.
133          * @return bool
134          */
135         public function insertMessageBlob( $name, ResourceLoaderModule $module, $lang ) {
136                 return false;
137         }
138
139         /**
140          * @since 1.27
141          * @param ResourceLoaderModule $module
142          * @param string $lang
143          * @return string Cache key
144          */
145         private function makeCacheKey( ResourceLoaderModule $module, $lang ) {
146                 $messages = array_values( array_unique( $module->getMessages() ) );
147                 sort( $messages );
148                 return $this->wanCache->makeKey( __CLASS__, $module->getName(), $lang,
149                         md5( json_encode( $messages ) )
150                 );
151         }
152
153         /**
154          * @since 1.27
155          * @param string $cacheKey
156          * @param ResourceLoaderModule $module
157          * @param string $lang
158          * @return string JSON blob
159          */
160         protected function recacheMessageBlob( $cacheKey, ResourceLoaderModule $module, $lang ) {
161                 $blob = $this->generateMessageBlob( $module, $lang );
162                 $cache = $this->wanCache;
163                 $cache->set( $cacheKey, $blob,
164                         // Add part of a day to TTL to avoid all modules expiring at once
165                         $cache::TTL_WEEK + mt_rand( 0, $cache::TTL_DAY ),
166                         Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) )
167                 );
168                 return $blob;
169         }
170
171         /**
172          * Invalidate cache keys for modules using this message key.
173          * Called by MessageCache when a message has changed.
174          *
175          * @param string $key Message key
176          */
177         public function updateMessage( $key ) {
178                 $moduleNames = $this->getResourceLoader()->getModulesByMessage( $key );
179                 foreach ( $moduleNames as $moduleName ) {
180                         // Uses a holdoff to account for database replica DB lag (for MessageCache)
181                         $this->wanCache->touchCheckKey( $this->wanCache->makeKey( __CLASS__, $moduleName ) );
182                 }
183         }
184
185         /**
186          * Invalidate cache keys for all known modules.
187          * Called by LocalisationCache after cache is regenerated.
188          */
189         public function clear() {
190                 $cache = $this->wanCache;
191                 // Disable holdoff because this invalidates all modules and also not needed since
192                 // LocalisationCache is stored outside the database and doesn't have lag.
193                 $cache->touchCheckKey( $cache->makeKey( __CLASS__ ), $cache::HOLDOFF_NONE );
194         }
195
196         /**
197          * @since 1.27
198          * @return ResourceLoader
199          */
200         protected function getResourceLoader() {
201                 // Back-compat: This class supports instantiation without a ResourceLoader object.
202                 // Lazy-initialise this property because most callers don't need it.
203                 if ( $this->resourceloader === null ) {
204                         $this->logger->warning( __CLASS__ . ' created without a ResourceLoader instance' );
205                         $this->resourceloader = new ResourceLoader();
206                 }
207                 return $this->resourceloader;
208         }
209
210         /**
211          * @since 1.27
212          * @param string $key Message key
213          * @param string $lang Language code
214          * @return string
215          */
216         protected function fetchMessage( $key, $lang ) {
217                 $message = wfMessage( $key )->inLanguage( $lang );
218                 $value = $message->plain();
219                 if ( !$message->exists() ) {
220                         $this->logger->warning( 'Failed to find {messageKey} ({lang})', [
221                                 'messageKey' => $key,
222                                 'lang' => $lang,
223                         ] );
224                 }
225                 return $value;
226         }
227
228         /**
229          * Generate the message blob for a given module in a given language.
230          *
231          * @param ResourceLoaderModule $module
232          * @param string $lang Language code
233          * @return string JSON blob
234          */
235         private function generateMessageBlob( ResourceLoaderModule $module, $lang ) {
236                 $messages = [];
237                 foreach ( $module->getMessages() as $key ) {
238                         $messages[$key] = $this->fetchMessage( $key, $lang );
239                 }
240
241                 $json = FormatJson::encode( (object)$messages );
242                 // @codeCoverageIgnoreStart
243                 if ( $json === false ) {
244                         $this->logger->warning( 'Failed to encode message blob for {module} ({lang})', [
245                                 'module' => $module->getName(),
246                                 'lang' => $lang,
247                         ] );
248                         $json = '{}';
249                 }
250                 // codeCoverageIgnoreEnd
251                 return $json;
252         }
253 }