Completed
Push — work-fleets ( 006942...3604bd )
by SuperNova.WS
06:54
created

classLocale::usage_stat_save()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 22
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 7
Bugs 0 Features 0
Metric Value
cc 2
eloc 13
c 7
b 0
f 0
nc 2
nop 0
dl 0
loc 22
rs 9.2
ccs 0
cts 16
cp 0
crap 6
1
<?php
2
3
class classLocale implements ArrayAccess {
4
  public $container = array();
5
  public $lang_list = null;
6
  public $active = null;
7
8
  public $enable_stat_usage = false;
9
  protected $stat_usage = array();
10
  protected $stat_usage_new = array();
11
12
  /**
13
   * Порядок проверки языков
14
   *
15
   * @var array $fallback
16
   */
17
  protected $fallback = array();
18
19
  /**
20
   * @var classCache $cache
21
   */
22
  protected $cache = null;
23
  protected $cache_prefix = 'lng_';
24
  protected $cache_prefix_lang = '';
25
26
  /**
27
   * ex $lang
28
   *
29
   * @var classLocale $lang
30
   */
31
  public static $lang = null;
32
33
  public function __construct($enable_stat_usage = false) {
34
    classSupernova::log_file('locale.__constructor: Starting', 1);
35
36
    $this->container = array();
37
38
    if(classSupernova::$cache->getMode() != CACHER_NO_CACHE && !classSupernova::$config->locale_cache_disable) {
39
      $this->cache = classSupernova::$cache;
40
      classSupernova::log_file('locale.__constructor: Cache is present');
41
//$this->cache->unset_by_prefix($this->cache_prefix); // TODO - remove? 'cause debug!
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
42
    }
43
44
    if($enable_stat_usage && empty($this->stat_usage)) {
45
      $this->enable_stat_usage = $enable_stat_usage;
46
      $this->usage_stat_load();
47
      // TODO shutdown function
48
      register_shutdown_function(array($this, 'usage_stat_save'));
49
    }
50
51
    classSupernova::log_file("locale.__constructor: Switching language to default");
52
    $this->lng_switch(DEFAULT_LANG);
53
54
    classSupernova::log_file("locale.__constructor: Complete - EXIT", -1);
55
  }
56
57
  /**
58
   * Фоллбэк для строки на другие локали
59
   *
60
   * @param array|string $offset
61
   */
62
  protected function locale_string_fallback($offset) {
63
global $locale_cache_statistic;
64
    // Фоллбэк вызывается только если мы не нашли нужную строку в массиве...
65
    $fallback = $this->fallback;
66
    // ...поэтому $offset в активном языке заведомо нет
67
    unset($fallback[$this->active]);
68
69
    // Проходим по оставшимся локалям
70
    foreach($fallback as $try_language) {
71
      // Если нет такой строки - пытаемся вытащить из кэша
72
      if(!isset($this->container[$try_language][$offset]) && $this->cache) {
73
        $this->container[$try_language][$offset] = $this->cache->__get($this->cache_prefix . $try_language . '_' . $offset);
74
// Записываем результат работы кэша
75
$locale_cache_statistic['queries']++;
76
isset($this->container[$try_language][$offset]) ? $locale_cache_statistic['hits']++ : $locale_cache_statistic['misses']++;
77
!isset($this->container[$try_language][$offset]) ? $locale_cache_statistic['missed_str'][] = $this->cache_prefix . $try_language . '_' . $offset : false;
78
      }
79
80
      // Если мы как-то где-то нашли строку...
81
      if(isset($this->container[$try_language][$offset])) {
82
        // ...значит она получена в результате фоллбэка и записываем её в кэш и контейнер
83
        $this[$offset] = $this->container[$try_language][$offset];
84
        $locale_cache_statistic['fallbacks']++;
85
        break;
86
      }
87
    }
88
  }
89
90
  public function offsetSet($offset, $value) {
91
//pdump('set', $this->cache_prefix_lang . $offset);
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
92
//pdump($this->container[$this->active][$offset]);
93
    if (is_null($offset)) {
94
      $this->container[$this->active][] = $value;
95 View Code Duplication
    } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
96
      $this->container[$this->active][$offset] = $value;
97
      if($this->cache) {
98
        $this->cache->__set($this->cache_prefix_lang . $offset, $value);
99
      }
100
    }
101
  }
