Cache   F
last analyzed

Complexity

Total Complexity 112

Size/Duplication

Total Lines 452
Duplicated Lines 13.27 %

Coupling/Cohesion

Components 2
Dependencies 0

Importance

Changes 0
Metric Value
dl 60
loc 452
rs 2
c 0
b 0
f 0
wmc 112
lcom 2
cbo 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 21 5
A fetch() 0 13 3
A _fetch() 10 28 4
A fetch_func() 10 26 4
A store() 0 29 4
A set_group() 0 12 3
A set_default_group() 0 3 1
A call() 16 27 4
D Clean() 0 38 20
A delete() 0 13 2
A delete_group() 0 7 4
A delete_func() 0 10 2
D delete_all() 13 43 24
B clearCacheFolder() 0 19 9
D cache_file() 11 28 19
A generatekey() 0 3 1
A log_cache_error() 0 23 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Cache often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Cache, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
if (!defined('BASEPATH')) {
4
    exit('No direct script access allowed');
5
}
6
7
/**
8
 * Image CMS
9
 * Cache Class
10
 */
11
class Cache
12
{
13
14
    public $CI;
15
16
    public $get = 0;
17
18
    public $set = 0;
19
20
    public $disableCache = 0;
21
22
    //Cache config
23
    // TODO: Rewrite auto_clean to fetch date from DB
24
    public $_Config = [
25
                       'store'           => 'cache',
26
                       'auto_clean'      => 500, //Random number to run _Clean();
27
                       'auto_clean_life' => 3600,
28
                       'auto_clean_all'  => TRUE,
29
                       'ttl'             => 3600,
30
                      ]; //one hour
31
32
    public function __construct() {
33
        $this->CI = & get_instance();
34
        if ($this->CI->config->item('cache_path') != '') {
35
            $this->_Config['default_store'] = $this->CI->config->item('cache_path');
36
            $this->_Config['store'] = $this->CI->config->item('cache_path');
37
        } else {
38
            $this->_Config['default_store'] = BASEPATH . 'cache/';
39
            $this->_Config['store'] = BASEPATH . 'cache/';
40
        }
41
        $this->disableCache = (boolean) $this->CI->config->item('disable_cache');
0 ignored issues
show
Documentation Bug introduced by
The property $disableCache was declared of type integer, but (bool) $this->CI->config->item('disable_cache') is of type boolean. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
42
43
        // Is cache folder wratible?
44
        if (!is_writable($this->_Config['store'])) {
45
            $this->log_cache_error('Constructor :: Store ' . $this->_Config['store'] . ' is not writable');
46
        }
47
48
        // autoclean if random is 1
49
        if (($this->_Config['auto_clean'] !== false) && (rand(1, $this->_Config['auto_clean']) === 1)) {
50
            $this->Clean();
51
        }
52
    }
53
54
    /**
55
     * Fetch Cached File
56
     *
57
     * @param string $key
58
     *
59
     * @return mixed
60
     */
61
    public function fetch($key, $group = FALSE) {
62
        if ($this->disableCache === true) {
63
            return false;
64
        }
65
66
        $this->set_group($group);
0 ignored issues
show
Documentation introduced by
$group is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
67
68
        if (($ret = $this->_fetch($key)) === false) {
69
            return false;
70
        } else {
71
            return $ret;
72
        }
73
    }
74
75
    /**
76
     * @param string $key
77
     */
78
    private function _fetch($key) {
79
        $file = $this->_Config['store'] . 'cache_' . $this->generatekey($key);
80
        $this->set_default_group();
81
82
        if (!file_exists($file)) {
83
            return FALSE;
84
        }
85
86 View Code Duplication
        if (!($fp = fopen($file, 'r'))) {
87
            $this->log_cache_error('Fetch :: Error Opening File ' . $file);
88
            return FALSE;
89
        }
90
91
        // Only reading
92
        flock($fp, LOCK_SH);
93
94
        // Cache data
95
        $data = unserialize(file_get_contents($file));
96
        fclose($fp);
97
98
        // if cache not expried return cache file
99 View Code Duplication
        if (time() < $data['expire']) {
100
            $this->get++;
101
            return $data['cache'];
102
        } else {
103
            return FALSE;
104
        }
105
    }
106
107
    /**
108
     * Fetch cached function
109
     */
110
    public function fetch_func($object, $func, $args = []) {
111
112
        $file = $this->_Config['store'] . 'cache_' . $this->generatekey(get_class($object) . '::' . $func . '::' . serialize($args));
113
        $this->set_default_group();
114
115
        if (!file_exists($file)) {
116
            return false;
117
        }
118
119 View Code Duplication
        if (!($fp = fopen($file, 'r'))) {
120
            $this->log_cache_error('Fetch :: Error Opening File ' . $file);
121
            return false;
122
        }
123
124
        flock($fp, LOCK_SH);
125
126
        $data = unserialize(file_get_contents($file));
127
        fclose($fp);
128
129 View Code Duplication
        if (time() < $data['expire']) {
130
            $this->get++;
131
            return $data['cache'];
132
        } else {
133
            return FALSE;
134
        }
135
    }
136
137
    /**
138
     * Store Cache Item
139
     *
140
     * @param string  $key
141
     * @param mixed   $data
142
     * @param int     $ttl
143
     *
144
     * @return bool
145
     */
146
    public function store($key, $data, $ttl = false, $group = false) {
147
        if (!$ttl) {
148
            $ttl = $this->_Config['ttl'];
149
        }
150
151
        $this->set_group($group);
0 ignored issues
show
Documentation introduced by
$group is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
152
153
        $file = $this->_Config['store'] . 'cache_' . $this->generatekey($key);
154
        $data = serialize(['expire' => ($ttl + time()), 'cache' => $data]);
155
156
        if (!($fp = fopen($file, 'a+'))) {
157
            $this->log_cache_error('Store :: Error Opening file ' . $file);
158
        }
159
160
        flock($fp, LOCK_EX);
161
        fseek($fp, 0);
162
        ftruncate($fp, 0);   // Clear file
163
164
        if (fwrite($fp, $data) === false) {
165
            $this->log_cache_error('Store :: Error writing to file ' . $file);
166
        }
167
168
        fclose($fp);
169
170
        $this->set_default_group();
171
        $this->set++;
172
173
        return true;
174
    }
175
176
    /**
177
     * Group Function
178
     *
179
     * @param string $group
180
     *
181
     * @access public
182
     */
183
    public function set_group($group) {
184
        if ($group == FALSE) {
185
            $this->_Config['store'] = $this->_Config['default_store'];
186
            return;
187
        }
188
189
        if (!is_dir($this->_Config['store'] . $group)) {
190
            mkdir($this->_Config['store'] . $group);
191
            @chmod($this->_Config['store'] . $group, 0777);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
192
        }
193
        $this->_Config['store'] .= $group . '/';
194
    }
195
196
    public function set_default_group() {
197
        $this->_Config['store'] = $this->_Config['default_store'];
198
    }
199
200
    /**
201
     * Cache Function
202
     *
203
     * @return mixed
204
     * @access public
205
     */
206
    public function call($func = [], $args = [], $ttl = false) {
0 ignored issues
show
Unused Code introduced by
The parameter $func is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
207
        if ($ttl == false) {
208
            $ttl = $this->_Config['ttl'];
0 ignored issues
show
Unused Code introduced by
$ttl is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
209
        }
210
211
        $arguments = func_get_args();
212
213
        //class_name::function
214
        $key = get_class($arguments[0][0]) . '::' . $arguments[0][1] . '::' . serialize($args);
215
216 View Code Duplication
        if (($cache = $this->fetch($key)) !== false) {
217
            $this->set_default_group();
218
            return $cache;
219
        } else {
220
221
            $target = array_shift($arguments);
222
            $result = call_user_func_array($target, $args);
223
224
            $this->set_default_group();
225
226
            if (!$this->store($key, $result, false)) {
227
                return false;
228
            }
229
230
            return $result;
231
        }
232
    }
233
234
    public function Clean() {
235
        if (!($dh = opendir($this->_Config['store']))) {
236
            $this->log_cache_error('Clean :: Error Opening Store ' . $this->_Config['store']);
237
            return false;
238
        }
239
240
        $this->log_cache_error('Clean :: Autoclean started');
241
242
        $n = 0;
243
244
        while ($file = readdir($dh)) {
245
            $stat = stat($this->_Config['store']);
246
            if (($file != '.') && ($file != '..') && ($file != 'index.html')) {
247
248
                if (substr($file, 0, 6) != 'cache_' && $file != 'hooks.php' && $file != '.' && $file != '..' && $file != '/' && strstr($file, '.') != TRUE && (time() - $stat['mtime']) > $this->_Config['auto_clean_life']) {
249
250
                    $files_all = opendir('./system/cache/' . $file);
251
                    while (false !== ($fileT = readdir($files_all))) {
252
                        $stat = stat($this->_Config['store']);
253
                        // echo $stat['mtime'];
254
                        if ($fileT != '.' && $fileT != '..' && $fileT != '/' && (time() - @$stat['mtime']) > $this->_Config['auto_clean_life']) {
255
                            @unlink('./system/cache/' . $file . '/' . $fileT);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
256
                            $n++;
257
                        }
258
                    }
259
                }
260
261
                if (strstr($file, '.') === TRUE && (time() - $stat['mtime']) > $this->_Config['auto_clean_life']) {
262
                    @unlink($file);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
263
                    $n++;
264
                }
265
            }
266
        }
267
268
        $this->log_cache_error('Clean :: Autoclean done');
269
270
        return $n;
271
    }
272
273
    /**
274
     * Delete Cache Item
275
     *
276
     * @param string $key
277
     *
278
     * @return bool
279
     */
280
    public function delete($key, $group = FALSE) {
281
        $this->set_group($group);
0 ignored issues
show
Documentation introduced by
$group is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
282
283
        $file = $this->_Config['store'] . 'cache_' . $this->generatekey($key);
284
285
        $this->set_default_group();
286
287
        if (file_exists($file)) {
288
            return @unlink($file);
289
        } else {
290
            return false;
291
        }
292
    }
293
294
    /**
295
     * Delete group folder
296
     *
297
     * @param string $group
298
     * @access public
299
     */
300
    public function delete_group($group) {
301
        if ($group != '.' AND $group != '..' AND $group != 'templates_c') {
302
            $file = BASEPATH . 'cache/' . $group;
303
            $this->CI->load->helper('file');
304
            delete_files($file);
305
        }
306
    }
307
308
    /**
309
     * Delete Cached Function
310
     *
311
     * @param $object
312
     * @param $func
313
     * @param array $args
314
     * @return bool
315
     */
316
    public function delete_func($object, $func, $args = []) {
317
        $file = $this->_Config['store'] . 'cache_' . $this->generatekey(get_class($object) . '::' . $func . '::' . serialize($args));
318
        $this->set_default_group();
319
320
        if (file_exists($file)) {
321
            return @unlink($file);
322
        } else {
323
            return false;
324
        }
325
    }
326
327
    /**
328
     * Delete All Cache Items
329
     *
330
     * @return integer
331
     * @access public
332
     */
333
    public function delete_all() {
334
        $n = 0;
335
336
        $cache_store_dir = $this->_Config['store'] . '/';
337
        if (is_dir($cache_store_dir) and ( $root_dir_handle = opendir($cache_store_dir))) {
338
            while (false !== ($file = readdir($root_dir_handle))) {
339
340 View Code Duplication
                if (substr($file, 0, 6) != 'cache_' && $file != 'hooks.php' && $file != '.' && $file != '..' && $file != '/') {
341
                    $cache_sub_dir = $cache_store_dir . $file . '/';
342
                    if (is_dir($cache_sub_dir) and ( $sub_dir_handle = opendir($cache_sub_dir))) {
343
                        while (FALSE !== ($fileT = readdir($sub_dir_handle))) {
344
345
                            if ($fileT != '.' && $fileT != '..' && $fileT != '/') {
346
347
                                $n++;
348
                                @unlink($cache_sub_dir . $fileT);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
349
                            }
350
                        }
351
                    }
352
                }
353
                if (substr($file, 0, 6) == 'cache_' || $file == 'hooks.php' || strstr($file, '.') === TRUE) {
354
355
                    $n++;
356
                    @unlink($cache_store_dir . $file);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
357
                }
358
            }
359
        }
360
361
        $cache_sub_dir = $cache_store_dir . 'templates_c/HTML/';
362
        if (is_dir($cache_sub_dir) and ( $sub_dir_handle = opendir($cache_sub_dir))) {
363
            while (false !== ($fileT = readdir($sub_dir_handle))) {
364
365
                if ($fileT != '.' && $fileT != '..' && $fileT != '/') {
366
367
                    @unlink($cache_sub_dir . $fileT);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
368
                }
369
            }
370
        }
371
372
        $this->log_cache_error('All cache files deleted');
373
374
        return $n;
375
    }
376
377
    public function clearCacheFolder($folder = NULL) {
378
379
        if ($folder !== NULL) {
380
            if ($files_all = opendir('./system/cache/' . $folder . '/')) {
381
                while (false !== ($fileT = readdir($files_all))) {
382
383
                    if ($fileT != '.' && $fileT != '..' && $fileT != '/' && substr($fileT, 0, 6) == 'cache_' || $fileT != 'hooks.php') {
384
                        @unlink('./system/cache/' . $folder . '/' . $fileT);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
385
                    }
386
                }
387
            } else {
388
                log_message('error', 'Library cache, Function clearCacheFolder , opendir Patch:' . './system/cache/' . $folder . '/' . ' RETURN FALSE');
389
                return false;
390
            }
391
        } else {
392
            log_message('error', 'Library cache, Function clearCacheFolder. Do not get the name of the directory to delete cache');
393
            return false;
394
        }
395
    }
396
397
    public function cache_file() {
398
        $n = 0;
399
400
        $cache_store_dir = $this->_Config['store'] . '/';
401
        if (is_dir($cache_store_dir) and ( $root_dir_handle = opendir($cache_store_dir))) {
402
            while (false !== ($file = readdir($root_dir_handle))) {
403
404 View Code Duplication
                if (substr($file, 0, 6) != 'cache_' && $file != 'hooks.php' && $file != '.' && $file != '..' && $file != '/') {
405
                    $cache_sub_dir = $cache_store_dir . $file . '/';
406
                    if (is_dir($cache_sub_dir) and ( $sub_dir_handle = opendir($cache_sub_dir))) {
407
                        while (false !== ($fileT = readdir($sub_dir_handle))) {
408
409
                            if ($fileT != '.' && $fileT != '..' && $fileT != '/' && strstr($fileT, '~') != TRUE) {
410
                                $n++;
411
                            }
412
                        }
413
                    }
414
                }
415
                if (substr($file, 0, 6) == 'cache_' || $file == 'hooks.php' && strstr($fileT, '~') != TRUE) {
416
                    $n++;
417
                }
418
            }
419
        }
420
421
        $this->log_cache_error('All cache files deleted');
422
423
        return $n;
424
    }
425
426
    /**
427
     * @param string $key
428
     * @return string
429
     */
430
    public function generatekey($key) {
431
        return md5($key);
432
    }
433
434
    /**
435
     * @param string $msg
436
     * @return bool
0 ignored issues
show
Documentation introduced by
Should the return type not be false|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
437
     */
438
    private function log_cache_error($msg) {
439
        $log_path = APPPATH . 'logs/';
440
441
        $filepath = $log_path . 'cache_log-' . date('Y-m-d') . EXT;
442
        $message = '';
443
444
        if (!file_exists($filepath)) {
445
            $message .= '<' . "?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed'); ?" . ">\n\n";
446
        }
447
448
        if (!$fp = @fopen($filepath, FOPEN_WRITE_CREATE)) {
449
            return FALSE;
450
        }
451
452
        $message .= date('Y-m-d H:i:s') . ' --> ' . $msg . "\n";
453
454
        flock($fp, LOCK_EX);
455
        fwrite($fp, $message);
456
        flock($fp, LOCK_UN);
457
        fclose($fp);
458
459
        @chmod($filepath, FILE_WRITE_MODE);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
460
    }
461
462
}
463
464
/* End of cache.php */