Completed
Push — work-fleets ( 22b024...7f5906 )
by SuperNova.WS
06:37
created

classLocale::usage_stat_save()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 24
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 4
eloc 18
nc 4
nop 0
dl 0
loc 24
rs 8.6845
c 4
b 0
f 0
ccs 0
cts 22
cp 0
crap 20
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->_MODE != CACHER_NO_CACHE && !classSupernova::$config->locale_cache_disable) {
0 ignored issues
show
Documentation introduced by
The property _MODE does not exist on object<classCache>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
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(); // TODO for debug
0 ignored issues
show
Documentation introduced by
The property lng_stat_usage does not exist on object<classCache>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
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;
0 ignored issues
show
Documentation introduced by
The property lng_stat_usage does not exist on object<classCache>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
148
      classSupernova::$db->doSelect("SELECT 1 FROM `{{lng_usage_stat}}` LIMIT 1");
149
      foreach($this->stat_usage_new as &$value) {
150
        foreach($value as &$value2) {
151
          $value2 = '"' . db_escape($value2) . '"';
152
        }
153
        $value = '(' . implode(',', $value) .')';
154
      }
155
      classSupernova::$gc->db->doReplaceValuesDeprecated(
0 ignored issues
show
Bug introduced by
The method doReplaceValuesDeprecated 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',
157
        array(
158
          'lang_code',
159
          'string_id',
160
          'file',
161
          'line',
162
          'is_empty',
163
          'locale',
164
        ),
165
        $this->stat_usage_new
166
      );
167
    }
168
  }
169
  public function usage_stat_log(&$offset, &$value) {
170
    $trace = debug_backtrace();
171
    unset($trace[0]);
172
    unset($trace[1]['object']);
173
174
    $file = str_replace('\\', '/', substr($trace[1]['file'], strlen(SN_ROOT_PHYSICAL) - 1));
175
176
    $string_id = $this->active . ':' . $offset . ':' . $file . ':' . $trace[1]['line'];
177
    if(!isset($this->stat_usage[$string_id]) || $this->stat_usage[$string_id] != empty($value)) {
178
      $this->stat_usage[$string_id] = empty($value);
179
      $this->stat_usage_new[] = array(
180
        'lang_code' => $this->active,
181
        'string_id' => $offset,
182
        'file' => $file,
183
        'line' => $trace[1]['line'],
184
        'is_empty' => intval(empty($value)),
185
        'locale' => '' . $value,
186
      );
187
    }
188
  }
189
190
191
  protected function lng_try_filepath($path, $file_path_relative) {
192
    $file_path = SN_ROOT_PHYSICAL . ($path && file_exists(SN_ROOT_PHYSICAL . $path . $file_path_relative) ? $path : '') . $file_path_relative;
193
    return file_exists($file_path) ? $file_path : false;
194
  }
195
196
  protected function make_fallback($language = '') {
197
    global $user;
198
199
    $this->fallback = array();
200
    $language ? $this->fallback[$language] = $language : false; // Desired language
201
    $this->active ? $this->fallback[$this->active] = $this->active : false; // Active language
202
    // TODO - account_language
203
    !empty($user['lang']) ? $this->fallback[$user['lang']] = $user['lang'] : false; // Player language
204
    $this->fallback[DEFAULT_LANG] = DEFAULT_LANG; // Server default language
205
    $this->fallback['ru'] = 'ru'; // Russian
206
    $this->fallback['en'] = 'en'; // English
207
  }
208
209
  public function lng_include($filename, $path = '', $ext = '.mo.php') {
210
    global $language;
211
212
    classSupernova::log_file("locale.include: Loading data from domain '{$filename}'", 1);
213
214
    $cache_file_key = $this->cache_prefix_lang . '__' . $filename;
215
216
    // Подключен ли внешний кэш?
217
    if($this->cache) {
218
      // Загружен ли уже данный файл?
219
      $cache_file_status = $this->cache->__get($cache_file_key);
220
      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);
221
      if($cache_file_status) {
222
        // Если да - повторять загрузку нет смысла
223
        return null;
224
      }
225
    }
226
227
    // У нас нет внешнего кэша или в кэш не загружена данная локализация текущего файла
228
229
    $ext = $ext ? $ext : '.mo.php';
230
    $filename_ext = "{$filename}{$ext}";
231
232
    $this->make_fallback($language);
233
234
    $file_path = '';
235
    foreach($this->fallback as $lang_try) {
236
      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...
237
        continue;
238
      }
239
240
      if($file_path = $this->lng_try_filepath($path, "language/{$lang_try}/{$filename_ext}")) {
241
        break;
242
      }
243
244
      if($file_path = $this->lng_try_filepath($path, "language/{$filename}_{$lang_try}{$ext}")) {
245
        break;
246
      }
247
248
      $file_path = '';
