Localization_ClientGenerator::writeFiles()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 3
eloc 14
nc 3
nop 0
dl 0
loc 24
rs 9.7998
c 3
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace AppLocalize;
6
7
use AppUtils\ConvertHelper\JSONConverter;
8
use AppUtils\FileHelper;
9
use AppUtils\FileHelper\FileInfo;
10
use AppUtils\FileHelper\FolderInfo;
11
use AppUtils\FileHelper_Exception;
12
13
class Localization_ClientGenerator
14
{
15
    public const ERROR_JS_FOLDER_NOT_FOUND = 39302;
16
    public const ERROR_TARGET_FOLDER_NOT_WRITABLE = 39303;
17
18
    protected Localization_Translator $translator;
19
    protected ?FolderInfo $targetFolder = null;
20
    protected ?FileInfo $cacheKeyFile = null;
21
    protected ?string $cacheKey = null;
22
23
    public function __construct()
24
    {
25
        $this->translator = Localization::getTranslator();
26
27
        Localization::onLocaleChanged(function () {
28
            $this->handleLocaleChanged();
29
        });
30
31
        Localization::onCacheKeyChanged(function () {
32
            $this->handleCacheKeyChanged();
33
        });
34
35
        Localization::onClientFolderChanged(function () {
36
            $this->handleFolderChanged();
37
        });
38
    }
39
40
    private function getTargetFolder() : ?FolderInfo
41
    {
42
        $folder = Localization::getClientFolder();
43
44
        if(!empty($folder)) {
45
            return FolderInfo::factory($folder);
46
        }
47
48
        return null;
49
    }
50
51
    private function getCacheKeyFile() : ?FileInfo
52
    {
53
        $folder = $this->getTargetFolder();
54
55
        if($folder !== null) {
56
            return FileInfo::factory($folder.'/cachekey.txt');
57
        }
58
59
        return null;
60
    }
61
62
    private function handleLocaleChanged() : void
63
    {
64
        self::log('EVENT | Locale changed | Resetting internal cache.');
65
66
        $this->handleCacheKeyChanged();
67
    }
68
69
    private function handleCacheKeyChanged() : void
70
    {
71
        self::log('EVENT | Cache Key changed | Resetting internal cache.');
72
73
        $this->cacheKey = null;
74
        self::$systemKey = null;
75
    }
76
77
    private function handleFolderChanged() : void
78
    {
79
        self::log('EVENT | Client folder changed | Resetting internal cache.');
80
81
        $this->targetFolder = null;
82
        $this->cacheKeyFile = null;
83
    }
84
85
    public static function setLoggingEnabled(bool $enabled) : void
86
    {
87
        self::$logging = $enabled;
88
    }
89
90
    private static bool $logging = false;
91
92
    private static function log(string $message, ...$args) : void
93
    {
94
        if(self::$logging === false) {
95
            return;
96
        }
97
98
        echo sprintf($message, ...$args).PHP_EOL;
99
    }
100
101
    public function getCacheKey() : ?string
102
    {
103
        if(isset($this->cacheKey)) {
104
            return $this->cacheKey;
105
        }
106
107
        $file = $this->getCacheKeyFile();
108
109
        if($file !== null && $file->exists()) {
110
            $this->cacheKey = $file->getContents();
111
        }
112
113
        return $this->cacheKey;
114
    }
115
116
    /**
117
     * @throws Localization_Exception
118
     * @throws FileHelper_Exception
119
     */
120
    public function writeFiles() : void
121
    {
122
        self::log('Write Files');
123
124
        if($this->getCacheKey() === self::getSystemKey()) {
125
            self::log('Write Files | SKIP | Still up to date.');
126
            return;
127
        }
128
129
        $targetFolder = $this->getTargetFolder();
130
131
        // no client libraries folder set: ignore.
132
        if($targetFolder === null) {
133
            self::log('Write Files | SKIP | No folder set.');
134
            return;
135
        }
136
137
        $targetFolder
138
            ->create()
139
            ->requireReadable(self::ERROR_TARGET_FOLDER_NOT_WRITABLE);
140
141
        $this->writeLocaleFiles();
142
        $this->writeLibraryFiles();
143
        $this->writeCacheKey();
144
    }
145
    
146
   /**
147
    * Retrieves a list of all localization client 
148
    * files that are written to disk. This includes
149
    * the locale files and the libraries required
150
    * to make it work clientside.
151
    * 
152
    * @return string[]
153
    */
154
    public function getFilesList() : array
155
    {
156
        $files = array();
157
        
158
        foreach($this->libraries as $fileName)
159
        {
160
            $files[] = $this->getLibraryFilePath($fileName);
161
        }
162
        
163
        foreach($this->getTargetLocales() as $locale)
164
        {
165
            $files[] = $this->getLocaleFilePath($locale);
166
        }
167
        
168
        return $files;
169
    }
170
    
171
    protected function writeLocaleFiles() : void
172
    {
173
        self::log('Write Files | Writing locales.');
174
175
        foreach(self::getTargetLocales() as $locale)
176
        {
177
            $this->writeLocaleFile($locale);
178
        }
179
    }
180
181
    /**
182
     * @var string[]
183
     */
184
    protected array $libraries = array(
185
        'translator.js',
186
        'md5.min.js'
187
    );
188
189
    /**
190
     * @throws FileHelper_Exception
191
     * @throws Localization_Exception
192
     */
193
    protected function writeLibraryFiles() : void
194
    {
195
        $sourceFolder = FolderInfo::factory(__DIR__.'/../js');
196
197
        if(!$sourceFolder->exists())
198
        {
199
            throw new Localization_Exception(
200
                'Unexpected folder structure encountered.',
201
                sprintf(
202
                    'The [js] folder is not in the expected location at [%s].',
203
                    $sourceFolder
204
                ),
205
                self::ERROR_JS_FOLDER_NOT_FOUND
206
            );
207
        }
208
        
209
        foreach($this->libraries as $fileName)
210
        {
211
            $targetFile = $this->getLibraryFilePath($fileName);
212
            $sourceFile = $sourceFolder.'/'.$fileName;
213
            
214
            FileHelper::copyFile($sourceFile, $targetFile);
215
        }
216
    }
217
218
    /**
219
     * @return Localization_Locale[]
220
     */
221
    protected static function getTargetLocales() : array
222
    {
223
        $result = array();
224
225
        foreach(Localization::getAppLocales() as $locale) {
226
            if($locale->isNative()) {
227
                continue;
228
            }
229
230
            $result[] = $locale;
231
        }
232
233
        return $result;
234
    }
235
236
    protected static function getTargetLocaleIDs() : array
237
    {
238
        $result = array();
239
240
        foreach(self::getTargetLocales() as $locale) {
241
            $result[] = $locale->getName();
242
        }
243
244
        return $result;
245
    }
246
    
247
    protected function getLibraryFilePath(string $fileName) : string
248
    {
249
        return $this->getTargetFolder().'/'.$fileName;
250
    }
251
    
252
    protected function getLocaleFilePath(Localization_Locale $locale) : string
253
    {
254
        return sprintf(
255
            '%s/locale-%s.js',
256
            $this->getTargetFolder(),
257
            $locale->getLanguageCode()
258
        );
259
    }
260
    
261
    /**
262
     * Generates the JavaScript code to register all
263
     * clientside strings using the bundled client
264
     * libraries.
265
     *
266
     * The application has to decide how to serve this
267
     * content in its pages: There are two main ways to
268
     * do it:
269
     *
270
     * 1) Save it to a Javascript file and include that
271
     * 2) Serve it as application/javascript content via PHP
272
     *
273
     * NOTE: Caching has to be handled on the application
274
     * side. This method creates a fresh collection each time.
275
     */
276
    protected function writeLocaleFile(Localization_Locale $locale) : void
277
    {
278
        self::log('Write Files | Writing locale [%s].', $locale->getName());
279
280
        $path = $this->getLocaleFilePath($locale);
281
        $strings = $this->translator->getClientStrings($locale);
282
        
283
        $tokens = array();
284
        foreach($strings as $hash => $text)
285
        {
286
            if(empty($text)) {
287
                continue;
288
            }
289
            
290
            $tokens[] = sprintf(
291
                "a('%s',%s)",
292
                $hash,
293
                JSONConverter::var2json($text)
294
            );
295
        }
296
        
297
        if(empty($tokens))
298
        {
299
            $content = '/* No strings found. */';
300
        }
301
        else
302
        {
303
            $content =
304
            '/**'.PHP_EOL.
305
            ' * @generated '.date('Y-m-d H:i:s').PHP_EOL.
306
            ' */'.PHP_EOL.
307
            'AppLocalize_Translator.'.implode('.', $tokens).';';
308
        }
309
        
310
        FileHelper::saveFile($path, $content);
311
    }
312
313
    private static ?string $systemKey = null;
314
315
    public static function getSystemKey() : string
316
    {
317
        if(!isset(self::$systemKey)) {
318
            self::$systemKey = sprintf(
319
                'Lib:%s|System:%s|Locales:%s',
320
                Localization::getClientLibrariesCacheKey(),
321
                Localization::getVersion(),
322
                implode(',', self::getTargetLocaleIDs())
323
            );
324
325
            self::log('System Key generated: [%s].', self::$systemKey);
326
        }
327
328
        return self::$systemKey;
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::systemKey could return the type null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
329
    }
330
331
   /**
332
    * Generates the cache key file, which is used to determine
333
    * automatically whether the client libraries need to be 
334
    * refreshed.
335
    */
336
    protected function writeCacheKey() : void
337
    {
338
        $this->cacheKey = self::getSystemKey();
339
340
        $file = $this->getCacheKeyFile();
341
342
        if($file !== null) {
343
            $file->putContents($this->cacheKey);
344
        }
345
346
        self::log('Write Files | Cache key written.');
347
    }
348
349
    /**
350
     * Whether the localization files have been written to
351
     * disk this session.
352
     *
353
     * @return bool
354
     */
355
    public function areFilesWritten() : bool
356
    {
357
        return $this->getCacheKey() === self::getSystemKey();
358
    }
359
}
360