Module::exist()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 6
nc 2
nop 0
1
<?php
2
/**
3
 * For the full copyright and license information, please view the LICENSE.md
4
 * file that was distributed with this source code.
5
 */
6
7
namespace Notamedia\ConsoleJedi\Module;
8
9
use Bitrix\Main\Config\Option;
10
use Bitrix\Main\ModuleManager;
11
use Notamedia\ConsoleJedi\Application\Exception\BitrixException;
12
use Symfony\Component\Filesystem\Filesystem;
13
14
/**
15
 * Module entity.
16
 *
17
 * @author Marat Shamshutdinov <[email protected]>
18
 */
19
class Module
20
{
21
    /**
22
     * @var string
23
     */
24
    private $name;
25
    /**
26
     * @var \CModule
27
     */
28
    private $object;
29
    /**
30
     * @var bool
31
     */
32
    private $beta = false;
33
34
    /**
35
     * @param string $moduleName
36
     */
37
    public function __construct($moduleName)
38
    {
39
        $this->name = $this->normalizeName($moduleName);
40
    }
41
42
    /**
43
     * @param string $moduleName
44
     * @return string
45
     */
46
    protected function normalizeName($moduleName)
47
    {
48
        return preg_replace("/[^a-zA-Z0-9_.]+/i", "", trim($moduleName));
49
    }
50
51
    /**
52
     * @return \CModule
53
     */
54
    protected function &getObject()
55
    {
56
        if (!isset($this->object)) {
57
            $this->object = \CModule::CreateModuleObject($this->name);
58
        }
59
60
        if (!is_object($this->object) || !($this->object instanceof \CModule)) {
0 ignored issues
show
Bug introduced by
The class CModule does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
61
            unset($this->object);
62
            throw new Exception\ModuleNotFoundException('Module not found or incorrect', $this->name);
63
        }
64
65
        return $this->object;
66
    }
67
68
    /**
69
     * Checks for module and module object existence.
70
     *
71
     * @return bool
72
     */
73
    public function exist()
74
    {
75
        try {
76
            $this->getObject();
77
        } catch (Exception\ModuleNotFoundException $e) {
78
            return false;
79
        }
80
81
        return true;
82
    }
83
84
    /**
85
     * Check if module exists and installed
86
     *
87
     * @return bool
88
     */
89
    public function isRegistered()
90
    {
91
        return ModuleManager::isModuleInstalled($this->name) && $this->exist();
92
    }
93
94
    /**
95
     * @return bool true for marketplace modules, false for kernel modules
96
     */
97
    public function isThirdParty()
98
    {
99
        return strpos($this->name, '.') !== false;
100
    }
101
102
    /**
103
     * Install module.
104
     *
105
     * @throws Exception\ModuleException
106
     * @throws BitrixException
107
     */
108 View Code Duplication
    public function register()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
109
    {
110
        if (!$this->isRegistered()) {
111
            $moduleObject =& $this->getObject();
112
113
            /**
114
             * It's important to check if module class defines InstallDB method (it must register module)
115
             * Thus absent InstallDB indicates that the module does not support automatic installation
116
             */
117
            if ((new \ReflectionClass($moduleObject))->getMethod('InstallDB')->class !== get_class($moduleObject)) {
118
                throw new Exception\ModuleInstallException(
119
                    'Missing InstallDB method. This module does not support automatic installation',
120
                    $this->name
121
                );
122
            }
123
124
            if (!$moduleObject->InstallDB() && BitrixException::hasException()) {
125
                throw new Exception\ModuleInstallException(
126
                    get_class($moduleObject) . '::InstallDB() returned false',
127
                    $this->name
128
                );
129
            }
130
131
            $moduleObject->InstallEvents();
132
133
            /** @noinspection PhpVoidFunctionResultUsedInspection */
134
            if (!$moduleObject->InstallFiles() && BitrixException::hasException()) {
135
                throw new Exception\ModuleInstallException(
136
                    get_class($moduleObject) . '::InstallFiles() returned false',
137
                    $this->name
138
                );
139
            }
140
141
            if (!$this->isRegistered()) {
142
                throw new Exception\ModuleInstallException(
143
                    'Module was not registered. Probably it does not support automatic installation.',
144
                    $this->name
145
                );
146
            }
147
        }
148
149
        return $this;
150
    }
151
152
    /**
153
     * Download module from Marketplace.
154
     *
155
     * @return $this
156
     */
157
    public function load()
0 ignored issues
show
Coding Style introduced by
load uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
158
    {
159
        if (!$this->isRegistered()) {
160
            if (!$this->exist()) {
161
                require_once($_SERVER["DOCUMENT_ROOT"] . '/bitrix/modules/main/classes/general/update_client_partner.php');
162
163
                if (!\CUpdateClientPartner::LoadModuleNoDemand(
164
                    $this->getName(),
165
                    $strError,
0 ignored issues
show
Bug introduced by
The variable $strError does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
166
                    $this->isBeta() ? 'N' : 'Y',
167
                    LANGUAGE_ID)
168
                ) {
169
                    throw new Exception\ModuleLoadException($strError, $this->getName());
170
                }
171
            }
172
        }
173
174
        return $this;
175
    }
176
177
    /**
178
     * Uninstall module.
179
     *
180
     * @throws Exception\ModuleException
181
     * @throws BitrixException
182
     */
183 View Code Duplication
    public function unRegister()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
184
    {
185
        $moduleObject = $this->getObject();
186
187
        if ($this->isRegistered()) {
188
            /**
189
             * It's important to check if module class defines UnInstallDB method (it should unregister module)
190
             * Thus absent UnInstallDB indicates that the module does not support automatic uninstallation
191
             */
192
            if ((new \ReflectionClass($moduleObject))->getMethod('UnInstallDB')->class !== get_class($moduleObject)) {
193
                throw new Exception\ModuleUninstallException(
194
                    'Missing UnInstallDB method. This module does not support automatic uninstallation',
195
                    $this->name
196
                );
197
            }
198
199
            /** @noinspection PhpVoidFunctionResultUsedInspection */
200
            if (!$moduleObject->UnInstallFiles() && BitrixException::hasException()) {
201
                throw new Exception\ModuleUninstallException(
202
                    get_class($moduleObject) . '::UnInstallFiles() returned false',
203
                    $this->name
204
                );
205
            }
206
207
            $moduleObject->UnInstallEvents();
208
209
            /** @noinspection PhpVoidFunctionResultUsedInspection */
210
            if (!$moduleObject->UnInstallDB() && BitrixException::hasException()) {
211
                throw new Exception\ModuleUninstallException(
212
                    get_class($moduleObject) . '::UnInstallFiles() returned false',
213
                    $this->name
214
                );
215
            }
216
217
            if ($this->isRegistered()) {
218
                throw new Exception\ModuleUninstallException('Module was not unregistered', $this->name);
219
            }
220
        }
221
222
        return $this;
223
    }
224
225
    /**
226
     * Uninstall and remove module directory.
227
     */
228
    public function remove()
0 ignored issues
show
Coding Style introduced by
remove uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
229
    {
230
        if ($this->isRegistered()) {
231
            $this->unRegister();
232
        }
233
234
        $path = getLocalPath('modules/' . $this->getName());
235
236
        if ($path) {
237
            (new Filesystem())->remove($_SERVER['DOCUMENT_ROOT'] . $path);
238
        }
239
240
        unset($this->object);
241
242
        return $this;
243
    }
244
245
    /**
246
     * Update module.
247
     *
248
     * It must be called repeatedly until the method returns false.
249
     * After each call php must be restarted (new process created) to update module class and function definitions.
250
     *
251
     * @param array $modulesUpdated [optional]
252
     * @return bool
253
     */
254
    public function update(&$modulesUpdated = null)
0 ignored issues
show
Coding Style introduced by
update uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
255
    {
256
        require_once($_SERVER["DOCUMENT_ROOT"] . '/bitrix/modules/main/classes/general/update_client_partner.php');
257
258
        if (!$this->isThirdParty()) {
259
            throw new Exception\ModuleUpdateException('Kernel module updates are currently not supported.',
260
                $this->getName());
261
        }
262
263
        // ensures module existence
264
        $this->getObject();
265
266
        $errorMessage = $updateDescription = null;
267
        $loadResult = \CUpdateClientPartner::LoadModulesUpdates(
268
            $errorMessage,
269
            $updateDescription,
270
            LANGUAGE_ID,
271
            $this->isBeta() ? 'N' : 'Y',
272
            [$this->getName()],
273
            true
274
        );
275
        switch ($loadResult) {
276
            // archive loaded
277
            case "S":
278
                return $this->update($modulesUpdated);
279
280
            // error
281
            case "E":
282
                throw new Exception\ModuleUpdateException($errorMessage, $this->getName());
283
284
            // finished installing updates
285
            case "F":
286
                return false;
287
288
            // need to process loaded update
289
            case 'U':
290
                break;
291
        }
292
293
        /** @var string Temp directory with update files */
294
        $updateDir = null;
295
296
        if (!\CUpdateClientPartner::UnGzipArchive($updateDir, $errorMessage, true)) {
297
            throw new Exception\ModuleUpdateException('[CL02] UnGzipArchive failed. ' . $errorMessage,
298
                $this->getName());
299
        }
300
301
        $this->validateUpdate($updateDir);
302
303
        if (isset($updateDescription["DATA"]["#"]["NOUPDATES"])) {
304
            \CUpdateClientPartner::ClearUpdateFolder($_SERVER["DOCUMENT_ROOT"] . "/bitrix/updates/" . $updateDir);
305
            return false;
306
        }
307
308
        $modulesUpdated = $updateDescr = [];
309
        if (isset($updateDescription["DATA"]["#"]["ITEM"])) {
310
            foreach ($updateDescription["DATA"]["#"]["ITEM"] as $moduleInfo) {
311
                $modulesUpdated[$moduleInfo["@"]["NAME"]] = $moduleInfo["@"]["VALUE"];
312
                $updateDescr[$moduleInfo["@"]["NAME"]] = $moduleInfo["@"]["DESCR"];
313
            }
314
        }
315
316
        if (\CUpdateClientPartner::UpdateStepModules($updateDir, $errorMessage)) {
317
            foreach ($modulesUpdated as $key => $value) {
318
                if (Option::set('main', 'event_log_marketplace', "Y") === "Y") {
319
                    \CEventLog::Log("INFO", "MP_MODULE_DOWNLOADED", "main", $key, $value);
320
                }
321
            }
322
        } else {
323
            throw new Exception\ModuleUpdateException('[CL04] UpdateStepModules failed. ' . $errorMessage,
324
                $this->getName());
325
        }
326
327
        return true;
328
    }
329
330
    /**
331
     * Check update files.
332
     *
333
     * @param string $updateDir
334
     */
335
    protected function validateUpdate($updateDir)
336
    {
337
        $errorMessage = null;
338
        if (!\CUpdateClientPartner::CheckUpdatability($updateDir, $errorMessage)) {
339
            throw new Exception\ModuleUpdateException('[CL03] CheckUpdatability failed. ' . $errorMessage,
340
                $this->getName());
341
        }
342
343
        if (isset($updateDescription["DATA"]["#"]["ERROR"])) {
0 ignored issues
show
Bug introduced by
The variable $updateDescription seems to never exist, and therefore isset should always return false. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
344
            $errorMessage = "";
345
            foreach ($updateDescription["DATA"]["#"]["ERROR"] as $errorDescription) {
346
                $errorMessage .= "[" . $errorDescription["@"]["TYPE"] . "] " . $errorDescription["#"];
347
            }
348
            throw new Exception\ModuleUpdateException($errorMessage, $this->getName());
349
        }
350
    }
351
352
    /**
353
     * Returns module name.
354
     *
355
     * @return string
356
     */
357
    public function getName()
358
    {
359
        return $this->name;
360
    }
361
362
    /**
363
     * Beta releases allowed?
364
     *
365
     * @return boolean
366
     */
367
    public function isBeta()
368
    {
369
        return $this->beta;
370
    }
371
372
    /**
373
     * Set beta releases installation.
374
     *
375
     * @param boolean $beta
376
     */
377
    public function setBeta($beta = true)
378
    {
379
        $this->beta = $beta;
380
    }
381
382
    public function getVersion()
383
    {
384
        return $this->getObject()->MODULE_VERSION;
385
    }
386
}