Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
97.06% |
66 / 68 |
|
80.00% |
8 / 10 |
CRAP | |
0.00% |
0 / 1 |
ApplicationManager | |
97.06% |
66 / 68 |
|
80.00% |
8 / 10 |
35 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
loadInfo | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
2 | |||
install | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
6 | |||
uninstall | |
90.91% |
10 / 11 |
|
0.00% |
0 / 1 |
4.01 | |||
reInit | |
85.71% |
6 / 7 |
|
0.00% |
0 / 1 |
3.03 | |||
getProvidingForModule | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
5 | |||
getInstalledApplications | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
8 | |||
installFiles | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
uninstallFiles | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
replacePlaceholder | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
4 |
1 | <?php |
2 | |
3 | /** |
4 | * Jingga |
5 | * |
6 | * PHP Version 8.1 |
7 | * |
8 | * @package phpOMS\Application |
9 | * @copyright Dennis Eichhorn |
10 | * @license OMS License 2.0 |
11 | * @version 1.0.0 |
12 | * @link https://jingga.app |
13 | */ |
14 | declare(strict_types=1); |
15 | |
16 | namespace phpOMS\Application; |
17 | |
18 | use phpOMS\System\File\Local\Directory; |
19 | use phpOMS\System\File\PathException; |
20 | |
21 | /** |
22 | * Application manager class. |
23 | * |
24 | * General application managing functionality. |
25 | * |
26 | * @package phpOMS\Application |
27 | * @license OMS License 2.0 |
28 | * @link https://jingga.app |
29 | * @since 1.0.0 |
30 | */ |
31 | final class ApplicationManager |
32 | { |
33 | /** |
34 | * Application instance. |
35 | * |
36 | * @var ApplicationAbstract |
37 | * @since 1.0.0 |
38 | */ |
39 | private ApplicationAbstract $app; |
40 | |
41 | /** |
42 | * Installed modules. |
43 | * |
44 | * @var array<string, ApplicationInfo> |
45 | * @since 1.0.0 |
46 | */ |
47 | private array $installed = []; |
48 | |
49 | /** |
50 | * Constructor. |
51 | * |
52 | * @param ApplicationAbstract $app Application |
53 | * |
54 | * @since 1.0.0 |
55 | */ |
56 | public function __construct(ApplicationAbstract $app) |
57 | { |
58 | $this->app = $app; |
59 | } |
60 | |
61 | /** |
62 | * Load info of application. |
63 | * |
64 | * @param string $appPath Application path |
65 | * |
66 | * @return ApplicationInfo |
67 | * |
68 | * @throws PathException |
69 | * |
70 | * @since 1.0.0 |
71 | */ |
72 | private function loadInfo(string $appPath) : ApplicationInfo |
73 | { |
74 | $path = \realpath($appPath); |
75 | if ($path === false) { |
76 | throw new PathException($appPath); |
77 | } |
78 | |
79 | $info = new ApplicationInfo($path); |
80 | $info->load(); |
81 | |
82 | return $info; |
83 | } |
84 | |
85 | /** |
86 | * Install the application |
87 | * |
88 | * @param string $source Source of the application |
89 | * @param string $destination Destination of the application |
90 | * @param string $theme Theme |
91 | * |
92 | * @return bool |
93 | * |
94 | * @since 1.0.0 |
95 | */ |
96 | public function install(string $source, string $destination, string $theme = 'Default') : bool |
97 | { |
98 | $destination = \rtrim($destination, '\\/'); |
99 | $source = \rtrim($source, '/\\'); |
100 | |
101 | if (!\is_dir($source) || \is_dir($destination) |
102 | || !\is_file($source . '/Admin/Installer.php') |
103 | ) { |
104 | return false; |
105 | } |
106 | |
107 | try { |
108 | $info = $this->loadInfo($source . '/info.json'); |
109 | $this->installed[$info->getInternalName()] = $info; |
110 | |
111 | $this->installFiles($source, $destination); |
112 | $this->replacePlaceholder($destination); |
113 | |
114 | if (($path = \realpath($destination)) === false) { |
115 | return false; // @codeCoverageIgnore |
116 | } |
117 | |
118 | $classPath = \substr($path . '/Admin/Installer', (int) \strlen((string) \realpath(__DIR__ . '/../../'))); |
119 | |
120 | // @var class-string<InstallerAbstract> $class |
121 | $class = \strtr($classPath, '/', '\\'); |
122 | $class::install($this->app, $info, $this->app->appSettings); |
123 | |
124 | return true; |
125 | } catch (\Throwable $_) { |
126 | return false; // @codeCoverageIgnore |
127 | } |
128 | } |
129 | |
130 | /** |
131 | * Uninstall the application |
132 | * |
133 | * @param string $source Source of the application |
134 | * |
135 | * @return bool |
136 | * |
137 | * @since 1.0.0 |
138 | */ |
139 | public function uninstall(string $source) : bool |
140 | { |
141 | $source = \rtrim($source, '/\\'); |
142 | if (($path = \realpath($source)) === false || !\is_file($source . '/Admin/Uninstaller.php')) { |
143 | return false; |
144 | } |
145 | |
146 | try { |
147 | $info = $this->loadInfo($source . '/info.json'); |
148 | $this->installed[$info->getInternalName()] = $info; |
149 | |
150 | $classPath = \substr($path . '/Admin/Uninstaller', (int) \strlen((string) \realpath(__DIR__ . '/../../'))); |
151 | |
152 | // @var class-string<UninstallerAbstract> $class |
153 | $class = \strtr($classPath, '/', '\\'); |
154 | $class::uninstall($this->app->dbPool, $info, $this->app->appSettings); |
155 | |
156 | $this->uninstallFiles($source); |
157 | |
158 | return true; |
159 | } catch (\Throwable $_) { |
160 | return false; // @codeCoverageIgnore |
161 | } |
162 | } |
163 | |
164 | /** |
165 | * Re-init application. |
166 | * |
167 | * @param string $appPath App path |
168 | * |
169 | * @return void |
170 | * |
171 | * @since 1.0.0 |
172 | */ |
173 | public function reInit(string $appPath) : void |
174 | { |
175 | $info = $this->loadInfo($appPath . '/info.json'); |
176 | if ($info === null) { |
177 | return; |
178 | } |
179 | |
180 | if (($path = \realpath($appPath)) === false) { |
181 | return; // @codeCoverageIgnore |
182 | } |
183 | |
184 | // @var class-string<InstallerAbstract> $class |
185 | $classPath = \substr($path . '/Admin/Installer', (int) \strlen((string) \realpath(__DIR__ . '/../../'))); |
186 | $class = \strtr($classPath, '/', '\\'); |
187 | |
188 | /** @var $class InstallerAbstract */ |
189 | $class::reInit($info); |
190 | } |
191 | |
192 | /** |
193 | * Get all applications who are providing for a specific module. |
194 | * |
195 | * @param string $module Module to check providings for |
196 | * |
197 | * @return array<string, string[]> |
198 | * |
199 | * @since 1.0.0 |
200 | */ |
201 | public function getProvidingForModule(string $module) : array |
202 | { |
203 | $providing = []; |
204 | $installed = $this->getInstalledApplications(); |
205 | |
206 | foreach ($installed as $app => $info) { |
207 | if (!isset($providing[$app])) { |
208 | $providing[$app] = []; |
209 | } |
210 | |
211 | $appProviding = $info->getProviding(); |
212 | foreach ($appProviding as $for => $version) { |
213 | if ($for !== $module) { |
214 | continue; |
215 | } |
216 | |
217 | $providing[$app][] = $for; |
218 | } |
219 | } |
220 | |
221 | return $providing; |
222 | } |
223 | |
224 | /** |
225 | * Get all installed modules. |
226 | * |
227 | * @param bool $useCache Use Cache |
228 | * @param string $basePath Base path for the applications |
229 | * |
230 | * @return array<string, ApplicationInfo> |
231 | * |
232 | * @since 1.0.0 |
233 | */ |
234 | public function getInstalledApplications(bool $useCache = true, string $basePath = __DIR__ . '/../../Web') : array |
235 | { |
236 | if (empty($this->installed) || !$useCache) { |
237 | $apps = \scandir($basePath); |
238 | |
239 | if ($apps === false) { |
240 | return $this->installed; // @codeCoverageIgnore |
241 | } |
242 | |
243 | foreach ($apps as $app) { |
244 | if ($app === '.' || $app === '..' || !\is_file($basePath . '/' . $app . '/info.json')) { |
245 | continue; |
246 | } |
247 | |
248 | $this->installed[$app] = $this->loadInfo($basePath . '/' . $app . '/info.json'); |
249 | } |
250 | } |
251 | |
252 | return $this->installed; |
253 | } |
254 | |
255 | /** |
256 | * Install the files to the destination |
257 | * |
258 | * @param string $source Source path |
259 | * @param string $destination Destination of the application |
260 | * |
261 | * @return void |
262 | * |
263 | * @since 1.0.0 |
264 | */ |
265 | private function installFiles(string $source, string $destination) : void |
266 | { |
267 | Directory::copy($source, $destination); |
268 | } |
269 | |
270 | /** |
271 | * Uninstall files |
272 | * |
273 | * @param string $source Source path |
274 | * |
275 | * @return void |
276 | * |
277 | * @since 1.0.0 |
278 | */ |
279 | private function uninstallFiles(string $source) : void |
280 | { |
281 | Directory::delete($source); |
282 | } |
283 | |
284 | /** |
285 | * Replace placeholder string (application placeholder name) |
286 | * |
287 | * @param string $destination Destination of the application |
288 | * |
289 | * @return void |
290 | * |
291 | * @since 1.0.0 |
292 | */ |
293 | private function replacePlaceholder(string $destination) : void |
294 | { |
295 | $files = Directory::list($destination, '*', true); |
296 | foreach ($files as $file) { |
297 | if (!\is_file($destination . '/' . $file)) { |
298 | continue; |
299 | } |
300 | |
301 | $content = \file_get_contents($destination . '/' . $file); |
302 | if ($content === false) { |
303 | continue; // @codeCoverageIgnore |
304 | } |
305 | |
306 | \file_put_contents($destination . '/' . $file, \str_replace('{APPNAME}', \basename($destination), $content)); |
307 | } |
308 | } |
309 | } |