102
  public function offsetExists($offset) {
103
    // Шорткат если у нас уже есть строка в памяти PHP
104
    if(!isset($this->container[$this->active][$offset])) {
105
//        pdump($this->cache_prefix_lang . $offset);
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
106 View Code Duplication
      if(!$this->cache || !($this->container[$this->active][$offset] = $this->cache->__get($this->cache_prefix_lang . $offset))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
107
//        pdump($this->cache_prefix_lang . $offset);
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
108
        // Если нету такой строки - делаем фоллбэк
109
        $this->locale_string_fallback($offset);
110
      }
111
//pdump($offset);
112
      return isset($this->container[$this->active][$offset]);
113
    } else {
114
      return true;
115
    }
116
  }
117
  public function offsetUnset($offset) {
118
    unset($this->container[$this->active][$offset]);
119
  }
120
  public function offsetGet($offset) {
121
    $value = $this->offsetExists($offset) ? $this->container[$this->active][$offset] : null;
122
    if($this->enable_stat_usage) {
123
      $this->usage_stat_log($offset, $value);
124
    }
125
    return $value;
126
  }
127
128
129
  public function merge($array) {
130
    $this->container[$this->active] = is_array($this->container[$this->active]) ? $this->container[$this->active] : array();
131
    // $this->container[$this->active] = array_merge($this->container[$this->active], $array);
0 ignored issues
show
Unused Code Comprehensibility introduced by
63% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
132
    $this->container[$this->active] = array_replace_recursive($this->container[$this->active], $array);
133
  }
134
135
136
  public function usage_stat_load() {
137
    $this->stat_usage = classSupernova::$cache->lng_stat_usage  = array();
138
    if(empty($this->stat_usage)) {
139
      $query = classSupernova::$db->doSelect("SELECT * FROM `{{lng_usage_stat}}`");
140
      while($row = db_fetch($query)) {
141
        $this->stat_usage[$row['lang_code'] . ':' . $row['string_id'] . ':' . $row['file'] . ':' . $row['line']] = $row['is_empty'];
142
      }
143
    }
144
  }
145
  public function usage_stat_save() {
146
    if(!empty($this->stat_usage_new)) {
147
      classSupernova::$cache->lng_stat_usage = $this->stat_usage;
148
      classSupernova::$db->doSelect("SELECT 1 FROM `{{lng_usage_stat}}` LIMIT 1");
149
//      foreach($this->stat_usage_new as &$value) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
48% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
150
//        foreach($value as &$value2) {
151
//          $value2 = '"' . db_escape($value2) . '"';
152
//        }
153
//        $value = '(' . implode(',', $value) .')';
154
//      }
155
      classSupernova::$gc->db->doInsertBatch(
0 ignored issues
show
Bug introduced by
The method doInsertBatch does only exist in db_mysql, but not in Closure.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
156
        'lng_usage_stat', $this->stat_usage_new, array(
157
        'lang_code',
158
        'string_id',
159
        'file',
160
        'line',
161
        'is_empty',
162
        'locale',
163
      ), DB_INSERT_REPLACE
164
      );
165
    }
166
  }
167
  public function usage_stat_log(&$offset, &$value) {
168
    $trace = debug_backtrace();
169
    unset($trace[0]);
170
    unset($trace[1]['object']);
171
172
    $file = str_replace('\\', '/', substr($trace[1]['file'], strlen(SN_ROOT_PHYSICAL) - 1));
173
174
    $string_id = $this->active . ':' . $offset . ':' . $file . ':' . $trace[1]['line'];
175
    if(!isset($this->stat_usage[$string_id]) || $this->stat_usage[$string_id] != empty($value)) {
176
      $this->stat_usage[$string_id] = empty($value);
177
      $this->stat_usage_new[] = array(
178
        'lang_code' => $this->active,
179
        'string_id' => $offset,
180
        'file' => $file,
181
        'line' => $trace[1]['line'],
182
        'is_empty' => intval(empty($value)),
183
        'locale' => '' . $value,
184
      );
185
    }
186
  }
