1 | <?php |
||
2 | |||
3 | namespace SunshineCMS\Installers; |
||
4 | |||
5 | use Composer\Composer; |
||
6 | use Composer\Installer\BinaryInstaller; |
||
7 | use Composer\Installer\LibraryInstaller; |
||
8 | use Composer\IO\IOInterface; |
||
9 | use Composer\Package\PackageInterface; |
||
10 | use Composer\Repository\InstalledRepositoryInterface; |
||
11 | use Composer\Util\Filesystem; |
||
12 | use InvalidArgumentException; |
||
13 | |||
14 | /** |
||
15 | * Composer Library Installer Core for SunshineCMS. |
||
16 | * |
||
17 | * Handles the installation of SunshineCMS core, plugins, |
||
18 | * themes and other extensions. |
||
19 | * |
||
20 | * @since 1.0.0 |
||
21 | * |
||
22 | * @author SunshineCMS Authors & Developers |
||
23 | * @license GPL-3.0-or-later |
||
24 | * |
||
25 | * @package SunshineCMS\Installers |
||
26 | */ |
||
27 | class Installer extends LibraryInstaller |
||
28 | { |
||
29 | /** |
||
30 | * Stores base name. |
||
31 | * |
||
32 | * @var string |
||
33 | */ |
||
34 | protected $name = 'sunshinecms'; |
||
35 | |||
36 | /** |
||
37 | * Stores installation locations. |
||
38 | * |
||
39 | * @var array |
||
40 | */ |
||
41 | protected $locations = [ |
||
42 | // SunshineCMS Core |
||
43 | 'core' => '', |
||
44 | |||
45 | // SunshineCMS Packs |
||
46 | '.*(?=-pack)' => 'vendor/{$vendor}/{$name}/', |
||
47 | |||
48 | // SunshineCMS Plugins |
||
49 | 'plugin' => 'plugins/{$vendor}/{$name}/', |
||
50 | |||
51 | // SunshineCMS Themes |
||
52 | 'theme' => 'themes/public/{$vendor}/{$name}/', |
||
53 | '.*(?=-theme)' => 'themes/{$type}/{$vendor}/{$name}/', |
||
54 | ]; |
||
55 | |||
56 | /** |
||
57 | * Constructs installer. |
||
58 | * |
||
59 | * Constructs the installer core for SunshineCMS or disables it when |
||
60 | * specified in main composer extra installer disable list. |
||
61 | * |
||
62 | * @param IOInterface $io |
||
63 | * @param Composer $composer |
||
64 | * @param string $type |
||
65 | * @param Filesystem|null $filesystem |
||
66 | * @param BinaryInstaller|null $binaryInstaller |
||
67 | */ |
||
68 | public function __construct( |
||
69 | IOInterface $io, |
||
70 | Composer $composer, |
||
71 | $type = 'library', |
||
72 | Filesystem $filesystem = null, |
||
73 | BinaryInstaller $binaryInstaller = null |
||
74 | ) { |
||
75 | parent::__construct($io, $composer, $type, $filesystem, $binaryInstaller); |
||
76 | $this->removeDisabledInstallers(); |
||
77 | } |
||
78 | |||
79 | /** |
||
80 | * {@inheritDoc} |
||
81 | */ |
||
82 | public function getInstallPath(PackageInterface $package) |
||
83 | { |
||
84 | // Get type |
||
85 | $type = $package->getType(); |
||
86 | |||
87 | // Get vendor and name |
||
88 | $prettyName = $package->getPrettyName(); |
||
89 | if (strpos($prettyName, '/') !== false) { |
||
90 | list($vendor, $name) = explode('/', $prettyName); |
||
91 | } else { |
||
92 | $vendor = ''; |
||
93 | $name = $prettyName; |
||
94 | } |
||
95 | |||
96 | // Check if supported |
||
97 | if (!$this->supports($type)) { |
||
98 | throw new InvalidArgumentException('Package type of this package is not supported'); |
||
99 | } |
||
100 | |||
101 | // Inflect package vars |
||
102 | $availableVars = $this->inflectPackageVars(compact('name', 'vendor', 'type')); |
||
103 | |||
104 | // Get different name if available |
||
105 | $extra = $package->getExtra(); |
||
106 | if (!empty($extra['installer-name'])) { |
||
107 | $availableVars['name'] = $extra['installer-name']; |
||
108 | } |
||
109 | |||
110 | // Return custom location |
||
111 | if ($this->composer->getPackage()) { |
||
112 | $extra = $this->composer->getPackage()->getExtra(); |
||
113 | if (!empty($extra['installer-paths'])) { |
||
114 | $customPath = $this->mapCustomInstallPaths($extra['installer-paths'], $prettyName, $type, $vendor); |
||
115 | if ($customPath !== false) { |
||
0 ignored issues
–
show
introduced
by
![]() |
|||
116 | return $this->templatePath($availableVars, $customPath); |
||
117 | } |
||
118 | } |
||
119 | } |
||
120 | |||
121 | // Return location |
||
122 | return $this->templatePath($availableVars); |
||
123 | } |
||
124 | |||
125 | /** |
||
126 | * {@inheritDoc} |
||
127 | */ |
||
128 | public function supports($type) |
||
129 | { |
||
130 | // Check base name |
||
131 | if ($this->name !== substr($type, 0, strlen($this->name))) { |
||
132 | return false; |
||
133 | } |
||
134 | |||
135 | // Check type name |
||
136 | foreach ($this->locations as $key => $value) { |
||
137 | preg_match('/' . $key . '/', $type, $matches, PREG_OFFSET_CAPTURE, 0); |
||
138 | |||
139 | if (!empty($matches)) { |
||
140 | return true; |
||
141 | } |
||
142 | } |
||
143 | |||
144 | // Return if not supported |
||
145 | return false; |
||
146 | } |
||
147 | |||
148 | /** |
||
149 | * Removes disabled installers. |
||
150 | * |
||
151 | * Looks for disabled installers in composer's extra config |
||
152 | * and remove them. |
||
153 | * |
||
154 | * @return void |
||
155 | */ |
||
156 | protected function removeDisabledInstallers() |
||
157 | { |
||
158 | // Get extra config |
||
159 | $extra = $this->composer->getPackage()->getExtra(); |
||
160 | |||
161 | // Check if installers need to be disabled |
||
162 | if (!isset($extra['installer-disable']) || $extra['installer-disable'] === false) { |
||
163 | return; |
||
164 | } |
||
165 | |||
166 | // Get installers to disable |
||
167 | $disable = $extra['installer-disable']; |
||
168 | |||
169 | // Ensure $disabled is an array |
||
170 | if (!is_array($disable)) { |
||
171 | $disable = [$disable]; |
||
172 | } |
||
173 | |||
174 | // Check which installers should be disabled |
||
175 | $all = [ |
||
176 | true, |
||
177 | 'all', |
||
178 | '*', |
||
179 | ]; |
||
180 | $intersect = array_intersect($all, $disable); |
||
181 | |||
182 | if (!empty($intersect)) { |
||
183 | // Disable all installers |
||
184 | $this->locations = []; |
||
185 | } else { |
||
186 | // Disable specified installers |
||
187 | foreach ($disable as $key => $installer) { |
||
188 | if (is_string($installer) && key_exists($installer, $this->locations)) { |
||
189 | unset($this->locations[$installer]); |
||
190 | } |
||
191 | } |
||
192 | } |
||
193 | } |
||
194 | |||
195 | /** |
||
196 | * Formats package name. |
||
197 | * |
||
198 | * Lowercases name and changes underscores to hyphens. Also |
||
199 | * cuts off leading or trailing `sunshinecms`, `plugin`, |
||
200 | * `theme` or `pack` from name depending on type. |
||
201 | * |
||
202 | * @param array $vars |
||
203 | * |
||
204 | * @return array |
||
205 | */ |
||
206 | protected function inflectPackageVars($vars) |
||
207 | { |
||
208 | // Lowercase and change underscores to hyphens |
||
209 | $vars['name'] = strtolower(str_replace('_', '-', $vars['name'])); |
||
210 | |||
211 | // Remove base name |
||
212 | $vars['name'] = preg_replace('/-' . $this->name . '/', '', $vars['name']); |
||
213 | $vars['name'] = preg_replace('/' . $this->name . '-/', '', $vars['name']); |
||
214 | |||
215 | // Remove type name |
||
216 | foreach ($this->locations as $key => $value) { |
||
217 | if ($vars['type'] === $this->name . '-' . $key) { |
||
218 | $vars['name'] = preg_replace('/[-]?' . $key . '[-]?/', '', $vars['name']); |
||
219 | break; |
||
220 | } |
||
221 | } |
||
222 | |||
223 | // Return formatted name |
||
224 | return $vars; |
||
225 | } |
||
226 | |||
227 | /** |
||
228 | * Searchs for install path. |
||
229 | * |
||
230 | * Searchs through a passed paths array for a custom install path. |
||
231 | * |
||
232 | * @param array $paths |
||
233 | * @param string $name |
||
234 | * @param string $type |
||
235 | * @param string $vendor = null |
||
236 | * |
||
237 | * @return string |
||
238 | */ |
||
239 | protected function mapCustomInstallPaths(array $paths, $name, $type, $vendor = null) |
||
240 | { |
||
241 | // Search in paths array |
||
242 | foreach ($paths as $path => $names) { |
||
243 | if (in_array($name, $names) || in_array('type:' . $type, $names) || in_array('vendor:' . $vendor, $names)) { |
||
244 | return $path; |
||
245 | } |
||
246 | } |
||
247 | |||
248 | // Return if not found |
||
249 | return false; |
||
0 ignored issues
–
show
|
|||
250 | } |
||
251 | |||
252 | /** |
||
253 | * Replaces vars in a path. |
||
254 | * |
||
255 | * Replaces vars for type, vendor and name with their calculated |
||
256 | * values from package details. |
||
257 | * |
||
258 | * @param array $vars |
||
259 | * |
||
260 | * @return string |
||
261 | */ |
||
262 | protected function templatePath(array $vars, $path = '') |
||
263 | { |
||
264 | // Export vars |
||
265 | extract($vars); |
||
266 | |||
267 | if (!empty($path)) { |
||
268 | // Insert vendor and name |
||
269 | preg_match_all('@\{\$([A-Za-z0-9_]*)\}@i', $path, $matches); |
||
270 | if (!empty($matches[1])) { |
||
271 | foreach ($matches[1] as $var) { |
||
272 | $path = str_replace('{$' . $var . '}', $$var, $path); |
||
273 | } |
||
274 | } |
||
275 | |||
276 | // Return template path |
||
277 | return preg_replace('#/+#', '/', $path); |
||
278 | } |
||
279 | |||
280 | // Search for path |
||
281 | foreach ($this->locations as $key => $value) { |
||
282 | if (preg_match('/(?<=' . $this->name . '-)' . $key . '/', $type)) { |
||
283 | if (strpos($value, '{') !== false) { |
||
284 | // Match type |
||
285 | preg_match( |
||
286 | '/(?<=' . $this->name . '-)' . $key . '/', |
||
287 | $type, |
||
288 | $type, |
||
289 | PREG_OFFSET_CAPTURE, |
||
290 | 0 |
||
291 | ); |
||
292 | $type = $type[0][0]; |
||
293 | |||
294 | // Insert vendor and name |
||
295 | preg_match_all('@\{\$([A-Za-z0-9_]*)\}@i', $value, $matches); |
||
296 | if (!empty($matches[1])) { |
||
297 | foreach ($matches[1] as $var) { |
||
298 | $value = str_replace('{$' . $var . '}', $$var, $value); |
||
299 | } |
||
300 | } |
||
301 | } |
||
302 | |||
303 | // Return template path |
||
304 | return preg_replace('#/+#', '/', $value); |
||
305 | } |
||
306 | } |
||
307 | } |
||
308 | } |
||
309 |