Completed
Push — master ( aaadf8...34625b )
by Arthur
18s queued 17s
created

CacheManager::checkCacheType()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 2
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace WebThumbnailer\Application;
4
5
use WebThumbnailer\Exception\CacheException;
6
use WebThumbnailer\Exception\IOException;
7
use WebThumbnailer\Utils\FileUtils;
8
use WebThumbnailer\WebThumbnailer;
9
10
/**
11
 * Class CacheManager
12
 *
13
 * Handles file caching using static methods.
14
 * There are 2 types of cache:
15
 *  - thumb: thumbnail images after being resized.
16
 *  - finder: url->thumbnail url resolution is also cached.
17
 * Cache files are organized by domains name, and have a unique name
18
 * based on their URL, max-width and max-height.
19
 *
20
 * Cache duration is defined in JSON settings.
21
 *
22
 * @package WebThumbnailer\Application
23
 */
24
class CacheManager
25
{
26
    /**
27
     * Thumbnails image cache.
28
     */
29
    const TYPE_THUMB  = 'thumb';
30
    /**
31
     * Finder cache.
32
     */
33
    const TYPE_FINDER = 'finder';
34
35
    const PATH_TYPE = [
36
        WebThumbnailer::PATH_RELATIVE => 0,
37
        WebThumbnailer::PATH_ABSOLUTE => 1,
38
    ];
39
40
    /**
41
     * @var string Clean filename, used to clean directories periodically.
42
     */
43
    protected static $CLEAN_FILE = '.clean';
44
45
    /**
46
     * Returns the cache path according to the given type.
47
     *
48
     * @param string $type    Type of cache.
49
     * @param bool   $rebuilt Flag to tell if a rebuild tentative has been done.
50
     *
51
     * @return string Cache absolute path.
52
     *
53
     * @throws IOException Type not found.
54
     */
55
    public static function getCachePath($type, $rebuilt = false)
56
    {
57
        self::checkCacheType($type);
58
        $cache = ConfigManager::get('settings.path.cache', 'cache/');
59
        $path = FileUtils::getPath($cache, $type);
60
        if (!$path && !$rebuilt) {
61
            self::rebuildCacheFolders();
62
            return self::getCachePath($type, true);
63
        } elseif (!$path) {
64
            throw new IOException('Cache folders are not writable: '. $cache);
65
        }
66
        return $path;
67
    }
68
69
    /**
70
     * Get a thumb cache file absolute path.
71
     *
72
     * @param string     $url    URL of the thumbnail (unique file per URL).
73
     * @param string     $domain Domain concerned.
74
     * @param string     $type   Type of cache.
75
     * @param int|string $width  User setting for image width.
76
     * @param int|string $height User setting for image height.
77
     * @param bool       $crop   Crop enabled or not.
78
     *
79
     * @param string     $pathType
80
     *
81
     * @return string Absolute file path.
82
     * @throws IOException
83
     */
84
    public static function getCacheFilePath(
85
        $url,
86
        $domain,
87
        $type,
88
        $width = 0,
89
        $height = 0,
90
        $crop = false,
91
        $pathType = WebThumbnailer::PATH_RELATIVE
92
    ) {
93
        $domainHash = self::getDomainHash($domain);
94
        self::createDomainThumbCacheFolder($domainHash, $type);
95
        $domainFolder = FileUtils::getPath(self::getCachePath($type), $domainHash);
96
        if ($type === self::TYPE_THUMB) {
97
            $suffix = $width . $height . ($crop ? '1' : '0') . (self::PATH_TYPE[$pathType]) .'.jpg';
98
        } else {
99
            $suffix = $width . $height;
100
        }
101
        return $domainFolder . self::getThumbFilename($url) . $suffix;
0 ignored issues
show
Bug introduced by
Are you sure $domainFolder of type false|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

101
        return /** @scrutinizer ignore-type */ $domainFolder . self::getThumbFilename($url) . $suffix;
Loading history...
102
    }
103
104
    /**
105
     * Check whether a valid cache file exists or not.
106
     * Also check that that file is still valid.
107
     *
108
     * Support endless cache using a negative value.
109
     *
110
     * @param string $cacheFile Cache file path.
111
     * @param string $domain Domain concerned.
112
     * @param string $type   Type of cache.
113
     *
114
     * @return bool true if valid cache exists, false otherwise.
115
     */
116
    public static function isCacheValid($cacheFile, $domain, $type)
117
    {
118
        $out = false;
119
        $cacheDuration = ConfigManager::get('settings.cache_duration', 3600*24*31);
120
121
        if (is_readable($cacheFile)
122
            && ($cacheDuration < 0 || (time() - filemtime($cacheFile)) < $cacheDuration)
123
        ) {
124
            $out = true;
125
        } else {
126
            self::createDomainThumbCacheFolder(self::getDomainHash($domain), $type);
127
        }
128
129
        return $out;
130
    }
131
132
    /**
133
     * Create the domains folder for thumb cache if it doesn't exists.
134
     *
135
     * @param string $domain Domain used.
136
     * @param string $type   Type of cache.
137
     */
138
    protected static function createDomainThumbCacheFolder($domain, $type)
139
    {
140
        $cachePath = self::getCachePath($type);
141
        $domainFolder = $cachePath . $domain;
142
        if (!file_exists($domainFolder)) {
143
            mkdir($domainFolder, 0775, false);
144
            touch($domainFolder . '/' . self::$CLEAN_FILE);
145
        }
146
        self::createHtaccessFile($cachePath, $type === self::TYPE_THUMB);
147
    }
148
149
    /**
150
     * Create a .htaccess file for Apache webserver if it doesn't exists.
151
     * The folder should be allowed for thumbs, and denied for finder's cache.
152
     *
153
     * @param string $path    Cache directory path
154
     * @param bool   $allowed Weather the access is allowed or not
155
     */
156
    protected static function createHtaccessFile($path, $allowed = false)
157
    {
158
        $htaccessFile = $path . '.htaccess';
159
        if (file_exists($htaccessFile)) {
160
            return;
161
        }
162
        $template = new \Text_Template(FileUtils::RESOURCES_PATH . 'htaccess_template');
163
        $template->setVar([
164
            'new_all' => $allowed ? 'granted' : 'denied',
165
            'old_allow' => $allowed ? 'all' : 'none',
166
            'old_deny' => $allowed ? 'none' : 'all',
167
        ]);
168
        file_put_contents($htaccessFile, $template->render());
169
    }
170
171
    /**
172
     * Get the cache filename according to the given URL.
173
     * Using a sha1 hash to get unique valid filenames.
174
     *
175
     * @param string $url Thumbnail URL.
176
     *
177
     * @return string Thumb filename.
178
     */
179
    protected static function getThumbFilename($url)
180
    {
181
        return hash('sha1', $url);
182
    }
183
184
    /**
185
     * Make sure that the cache type exists.
186
     *
187
     * @param string $type Cache type.
188
     *
189
     * @throws CacheException Cache type doesn't exists.
190
     */
191
    protected static function checkCacheType($type)
192
    {
193
        if ($type != self::TYPE_THUMB && $type != self::TYPE_FINDER) {
194
            throw new CacheException('Unknown cache type '. $type);
195
        }
196
    }
197
198
    /**
199
     * Recreates cache folders just in case the user delete them.
200
     */
201
    protected static function rebuildCacheFolders()
202
    {
203
        $mainFolder = ConfigManager::get('settings.path.cache', 'cache/');
204
        if (! is_dir($mainFolder)) {
205
            mkdir($mainFolder, 0755);
206
        }
207
        if (! is_dir($mainFolder.self::TYPE_THUMB)) {
208
            mkdir($mainFolder.self::TYPE_THUMB, 0755);
209
        }
210
        if (! is_readable($mainFolder . self::TYPE_THUMB . DIRECTORY_SEPARATOR . '.gitkeep')) {
211
            touch($mainFolder . self::TYPE_THUMB . DIRECTORY_SEPARATOR . '.gitkeep');
212
        }
213
        if (! is_dir($mainFolder.self::TYPE_FINDER)) {
214
            mkdir($mainFolder . self::TYPE_FINDER, 0755);
215
        }
216
        if (! is_readable($mainFolder . self::TYPE_THUMB . DIRECTORY_SEPARATOR . '.gitkeep')) {
217
            touch($mainFolder.self::TYPE_FINDER.DIRECTORY_SEPARATOR.'.gitkeep');
218
        }
219
    }
220
221
    /**
222
     * Return the hashed folder name for a given domain.
223
     *
224
     * @param string $domain name
225
     *
226
     * @return string hash
227
     */
228
    protected static function getDomainHash($domain)
229
    {
230
        return md5($domain);
231
    }
232
}
233