Passed
Push — develop ( 535216...2a3c68 )
by Paul
07:18
created

AbstractAsset::optimize()   B

Complexity

Conditions 8
Paths 8

Size

Total Lines 22
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 72

Importance

Changes 0
Metric Value
eloc 15
c 0
b 0
f 0
dl 0
loc 22
ccs 0
cts 16
cp 0
rs 8.4444
cc 8
nc 8
nop 0
crap 72
1
<?php
2
3
namespace GeminiLabs\SiteReviews\Modules\Assets;
4
5
use GeminiLabs\SiteReviews\Helpers\Arr;
6
7
abstract class AbstractAsset
8
{
9
    public $abort;
10
    public $after;
11
    public $before;
12
    public $contents;
13
    public $dependencies;
14
    public $handles;
15
    public $sources;
16
17
    public function __construct()
18
    {
19
        $this->reset();
20
        if ('1' === filter_input(INPUT_GET, 'nocache') || $this->isOptimizationDisabled()) {
21
            $this->abort = true;
22
            delete_transient($this->transient());
23
        }
24
    }
25
26
    public function canOptimize(): bool
27
    {
28
        if ($this->abort) {
29
            return false;
30
        }
31
        if ($this->isOptimizationDisabled()) {
32
            return false;
33
        }
34
        return true;
35
    }
36
37
    public function isOptimizationDisabled(): bool
38
    {
39
        return !$this->isOptimizationEnabled();
40
    }
41
42
    public function isOptimizationEnabled(): bool
43
    {
44
        return glsr()->filterBool('optimize/'.$this->type(), false);
45
    }
46
47
    public function isOptimized(): bool
48
    {
49
        $hash = $this->hash();
50
        $path = Arr::getAs('string', $this->file(), 'path');
51
        if (file_exists($path) && $hash === get_transient($this->transient())) {
52
            return true;
53
        }
54
        return false;
55
    }
56
57
    public function optimize(): void
58
    {
59
        if (!$this->canOptimize() || $this->isOptimized()) {
60
            return;
61
        }
62
        $file = $this->file();
63
        if (empty($file)) {
64
            return;
65
        }
66
        $hash = $this->hash();
67
        $this->handles = array_keys($this->versions());
68
        $this->prepare();
69
        if ($hash !== get_transient($this->transient())) {
70
            $this->combine();
71
            if ($this->store($file['path'])) {
72
                set_transient($this->transient(), $hash);
73
            }
74
        }
75
        if (!$this->abort && file_exists($file['path'])) {
76
            $this->enqueue($file['url'], $hash);
77
        }
78
        $this->reset();
79
    }
80
81
    public function url(): string
82
    {
83
        if ($this->canOptimize() && $this->isOptimized()) {
84
            return Arr::getAs('string', $this->file(), 'url');
85
        }
86
        return $this->originalUrl();
87
    }
88
89
    public function version(): string
90
    {
91
        if ($this->canOptimize() && $this->isOptimized()) {
92
            return $this->hash();
93
        }
94
        return glsr()->version;
95
    }
96
97
    protected function combine(): void
98
    {
99
        $pluginDirUrl = plugin_dir_url('');
100
        $pluginDirPath = substr(glsr()->path(), 0, -1 * strlen(glsr()->id.'/'));
101
        $sources = array_filter($this->sources);
102
        foreach ($sources as $url) {
103
            $path = str_replace($pluginDirUrl, $pluginDirPath, $url);
104
            if ($path !== $url) {
105
                $contents = $this->filesystem()->get_contents($path);
106
            }
107
            if (empty($contents)) { // @todo if this fails, do the addon assets still load?
108
                $this->abort = true;
109
                break;
110
            }
111
            $this->contents .= $contents;
112
        }
113
    }
114
115
    abstract protected function enqueue(string $url, string $hash): void;
116
117
    protected function file(): array
118
    {
119
        require_once ABSPATH.WPINC.'/pluggable.php';
120
        $uploads = wp_upload_dir();
121
        if (!file_exists($uploads['basedir'])) {
122
            $uploads = wp_upload_dir(null, true, true); // maybe the site has been moved, so refresh the cached uploads path
123
        }
124
        $basedir = sprintf('%s/%s/assets', $uploads['basedir'], glsr()->id);
125
        $baseurl = sprintf('%s/%s/assets', $uploads['baseurl'], glsr()->id);
126
        if (is_ssl()) { // fix SSL just in case...
127
            $baseurl = str_replace('http://', 'https://', $baseurl);
128
        }
129
        if (!wp_mkdir_p($basedir)) {
130
            glsr_log()->error('Unable to store optimized assets in the uploads directory.');
131
            return [];
132
        }
133
        return [
134
            'path' => sprintf('%s/%s.%s', $basedir, glsr()->id, $this->type()),
135
            'url' => sprintf('%s/%s.%s', $baseurl, glsr()->id, $this->type()),
136
        ];
137
    }
138
139
    protected function filesystem(): \WP_Filesystem_Base
140
    {
141
        global $wp_filesystem;
142
        if (empty($wp_filesystem)) {
143
            require_once ABSPATH.'wp-admin/includes/file.php';
144
            WP_Filesystem();
145
        }
146
        return $wp_filesystem;
147
    }
148
149
    public function hash(): string
150
    {
151
        return md5(serialize($this->versions()));
152
    }
153
154
    abstract protected function originalUrl(): string;
155
156
    protected function prepare(): void
157
    {
158
        $removedDeps = array_diff($this->handles, [glsr()->id]);
159
        $this->sources = array_fill_keys($this->handles, ''); // ensure correct order!
160
        foreach ($this->registered() as $handle => $dependency) {
161
            if (str_starts_with($handle, glsr()->id)) {
162
                $dependency->deps = array_diff($dependency->deps, $removedDeps);
163
            }
164
            if (!in_array($handle, $this->handles)) {
165
                continue;
166
            }
167
            if (!empty($dependency->extra['after'])) {
168
                $this->after = array_merge($this->after, $dependency->extra['after']);
169
                $this->after = Arr::reindex(Arr::unique($this->after));
170
            }
171
            if (!empty($dependency->extra['before'])) {
172
                $this->before = array_merge($this->before, $dependency->extra['before']);
173
                $this->before = Arr::reindex(Arr::unique($this->before));
174
            }
175
            if (!empty($dependency->deps)) {
176
                $this->dependencies = array_merge($this->dependencies, $dependency->deps);
177
                $this->dependencies = array_diff($dependency->deps, [glsr()->id]);
178
            }
179
            $this->sources[$handle] = $dependency->src;
180
        }
181
    }
182
183
    abstract protected function registered(): array;
184
185
    protected function reset(): void
186
    {
187
        $this->abort = false;
188
        $this->after = [];
189
        $this->before = [];
190
        $this->contents = '';
191
        $this->dependencies = [];
192
        $this->handles = [];
193
        $this->sources = [];
194
    }
195
196
    protected function store(string $filepath): bool
197
    {
198
        if ($this->abort) {
199
            return false;
200
        }
201
        if ($this->filesystem()->put_contents($filepath, $this->contents)) {
202
            return true;
203
        }
204
        glsr_log()->error('Unable to write content to optimized assets.');
205
        return false;
206
    }
207
208
    protected function transient(): string
209
    {
210
        return glsr()->prefix.'optimized_'.$this->type();
211
    }
212
213
    abstract protected function type(): string;
214
215
    protected function versions(): array
216
    {
217
        $versions = glsr()->retrieveAs('array', 'addons');
218
        $versions = Arr::prepend($versions, glsr()->version, glsr()->id);
219
        return $versions;
220
    }
221
}
222