Passed
Push — main ( 8d4724...4012f7 )
by Sammy
06:23
created

Lezer::detectLanguageFiles()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 3
nop 0
dl 0
loc 13
rs 10
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * i18n class called Lezer, shorthand L
5
 * honnors Ludwik *Lejzer* Zamenhof (Polish: Ludwik Łazarz Zamenhof;
6
 * 15 December [O.S. 3 December] 1859 – 14 April [O.S. 1 April] 1917),
7
 * a medical doctor, inventor, and writer; most widely known for creating Esperanto.
8
 *
9
 * also Lezer is dutch for Reader, and it sounds like LASER, which is kinda cool.
10
 */
11
namespace HexMakina\Lezer;
12
13
use \HexMakina\LocalFS\FileSystem;
14
use \HexMakina\Tempus\{Dato,DatoTempo,Tempo};
15
16
class Lezer extends \i18n
17
{
18
    private $detected_language_files = [];
19
    private $detected_language_env = [];
20
21
  // protected $basePath = 'locale';
22
  // protected $filePath = 'locale/{LANGUAGE}/user_interface.ini'; // uses gettext hierarchy
23
  // protected $cachePath = 'locale/cache/';
24
  // protected $fallbackLang = 'fra';  // uses ISO-639-3
25
    protected $currentLang = null;
26
27
    public function availableLanguage()
28
    {
29
        $the_one_language = current(array_intersect($this->detectLanguageFiles(), $this->detectLanguageEnv()));
30
31
        if ($the_one_language) {
32
            $this->setForcedLang($the_one_language);
33
        }
34
35
        return $the_one_language;
36
    }
37
38
    private function detectLanguageFiles()
39
    {
40
        $files = FileSystem::preg_scandir(dirname($this->filePath), '/.json$/');
41
        if (empty($files)) {
42
            return [];
43
        }
44
45
        $files = implode('', $files);
46
        $res = preg_match_all('/([a-z]{3})\.json/', $files, $m);
47
        if ($res) { // false or 0 is none found
48
            $this->detected_language_files = $m[1];
49
        }
50
        return $this->detected_language_files;
51
    }
52
53
  /**
54
   * getUserLangs()
55
   * Returns the user languages
56
   * Normally it returns an array like this:
57
   * 1. Forced language
58
   * 2. Language in $_GET['lang']
59
   * 3. Language in $_SESSION['lang']
60
   * 4. COOKIE
61
   * 5. Fallback language
62
   * Note: duplicate values are deleted.
63
   *
64
   * @return array with the user languages sorted by priority.
65
   */
66
    private function detectLanguageEnv()
67
    {
68
        $userLangs = array();
69
70
        // 1. forced language
71
        if ($this->forcedLang != null) {
72
            $userLangs['forced'] = $this->forcedLang;
73
        }
74
75
        // 2. GET parameter 'lang'
76
        if (isset($_GET['lang']) && is_string($_GET['lang'])) {
77
            $userLangs['get'] = $_GET['lang'];
78
        }
79
80
        // 3. SESSION parameter 'lang'
81
        if (isset($_SESSION['lang']) && is_string($_SESSION['lang'])) {
82
            $userLangs['session'] = $_SESSION['lang'];
83
        }
84
85
        // 4. COOKIES
86
        if (isset($_COOKIE['lang']) && is_string($_COOKIE['lang'])) {
87
            $userLangs['cookie'] = $_COOKIE['lang'];
88
        }
89
90
        // Lowest priority: fallback
91
        $userLangs['fallback'] = $this->fallbackLang;
92
        // remove duplicate elements
93
        $userLangs = array_unique($userLangs);
94
95
        // remove illegal userLangs
96
        foreach ($userLangs as $key => $value) {
97
            // only allow a-z, A-Z and 0-9 and _ and -
98
            if (preg_match('/^[a-zA-Z0-9_-]*$/', $value) === 1) {
99
                $this->detected_language_env[$key] = $value;
100
            }
101
        }
102
103
        return $this->detected_language_env;
104
    }
105
106
107
108
109
110
  // options['decimals'] = int
111
  // options['abbrev'] = mixed: key needs to be set
112
    public function when($event, $options = [])
113
    {
114
        try {
115
            $amount_of_days = DatoTempo::days_diff(new \DateTime($event), new \DateTime());
116
        } catch (\Exception $e) {
117
            return __FUNCTION__ . ': error';
118
        }
119
120
        if ($amount_of_days === -1) {
121
            return $this->l('DATETIME_RANGE_YESTERDAY');
122
        }
123
        if ($amount_of_days === 0) {
124
            return $this->l('DATETIME_RANGE_TODAY');
125
        }
126
        if ($amount_of_days === 1) {
127
            return $this->l('DATETIME_RANGE_TOMORROW');
128
        }
129
130
131
        $datetime_parts = [
132
        'y' => 'DATETIME_UNIT_YEAR',
133
        'm' => 'DATETIME_UNIT_MONTH',
134
        'w' => 'DATETIME_UNIT_WEEK',
135
        'd' => 'DATETIME_UNIT_DAY',
136
        'h' => 'DATETIME_UNIT_HOUR',
137
        'i' => 'DATETIME_UNIT_MINUTE',
138
        's' => 'DATETIME_UNIT_SECOND'
139
        ];
140
141
        $date_diff = DatoTempo::days_diff_in_parts(abs($amount_of_days));
142
        $ordering = [];
143
        foreach ($datetime_parts as $unit => $label) {
144
            if (!isset($date_diff[$unit])) {
145
                continue;
146
            }
147
148
            $qty = (int)$date_diff[$unit];
149
150
            if ($qty === 0) {
151
                continue;
152
            }
153
154
            if (isset($options['abbrev'])) {
155
                $label .= '_ABBREV';
156
            } elseif ($qty > 1) {
157
                $label .= '_PLURAL';
158
            }
159
160
            $ordering[$unit] = $qty . ' ' . $this->l($label) . '.';
161
        }
162
        $ret = 'DATETIME_RANGE_PREFIX_';
163
        $ret.= (isset($amount_of_days) && $amount_of_days >= 0) ? 'FUTURE' : 'PAST';
164
        $ret = $this->l($ret) . ' ' . implode(' & ', array_slice($ordering, 0, 2));
165
166
        return $ret;
167
    }
168
169
    public function time($time_string, $short = true)
170
    {
171
        if ($short === true) {
172
            $time_string = substr($time_string, 0, 5);
173
        }
174
        return $time_string;
175
    }
176
177
    public function date($date_string, $short = true)
178
    {
179
        if ($date_string === '0000-00-00' || empty($date_string)) {
180
            return $this->l('MODEL_common_VALUE_EMPTY');
181
        }
182
183
        if (preg_match('/^[0-9]{4}$/', $date_string) === 1) {
184
            return intval($date_string);
185
        }
186
187
        list($year, $month, $day) = explode('-', $date_string);
188
189
        $ret = intval($day) . ' ' . $this->l("DATETIME_CALENDAR_MONTH_$month");
190
191
        if ($short === true && Dato::format(null, 'Y') === $year) {
192
            return $ret;
193
        } else {
194
            return "$ret $year";
195
        }
196
    }
197
198
    public function month($date_string)
199
    {
200
        return $this->l('DATETIME_CALENDAR_MONTH_' . Dato::format($date_string, 'm'));
201
    }
202
203
    public function day($date_string)
204
    {
205
        return $this->l('DATETIME_CALENDAR_DAY_' . Dato::format($date_string, 'N'));
206
    }
207
208
    public function seconds($seconds)
209
    {
210
        $hours = floor($seconds / 3600);
211
        $mins = floor(($seconds - $hours * 3600) / 60);
212
        $secs = floor($seconds % 60);
213
214
        $hours_format = '%dh %dm %ds';
215
        return sprintf($hours_format, $hours, $mins, $secs);
216
    }
217
218
    public function init() {
219
        if ($this->isInitialized()) {
220
            throw new BadMethodCallException('This object from class ' . __CLASS__ . ' is already initialized. It is not possible to init one object twice!');
0 ignored issues
show
Bug introduced by
The type HexMakina\Lezer\BadMethodCallException was not found. Did you mean BadMethodCallException? If so, make sure to prefix the type with \.
Loading history...
221
        }
222
223
        $this->isInitialized = true;
224
225
        $this->userLangs = $this->getUserLangs();
226
227
        // search for language file
228
        $this->appliedLang = NULL;
229
        foreach ($this->userLangs as $priority => $langcode) {
230
            $this->langFilePath = $this->getConfigFilename($langcode);
231
            if (file_exists($this->langFilePath)) {
232
                $this->appliedLang = $langcode;
233
                break;
234
            }
235
        }
236
        if ($this->appliedLang == NULL) {
237
            throw new RuntimeException('No language file was found.');
0 ignored issues
show
Bug introduced by
The type HexMakina\Lezer\RuntimeException was not found. Did you mean RuntimeException? If so, make sure to prefix the type with \.
Loading history...
238
        }
239
240
        // search for cache file
241
        $this->cacheFilePath = $this->cachePath . '/php_i18n_' . md5_file(__FILE__) . '_' . $this->prefix . '_' . $this->appliedLang . '.cache.php';
242
243
        // whether we need to create a new cache file
244
        $outdated = !file_exists($this->cacheFilePath) ||
245
            filemtime($this->cacheFilePath) < filemtime($this->langFilePath) || // the language config was updated
246
            ($this->mergeFallback && filemtime($this->cacheFilePath) < filemtime($this->getConfigFilename($this->fallbackLang))); // the fallback language config was updated
247
248
        if ($outdated) {
249
            $config = $this->load($this->langFilePath);
250
            if ($this->mergeFallback)
251
                $config = array_replace_recursive($this->load($this->getConfigFilename($this->fallbackLang)), $config);
252
253
            $compiled = "<?php class " . $this->prefix . " {\n"
254
            	. $this->compile($config)
255
            	. 'public static function __callStatic($string, $args) {' . "\n"
256
            	. '    return vsprintf(constant("self::" . $string), $args);'
257
            	. "\n}\n}\n"
258
              . $this->compileFunction();
259
260
			if( ! is_dir($this->cachePath))
261
				mkdir($this->cachePath, 0755, true);
262
263
            if (file_put_contents($this->cacheFilePath, $compiled) === FALSE) {
264
                throw new Exception("Could not write cache file to path '" . $this->cacheFilePath . "'. Is it writable?");
0 ignored issues
show
Bug introduced by
The type HexMakina\Lezer\Exception was not found. Did you mean Exception? If so, make sure to prefix the type with \.
Loading history...
265
            }
266
            chmod($this->cacheFilePath, 0755);
267
268
        }
269
270
        require_once $this->cacheFilePath;
271
    }
272
273
    public function compileFunction()
274
    {
275
        return ''
276
        . "function " . $this->prefix . '($string, $args=NULL) {' . "\n"
277
        . '    if (!defined("' . $this->prefix . '::".$string))'
278
        . '       return $string;'
279
        . '    $return = constant("' . $this->prefix . '::".$string);' . "\n"
280
        . '    return $args ? vsprintf($return,$args) : $return;'
281
        . "\n}";
282
    }
283
284
    public function l($message, $context = []): string
285
    {
286
        foreach($context as $i => $context_message)
287
          $context[$i] = $this->l($context_message);
288
          
289
        return call_user_func($this->prefix, $message, $context);
290
    }
291
}
292