249
    }
250
251
    if($file_path) {
252
      $a_lang_array = array();
253
      include($file_path);
254
255
      if(!empty($a_lang_array)) {
256
        $this->merge($a_lang_array);
257
258
        // Загрузка данных из файла в кэш
259
        if($this->cache) {
260
          classSupernova::log_file("Locale: loading '{$filename}' into cache");
261
          foreach($a_lang_array as $key => $value) {
262
            $value_cache_key = $this->cache_prefix_lang . $key;
263
            if($this->cache->__isset($value_cache_key)) {
264
              if(is_array($value)) {
265
                $alt_value = $this->cache->__get($value_cache_key);
266
                $value = array_replace_recursive($alt_value, $value);
267
                // 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...
268
              }
269
            }
270
            $this->cache->__set($this->cache_prefix_lang . $key, $value);
271
          }
272
        }
273
      }
274
275
      if($this->cache) {
276
        $this->cache->__set($cache_file_key, true);
277
      }
278
279
      unset($a_lang_array);
280
    }
281
282
    classSupernova::log_file("locale.include: Complete - EXIT", -1);
283
284
    return null;
285
  }
286
287
  public function lng_load_i18n($i18n) {
288
    if(!isset($i18n)) {
289
      return;
290
    }
291
292
    foreach($i18n as $i18n_data) {
293
      if(is_string($i18n_data)) {
294
        $this->lng_include($i18n_data);
295
      } elseif(is_array($i18n_data)) {
296
        $this->lng_include($i18n_data['file'], $i18n_data['path']);
297
      }
298
    }
299
300
    return null;
301
  }
302
303
  public function lng_switch($language_new) {
304
    global $language, $user;
305
306
    classSupernova::log_file("locale.switch: Request for switch to '{$language_new}'", 1);
307
308
    $language_new = str_replace(array('?', '&', 'lang='), '', $language_new);
309
    $language_new = $language_new ? $language_new : (!empty($user['lang']) ? $user['lang'] : DEFAULT_LANG);
310
311
    classSupernova::log_file("locale.switch: Trying to switch language to '{$language_new}'");
312
313
    if($language_new == $this->active) {
314
      classSupernova::log_file("locale.switch: New language '{$language_new}' is equal to current language '{$this->active}' - EXIT", -1);
315
      return false;
316
    }
317
318
    $this->active = $language = $language_new;
319
    $this->cache_prefix_lang = $this->cache_prefix . $this->active . '_';
320
321
    $this['LANG_INFO'] = $this->lng_get_info($this->active);
322
    $this->make_fallback($this->active);
323
324
    if($this->cache) {
325
      $cache_lang_init_status = $this->cache->__get($this->cache_prefix_lang . '__INIT');
326
      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);
327
      if($cache_lang_init_status) {
328
        return false;
329
      }
330
331
      // Чистим текущие локализации из кэша. Достаточно почистить только флаги инициализации языкового кэша и загрузки файлов - они начинаются с '__'
332
      classSupernova::log_file("locale.switch: Cache - invalidating data");
333
      $this->cache->unset_by_prefix($this->cache_prefix_lang . '__');
334
    }
335
336
    $this->lng_include('system');
337
//    $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...
338
    $this->lng_include('tech');
339
    $this->lng_include('payment');
340
    // Loading global language files
341
    $this->lng_load_i18n(classSupernova::$sn_mvc['i18n']['']);
342
343
    if($this->cache) {
344
      classSupernova::log_file("locale.switch: Cache - setting flag " . $this->cache_prefix_lang . '__INIT');
345
      $this->cache->__set($this->cache_prefix_lang . '__INIT', true);
346
    }
347
348
    classSupernova::log_file("locale.switch: Complete - EXIT");
349
350
    return true;
351
  }
352
353
354
  public function lng_get_info($entry) {
355
    $file_name = SN_ROOT_PHYSICAL . 'language/' . $entry . '/language.mo.php';
356
    $lang_info = array();
357
    if(file_exists($file_name)) {
358
      include($file_name);
359
    }
360
361
    return($lang_info);
362
  }
363
364
  public function lng_get_list() {
365
    if(empty($this->lang_list)) {
366
      $this->lang_list = array();
367
368
      $path = SN_ROOT_PHYSICAL . 'language/';
369
      $dir = dir($path);
370
      while(false !== ($entry = $dir->read())) {
371
        if(is_dir($path . $entry) && $entry[0] != '.') {
372
          $lang_info = $this->lng_get_info($entry);
373
          if($lang_info['LANG_NAME_ISO2'] == $entry) {
374
            $this->lang_list[$lang_info['LANG_NAME_ISO2']] = $lang_info;
375
          }
376
        }
377
      }
378
      $dir->close();
379
    }
380
381
    return $this->lang_list;
382
  }
383
}
384