187
188
189
  protected function lng_try_filepath($path, $file_path_relative) {
190
    $file_path = SN_ROOT_PHYSICAL . ($path && file_exists(SN_ROOT_PHYSICAL . $path . $file_path_relative) ? $path : '') . $file_path_relative;
191
    return file_exists($file_path) ? $file_path : false;
192
  }
193
194
  protected function make_fallback($language = '') {
195
    global $user;
196
197
    $this->fallback = array();
198
    $language ? $this->fallback[$language] = $language : false; // Desired language
199
    $this->active ? $this->fallback[$this->active] = $this->active : false; // Active language
200
    // TODO - account_language
201
    !empty($user['lang']) ? $this->fallback[$user['lang']] = $user['lang'] : false; // Player language
202
    $this->fallback[DEFAULT_LANG] = DEFAULT_LANG; // Server default language
203
    $this->fallback['ru'] = 'ru'; // Russian
204
    $this->fallback['en'] = 'en'; // English
205
  }
206
207
  public function lng_include($filename, $path = '', $ext = '.mo.php') {
208
    global $language;
209
210
    classSupernova::log_file("locale.include: Loading data from domain '{$filename}'", 1);
211
212
    $cache_file_key = $this->cache_prefix_lang . '__' . $filename;
213
214
    // Подключен ли внешний кэш?
215
    if($this->cache) {
216
      // Загружен ли уже данный файл?
217
      $cache_file_status = $this->cache->__get($cache_file_key);
218
      classSupernova::log_file("locale.include: Cache - '{$filename}' has key '{$cache_file_key}' and is " . ($cache_file_status ? 'already loaded - EXIT' : 'EMPTY'), $cache_file_status ? -1 : 0);
219
      if($cache_file_status) {
220
        // Если да - повторять загрузку нет смысла
221
        return null;
222
      }
223
    }
224
225
    // У нас нет внешнего кэша или в кэш не загружена данная локализация текущего файла
226
227
    $ext = $ext ? $ext : '.mo.php';
228
    $filename_ext = "{$filename}{$ext}";
229
230
    $this->make_fallback($language);
231
232
    $file_path = '';
233
    foreach($this->fallback as $lang_try) {
234
      if(!$lang_try /* || isset($language_tried[$lang_try]) */) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
80% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
235
        continue;
236
      }
237
238
      if($file_path = $this->lng_try_filepath($path, "language/{$lang_try}/{$filename_ext}")) {
239
        break;
240
      }
241
242
      if($file_path = $this->lng_try_filepath($path, "language/{$filename}_{$lang_try}{$ext}")) {
243
        break;
244
      }
245
246
      $file_path = '';
247
    }
248
249
    if($file_path) {
250
      $a_lang_array = array();
251
      include($file_path);
252
253
      if(!empty($a_lang_array)) {
254
        $this->merge($a_lang_array);
255
256
        // Загрузка данных из файла в кэш
257
        if($this->cache) {
258
          classSupernova::log_file("Locale: loading '{$filename}' into cache");
259
          foreach($a_lang_array as $key => $value) {
260
            $value_cache_key = $this->cache_prefix_lang . $key;
261
            if($this->cache->__isset($value_cache_key)) {
262
              if(is_array($value)) {
263
                $alt_value = $this->cache->__get($value_cache_key);
264
                $value = array_replace_recursive($alt_value, $value);
265
                // pdump($alt_value, $alt_value);
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
266
              }
267
            }
268
            $this->cache->__set($this->cache_prefix_lang . $key, $value);
269
          }
270
        }
271
      }
272
273
      if($this->cache) {
274
        $this->cache->__set($cache_file_key, true);
275
      }
276
277
      unset($a_lang_array);
278
    }
279
280
    classSupernova::log_file("locale.include: Complete - EXIT", -1);
281
282
    return null;
