Passed
Push — master ( 4d7221...80adc8 )
by Carlos
04:20
created

PluginDeploy::mergeXMLDocs()   B

Complexity

Conditions 7
Paths 15

Size

Total Lines 38
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 1
Metric Value
eloc 24
c 3
b 0
f 1
dl 0
loc 38
rs 8.6026
cc 7
nc 15
nop 2
1
<?php
2
/**
3
 * This file is part of FacturaScripts
4
 * Copyright (C) 2017-2019 Carlos Garcia Gomez <[email protected]>
5
 *
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU Lesser General Public License as
8
 * published by the Free Software Foundation, either version 3 of the
9
 * License, or (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
 * GNU Lesser General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Lesser General Public License
17
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18
 */
19
namespace FacturaScripts\Core\Base;
20
21
use Exception;
22
use SimpleXMLElement;
23
24
/**
25
 * Description of PluginDeploy
26
 *
27
 * @author Carlos García Gómez <[email protected]>
28
 */
29
class PluginDeploy
30
{
31
32
    /**
33
     *
34
     * @var array
35
     */
36
    private $enabledPlugins = [];
37
38
    /**
39
     *
40
     * @var array
41
     */
42
    private $fileList = [];
43
44
    /**
45
     * Deploy all the necessary files in the Dinamic folder to be able to use plugins
46
     * with the autoloader, but following the priority system of FacturaScripts.
47
     *
48
     * @param string $pluginPath
49
     * @param array  $enabledPlugins
50
     * @param bool   $clean
51
     */
52
    public function deploy(string $pluginPath, array $enabledPlugins, bool $clean = true)
53
    {
54
        $this->enabledPlugins = array_reverse($enabledPlugins);
55
56
        $fileManager = $this->toolBox()->files();
57
        $folders = ['Assets', 'Controller', 'Data', 'Lib', 'Model', 'Table', 'View', 'XMLView'];
58
        foreach ($folders as $folder) {
59
            if ($clean) {
60
                $fileManager->delTree(\FS_FOLDER . DIRECTORY_SEPARATOR . 'Dinamic' . DIRECTORY_SEPARATOR . $folder);
61
            }
62
63
            $this->createFolder(\FS_FOLDER . DIRECTORY_SEPARATOR . 'Dinamic' . DIRECTORY_SEPARATOR . $folder);
64
65
            /// examine the plugins
66
            foreach ($this->enabledPlugins as $pluginName) {
67
                if (file_exists($pluginPath . $pluginName . DIRECTORY_SEPARATOR . $folder)) {
68
                    $this->linkFiles($folder, 'Plugins', $pluginName);
69
                }
70
            }
71
72
            /// examine the core
73
            if (file_exists(\FS_FOLDER . DIRECTORY_SEPARATOR . 'Core' . DIRECTORY_SEPARATOR . $folder)) {
74
                $this->linkFiles($folder);
75
            }
76
        }
77
    }
78
79
    /**
80
     * Initialize the controllers dynamically.
81
     */
82
    public function initControllers()
83
    {
84
        $menuManager = new MenuManager();
85
        $menuManager->init();
86
        $pageNames = [];
87
88
        $files = $this->toolBox()->files()->scanFolder(\FS_FOLDER . DIRECTORY_SEPARATOR . 'Dinamic' . DIRECTORY_SEPARATOR . 'Controller', false);
89
        foreach ($files as $fileName) {
90
            if (substr($fileName, -4) !== '.php') {
91
                continue;
92
            }
93
94
            $controllerName = substr($fileName, 0, -4);
95
            $controllerNamespace = '\\FacturaScripts\\Dinamic\\Controller\\' . $controllerName;
96
97
            if (!class_exists($controllerNamespace)) {
98
                /// we force the loading of the file because at this point the autoloader will not find it
99
                require \FS_FOLDER . DIRECTORY_SEPARATOR . 'Dinamic' . DIRECTORY_SEPARATOR . 'Controller' . DIRECTORY_SEPARATOR . $controllerName . '.php';
100
            }
101
102
            try {
103
                $controller = new $controllerNamespace($controllerName);
104
                $menuManager->selectPage($controller->getPageData());
105
                $pageNames[] = $controllerName;
106
            } catch (Exception $exc) {
107
                $this->toolBox()->i18nLog()->critical('cant-load-controller', ['%controllerName%' => $controllerName]);
108
                $this->toolBox()->log()->critical($exc->getMessage());
109
            }
110
        }
111
112
        $menuManager->removeOld($pageNames);
113
        $menuManager->reload();
114
115
        /// checks app homepage
116
        $appSettings = $this->toolBox()->appSettings();
117
        if (!in_array($appSettings->get('default', 'homepage', ''), $pageNames)) {
118
            $appSettings->set('default', 'homepage', 'AdminPlugins');
119
            $appSettings->save();
120
        }
121
    }
122
123
    /**
124
     * Create the folder.
125
     *
126
     * @param string $folder
127
     *
128
     * @return bool
129
     */
130
    private function createFolder(string $folder): bool
131
    {
132
        if ($this->toolBox()->files()->createFolder($folder, true)) {
133
            return true;
134
        }
135
136
        $this->toolBox()->i18nLog()->critical('cant-create-folder', ['%folderName%' => $folder]);
137
        return false;
138
    }
139
140
    /**
141
     * 
142
     * @param string $namespace
143
     *
144
     * @return bool
145
     */
146
    private function extensionSupport(string $namespace)
147
    {
148
        return $namespace === 'FacturaScripts\Dinamic\Controller';
149
    }
150
151
    /**
152
     * 
153
     * @param string $fileName
154
     * @param string $folder
155
     * @param string $place
156
     * @param string $pluginName
157
     *
158
     * @return string
159
     */
160
    private function getClassType(string $fileName, string $folder, string $place, string $pluginName): string
161
    {
162
        $path = \FS_FOLDER . DIRECTORY_SEPARATOR . $place . DIRECTORY_SEPARATOR;
163
        $path .= empty($pluginName) ? $folder : $pluginName . DIRECTORY_SEPARATOR . $folder;
164
165
        $txt = file_get_contents($path . DIRECTORY_SEPARATOR . $fileName);
166
        return strpos($txt, 'abstract class ') === false ? 'class' : 'abstract class';
167
    }
168
169
    /**
170
     * Link the files.
171
     *
172
     * @param string $folder
173
     * @param string $place
174
     * @param string $pluginName
175
     */
176
    private function linkFiles(string $folder, string $place = 'Core', string $pluginName = '')
177
    {
178
        $path = \FS_FOLDER . DIRECTORY_SEPARATOR . $place . DIRECTORY_SEPARATOR;
179
        $path .= empty($pluginName) ? $folder : $pluginName . DIRECTORY_SEPARATOR . $folder;
180
181
        foreach ($this->toolBox()->files()->scanFolder($path, true) as $fileName) {
182
            if (isset($this->fileList[$folder][$fileName])) {
183
                continue;
184
            }
185
186
            $fileInfo = pathinfo($fileName);
187
            if (is_dir($path . DIRECTORY_SEPARATOR . $fileName)) {
188
                $this->createFolder(\FS_FOLDER . DIRECTORY_SEPARATOR . 'Dinamic' . DIRECTORY_SEPARATOR . $folder . DIRECTORY_SEPARATOR . $fileName);
189
                continue;
190
            } elseif ($fileInfo['filename'] === '' || !is_file($path . DIRECTORY_SEPARATOR . $fileName)) {
191
                continue;
192
            }
193
194
            $filePath = $path . DIRECTORY_SEPARATOR . $fileName;
195
            $extension = $fileInfo['extension'] ?? '';
196
            switch ($extension) {
197
                case 'php':
198
                    $this->linkPHPFile($fileName, $folder, $place, $pluginName);
199
                    break;
200
201
                case 'xml':
202
                    $this->linkXMLFile($fileName, $folder, $filePath);
203
                    break;
204
205
                default:
206
                    $this->linkFile($fileName, $folder, $filePath);
207
            }
208
        }
209
    }
210
211
    /**
212
     * Link PHP files dinamically.
213
     *
214
     * @param string $fileName
215
     * @param string $folder
216
     * @param string $place
217
     * @param string $pluginName
218
     */
219
    private function linkPHPFile(string $fileName, string $folder, string $place, string $pluginName)
220
    {
221
        $auxNamespace = empty($pluginName) ? $place : 'Plugins\\' . $pluginName;
222
        $namespace = 'FacturaScripts\\' . $auxNamespace . '\\' . $folder;
223
        $newNamespace = "FacturaScripts\Dinamic\\" . $folder;
224
225
        $paths = explode(DIRECTORY_SEPARATOR, $fileName);
226
        for ($key = 0; $key < count($paths) - 1; ++$key) {
227
            $namespace .= '\\' . $paths[$key];
228
            $newNamespace .= '\\' . $paths[$key];
229
        }
230
231
        $className = basename($fileName, '.php');
232
        $txt = '<?php namespace ' . $newNamespace . ";\n\n"
233
            . '/**' . "\n"
234
            . ' * Class created by Core/Base/PluginManager' . "\n"
235
            . ' * @author FacturaScripts <[email protected]>' . "\n"
236
            . ' */' . "\n"
237
            . $this->getClassType($fileName, $folder, $place, $pluginName) . ' ' . $className . ' extends \\' . $namespace . '\\' . $className;
238
239
        $txt .= $this->extensionSupport($newNamespace) ? "\n{\n\tuse \FacturaScripts\Core\Base\ExtensionsTrait;\n}\n" : "\n{\n}\n";
240
241
        file_put_contents(\FS_FOLDER . DIRECTORY_SEPARATOR . 'Dinamic' . DIRECTORY_SEPARATOR . $folder . DIRECTORY_SEPARATOR . $fileName, $txt);
242
        $this->fileList[$folder][$fileName] = $fileName;
243
    }
244
245
    /**
246
     * Link other static files.
247
     *
248
     * @param string $fileName
249
     * @param string $folder
250
     * @param string $filePath
251
     */
252
    private function linkFile(string $fileName, string $folder, string $filePath)
253
    {
254
        $path = \FS_FOLDER . DIRECTORY_SEPARATOR . 'Dinamic' . DIRECTORY_SEPARATOR . $folder . DIRECTORY_SEPARATOR . $fileName;
255
        copy($filePath, $path);
256
        $this->fileList[$folder][$fileName] = $fileName;
257
    }
258
259
    /**
260
     * Link other static files.
261
     *
262
     * @param string $fileName
263
     * @param string $folder
264
     * @param string $originPath
265
     */
266
    private function linkXMLFile(string $fileName, string $folder, string $originPath)
267
    {
268
        /// Find extensions
269
        $extensions = [];
270
        foreach ($this->enabledPlugins as $pluginName) {
271
            $extensionPath = \FS_FOLDER . DIRECTORY_SEPARATOR . 'Plugins' . DIRECTORY_SEPARATOR . $pluginName . DIRECTORY_SEPARATOR
272
                . 'Extension' . DIRECTORY_SEPARATOR . $folder . DIRECTORY_SEPARATOR . $fileName;
273
            if (file_exists($extensionPath)) {
274
                $extensions[] = $extensionPath;
275
            }
276
        }
277
278
        /// Merge XML files
279
        $xml = simplexml_load_file($originPath);
280
        foreach ($extensions as $extension) {
281
            $xmlExtension = simplexml_load_file($extension);
282
            $this->mergeXMLDocs($xml, $xmlExtension);
283
        }
284
285
        $destinationPath = \FS_FOLDER . DIRECTORY_SEPARATOR . 'Dinamic' . DIRECTORY_SEPARATOR . $folder . DIRECTORY_SEPARATOR . $fileName;
286
        $xml->asXML($destinationPath);
287
288
        $this->fileList[$folder][$fileName] = $fileName;
289
    }
290
291
    /**
292
     * 
293
     * @param SimpleXMLElement $source
294
     * @param SimpleXMLElement $extension
295
     */
296
    private function mergeXMLDocs(&$source, $extension)
297
    {
298
        foreach ($extension->children() as $extChild) {
299
            /// we need $num to know wich dom element number to overwrite
300
            $num = -1;
301
302
            $found = false;
303
            foreach ($source->children() as $child) {
304
                if ($child->getName() == $extChild->getName()) {
305
                    $num++;
306
                }
307
308
                if (!$this->mergeXMLDocsCompare($child, $extChild)) {
309
                    continue;
310
                }
311
312
                /// element found. Overwrite or append children?
313
                $found = true;
314
                $extDom = dom_import_simplexml($extChild);
315
                switch (mb_strtolower($extDom->getAttribute('overwrite'))) {
316
                    case 'true':
317
                        $sourceDom = dom_import_simplexml($source);
318
                        $newElement = $sourceDom->ownerDocument->importNode($extDom, true);
319
                        $sourceDom->replaceChild($newElement, $sourceDom->getElementsByTagName($newElement->nodeName)->item($num));
320
                        break;
321
322
                    default:
323
                        $this->mergeXMLDocs($child, $extChild);
324
                }
325
                break;
326
            }
327
328
            /// elemento not found. Append all.
329
            if (!$found) {
330
                $sourceDom = dom_import_simplexml($source);
331
                $extDom = dom_import_simplexml($extChild);
332
                $newElement = $sourceDom->ownerDocument->importNode($extDom, true);
333
                $sourceDom->appendChild($newElement);
334
            }
335
        }
336
    }
337
338
    /**
339
     * 
340
     * @param SimpleXMLElement $source
341
     * @param SimpleXMLElement $extension
342
     *
343
     * @return bool
344
     */
345
    private function mergeXMLDocsCompare($source, $extension)
346
    {
347
        if ($source->getName() != $extension->getName()) {
348
            return false;
349
        }
350
351
        foreach ($extension->attributes() as $extAttr => $extAttrValue) {
352
            if ($extAttr != 'name') {
353
                continue;
354
            }
355
356
            foreach ($source->attributes() as $attr => $attrValue) {
357
                if ($attr == $extAttr) {
358
                    return (string) $extAttrValue == (string) $attrValue;
359
                }
360
            }
361
        }
362
363
        return in_array($extension->getName(), ['columns', 'modals', 'rows']);
364
    }
365
366
    /**
367
     * 
368
     * @return ToolBox
369
     */
370
    private function toolBox()
371
    {
372
        return new ToolBox();
373
    }
374
}
375