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)) { |
|
|
|
|
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() |
|
|
|
|
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() |
|
|
|
|
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, |
|
|
|
|
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() |
|
|
|
|
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() |
|
|
|
|
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) |
|
|
|
|
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"])) { |
|
|
|
|
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
|
|
|
} |
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 thecomposer.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
orrequire-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 you have not tested against this specific condition, such errors might go unnoticed.