283
  }
284
285
  public function lng_load_i18n($i18n) {
286
    if(!isset($i18n)) {
287
      return;
288
    }
289
290
    foreach($i18n as $i18n_data) {
291
      if(is_string($i18n_data)) {
292
        $this->lng_include($i18n_data);
293
      } elseif(is_array($i18n_data)) {
294
        $this->lng_include($i18n_data['file'], $i18n_data['path']);
295
      }
296
    }
297
298
    return null;
299
  }
300
301
  public function lng_switch($language_new) {
302
    global $language, $user;
303
304
    classSupernova::log_file("locale.switch: Request for switch to '{$language_new}'", 1);
305
306
    $language_new = str_replace(array('?', '&', 'lang='), '', $language_new);
307
    $language_new = $language_new ? $language_new : (!empty($user['lang']) ? $user['lang'] : DEFAULT_LANG);
308
309
    classSupernova::log_file("locale.switch: Trying to switch language to '{$language_new}'");
310
311
    if($language_new == $this->active) {
312
      classSupernova::log_file("locale.switch: New language '{$language_new}' is equal to current language '{$this->active}' - EXIT", -1);
313
      return false;
314
    }
315
316
    $this->active = $language = $language_new;
317
    $this->cache_prefix_lang = $this->cache_prefix . $this->active . '_';
318
319
    $this['LANG_INFO'] = $this->lng_get_info($this->active);
320
    $this->make_fallback($this->active);
321
322
    if($this->cache) {
323
      $cache_lang_init_status = $this->cache->__get($this->cache_prefix_lang . '__INIT');
324
      classSupernova::log_file("locale.switch: Cache for '{$this->active}' prefixed '{$this->cache_prefix_lang}' is " . ($cache_lang_init_status ? 'already loaded. Doing nothing - EXIT' : 'EMPTY'), $cache_lang_init_status ? -1 : 0);
325
      if($cache_lang_init_status) {
326
        return false;
327
      }
328
329
      // Чистим текущие локализации из кэша. Достаточно почистить только флаги инициализации языкового кэша и загрузки файлов - они начинаются с '__'
330
      classSupernova::log_file("locale.switch: Cache - invalidating data");
331
      $this->cache->unset_by_prefix($this->cache_prefix_lang . '__');
332
    }
333
334
    $this->lng_include('system');
335
//    $this->lng_include('menu');
0 ignored issues
show
Unused Code Comprehensibility introduced by
75% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
336
    $this->lng_include('tech');
337
    $this->lng_include('payment');
338
    // Loading global language files
339
    $this->lng_load_i18n(classSupernova::$sn_mvc['i18n']['']);
340
341
    if($this->cache) {
342
      classSupernova::log_file("locale.switch: Cache - setting flag " . $this->cache_prefix_lang . '__INIT');
343
      $this->cache->__set($this->cache_prefix_lang . '__INIT', true);
344
    }
345
346
    classSupernova::log_file("locale.switch: Complete - EXIT");
347
348
    return true;
349
  }
350
351
352
  public function lng_get_info($entry) {
353
    $file_name = SN_ROOT_PHYSICAL . 'language/' . $entry . '/language.mo.php';
354
    $lang_info = array();
355
    if(file_exists($file_name)) {
356
      include($file_name);
357
    }
358
359
    return($lang_info);
360
  }
361
362
  public function lng_get_list() {
363
    if(empty($this->lang_list)) {
364
      $this->lang_list = array();
365
366
      $path = SN_ROOT_PHYSICAL . 'language/';
367
      $dir = dir($path);
368
      while(false !== ($entry = $dir->read())) {
369
        if(is_dir($path . $entry) && $entry[0] != '.') {
370
          $lang_info = $this->lng_get_info($entry);
371
          if($lang_info['LANG_NAME_ISO2'] == $entry) {
372
            $this->lang_list[$lang_info['LANG_NAME_ISO2']] = $lang_info;
373
          }
374
        }
375
      }
376
      $dir->close();
377
    }
378
379
    return $this->lang_list;
380
  }
381
}
382