gplcart /
installer
This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 1 | <?php |
||
| 2 | |||
| 3 | /** |
||
| 4 | * @package Installer |
||
| 5 | * @author Iurii Makukh <[email protected]> |
||
| 6 | * @copyright Copyright (c) 2015, Iurii Makukh |
||
| 7 | * @license https://www.gnu.org/licenses/gpl.html GNU/GPLv3 |
||
| 8 | */ |
||
| 9 | |||
| 10 | namespace gplcart\modules\installer\models; |
||
| 11 | |||
| 12 | use Exception; |
||
| 13 | use gplcart\core\Config; |
||
| 14 | use gplcart\core\helpers\Url; |
||
| 15 | use gplcart\core\helpers\Zip; |
||
| 16 | use gplcart\core\models\Job; |
||
| 17 | use gplcart\core\models\Module as ModuleModel; |
||
| 18 | use gplcart\core\models\Translation; |
||
| 19 | use gplcart\core\Module; |
||
| 20 | |||
| 21 | /** |
||
| 22 | * Manages basic behaviors and data related to Installer module |
||
| 23 | */ |
||
| 24 | class Install |
||
| 25 | { |
||
| 26 | |||
| 27 | /** |
||
| 28 | * Database class instance |
||
| 29 | * @var \gplcart\core\Database $db |
||
| 30 | */ |
||
| 31 | protected $db; |
||
| 32 | |||
| 33 | /** |
||
| 34 | * Config class instance |
||
| 35 | * @var \gplcart\core\Config $config |
||
| 36 | */ |
||
| 37 | protected $config; |
||
| 38 | |||
| 39 | /** |
||
| 40 | * Module model instance |
||
| 41 | * @var \gplcart\core\Module $module |
||
| 42 | */ |
||
| 43 | protected $module; |
||
| 44 | |||
| 45 | /** |
||
| 46 | * Zip helper class instance |
||
| 47 | * @var \gplcart\core\helpers\Zip $zip |
||
| 48 | */ |
||
| 49 | protected $zip; |
||
| 50 | |||
| 51 | /** |
||
| 52 | * Url helper class instance |
||
| 53 | * @var \gplcart\core\helpers\Url $url |
||
| 54 | */ |
||
| 55 | protected $url; |
||
| 56 | |||
| 57 | /** |
||
| 58 | * Translation UI model instance |
||
| 59 | * @var \gplcart\core\models\Translation $translation |
||
| 60 | */ |
||
| 61 | protected $translation; |
||
| 62 | |||
| 63 | /** |
||
| 64 | * Job model instance |
||
| 65 | * @var \gplcart\core\models\Job $job |
||
| 66 | */ |
||
| 67 | protected $job; |
||
| 68 | |||
| 69 | /** |
||
| 70 | * Module model instance |
||
| 71 | * @var \gplcart\core\models\Module $module_model |
||
| 72 | */ |
||
| 73 | protected $module_model; |
||
| 74 | |||
| 75 | /** |
||
| 76 | * The latest validation error |
||
| 77 | * @var string |
||
| 78 | */ |
||
| 79 | protected $error; |
||
| 80 | |||
| 81 | /** |
||
| 82 | * The temporary renamed module directory |
||
| 83 | * @var string |
||
| 84 | */ |
||
| 85 | protected $tempname; |
||
| 86 | |||
| 87 | /** |
||
| 88 | * The module directory |
||
| 89 | * @var string |
||
| 90 | */ |
||
| 91 | protected $destination; |
||
| 92 | |||
| 93 | /** |
||
| 94 | * The module ID |
||
| 95 | * @var string |
||
| 96 | */ |
||
| 97 | protected $module_id; |
||
| 98 | |||
| 99 | /** |
||
| 100 | * An array of module data |
||
| 101 | * @var array |
||
| 102 | */ |
||
| 103 | protected $data; |
||
| 104 | |||
| 105 | /** |
||
| 106 | * Whether an original module has been temporary renamed |
||
| 107 | * @var boolean |
||
| 108 | */ |
||
| 109 | protected $renamed; |
||
| 110 | |||
| 111 | /** |
||
| 112 | * Install constructor. |
||
| 113 | * @param Config $config |
||
| 114 | * @param Module $module |
||
| 115 | * @param ModuleModel $module_model |
||
| 116 | * @param Translation $translation |
||
| 117 | * @param Job $job |
||
| 118 | * @param Zip $zip |
||
| 119 | * @param Url $url |
||
| 120 | */ |
||
| 121 | public function __construct(Config $config, Module $module, ModuleModel $module_model, |
||
| 122 | Translation $translation, Job $job, Zip $zip, Url $url) |
||
| 123 | { |
||
| 124 | $this->config = $config; |
||
| 125 | $this->module = $module; |
||
| 126 | $this->db = $this->config->getDb(); |
||
| 127 | |||
| 128 | $this->zip = $zip; |
||
| 129 | $this->url = $url; |
||
| 130 | $this->job = $job; |
||
| 131 | $this->translation = $translation; |
||
| 132 | $this->module_model = $module_model; |
||
| 133 | } |
||
| 134 | |||
| 135 | /** |
||
| 136 | * Installs a module from a ZIP file |
||
| 137 | * @param string $zip |
||
| 138 | * @return string|bool |
||
| 139 | */ |
||
| 140 | public function fromZip($zip) |
||
| 141 | { |
||
| 142 | $this->data = null; |
||
|
0 ignored issues
–
show
|
|||
| 143 | $this->error = null; |
||
| 144 | $this->renamed = false; |
||
| 145 | $this->tempname = null; |
||
| 146 | $this->module_id = null; |
||
| 147 | $this->destination = null; |
||
| 148 | |||
| 149 | if (!$this->setModuleId($zip)) { |
||
| 150 | return $this->error; |
||
| 151 | } |
||
| 152 | |||
| 153 | if (!$this->extract()) { |
||
| 154 | $this->rollback(); |
||
| 155 | return $this->error; |
||
| 156 | } |
||
| 157 | |||
| 158 | if (!$this->validate()) { |
||
| 159 | $this->rollback(); |
||
| 160 | return $this->error; |
||
| 161 | } |
||
| 162 | |||
| 163 | if (!$this->backup()) { |
||
| 164 | return $this->error; |
||
| 165 | } |
||
| 166 | |||
| 167 | return true; |
||
| 168 | } |
||
| 169 | |||
| 170 | /** |
||
| 171 | * Install modules from multiple URLs |
||
| 172 | * @param array $sources |
||
| 173 | */ |
||
| 174 | public function fromUrl(array $sources) |
||
| 175 | { |
||
| 176 | $total = count($sources); |
||
| 177 | $finish_message = $this->translation->text('New modules: %inserted, updated: %updated'); |
||
| 178 | $vars = array('@url' => $this->url->get('', array('download_errors' => true))); |
||
| 179 | $errors_message = $this->translation->text('New modules: %inserted, updated: %updated, errors: %errors. <a href="@url">See error log</a>', $vars); |
||
| 180 | |||
| 181 | $data = array( |
||
| 182 | 'total' => $total, |
||
| 183 | 'data' => array('sources' => $sources), |
||
| 184 | 'id' => 'installer_download_module', |
||
| 185 | 'log' => array('errors' => $this->getErrorLogFile()), |
||
| 186 | 'redirect_message' => array('finish' => $finish_message, 'errors' => $errors_message) |
||
| 187 | ); |
||
| 188 | |||
| 189 | $this->job->submit($data); |
||
| 190 | } |
||
| 191 | |||
| 192 | /** |
||
| 193 | * Returns path to error log file |
||
| 194 | * @return string |
||
| 195 | */ |
||
| 196 | public function getErrorLogFile() |
||
| 197 | { |
||
| 198 | return gplcart_file_private_temp('installer-download-errors.csv'); |
||
| 199 | } |
||
| 200 | |||
| 201 | /** |
||
| 202 | * Backup the previous version of the updated module |
||
| 203 | */ |
||
| 204 | protected function backup() |
||
| 205 | { |
||
| 206 | if (empty($this->tempname)) { |
||
| 207 | return true; |
||
| 208 | } |
||
| 209 | |||
| 210 | $module = $this->data; |
||
| 211 | |||
| 212 | $module += array( |
||
| 213 | 'directory' => $this->tempname, |
||
| 214 | 'module_id' => $this->module_id |
||
| 215 | ); |
||
| 216 | |||
| 217 | $result = $this->getBackupModule()->backup('module', $module); |
||
| 218 | |||
| 219 | if ($result === true) { |
||
| 220 | gplcart_file_delete_recursive($this->tempname); |
||
| 221 | return true; |
||
| 222 | } |
||
| 223 | |||
| 224 | $this->error = $this->translation->text('Failed to backup module @id', array('@id' => $this->module_id)); |
||
| 225 | return false; |
||
| 226 | } |
||
| 227 | |||
| 228 | /** |
||
| 229 | * Returns Backup module instance |
||
| 230 | * @return \gplcart\modules\backup\Main |
||
| 231 | */ |
||
| 232 | protected function getBackupModule() |
||
| 233 | { |
||
| 234 | /** @var \gplcart\modules\backup\Main $instance */ |
||
| 235 | $instance = $this->module->getInstance('backup'); |
||
| 236 | return $instance; |
||
| 237 | } |
||
| 238 | |||
| 239 | /** |
||
| 240 | * Extracts module files to the system directory |
||
| 241 | * @return boolean |
||
| 242 | */ |
||
| 243 | protected function extract() |
||
| 244 | { |
||
| 245 | $this->destination = GC_DIR_MODULE . "/{$this->module_id}"; |
||
| 246 | |||
| 247 | if (file_exists($this->destination)) { |
||
| 248 | |||
| 249 | $this->tempname = gplcart_file_unique($this->destination . '~'); |
||
| 250 | |||
| 251 | if (!rename($this->destination, $this->tempname)) { |
||
| 252 | $this->error = $this->translation->text('Failed to rename @old to @new', array( |
||
| 253 | '@old' => $this->destination, '@new' => $this->tempname)); |
||
| 254 | return false; |
||
| 255 | } |
||
| 256 | |||
| 257 | $this->renamed = true; |
||
| 258 | } |
||
| 259 | |||
| 260 | if ($this->zip->extract(GC_DIR_MODULE)) { |
||
| 261 | return true; |
||
| 262 | } |
||
| 263 | |||
| 264 | $this->error = $this->translation->text('Failed to extract to @name', array('@name' => $this->destination)); |
||
| 265 | return false; |
||
| 266 | } |
||
| 267 | |||
| 268 | /** |
||
| 269 | * Restore the original module files |
||
| 270 | */ |
||
| 271 | protected function rollback() |
||
| 272 | { |
||
| 273 | if (!$this->isUpdate() || ($this->isUpdate() && $this->renamed)) { |
||
| 274 | gplcart_file_delete_recursive($this->destination); |
||
| 275 | } |
||
| 276 | |||
| 277 | if (isset($this->tempname)) { |
||
| 278 | rename($this->tempname, $this->destination); |
||
| 279 | } |
||
| 280 | } |
||
| 281 | |||
| 282 | /** |
||
| 283 | * Validates a module data |
||
| 284 | * @return boolean |
||
| 285 | */ |
||
| 286 | protected function validate() |
||
| 287 | { |
||
| 288 | $this->data = $this->module->getInfo($this->module_id); |
||
| 289 | |||
| 290 | if (empty($this->data)) { |
||
| 291 | $this->error = $this->translation->text('Failed to read module @id', array('@id' => $this->module_id)); |
||
| 292 | return false; |
||
| 293 | } |
||
| 294 | |||
| 295 | try { |
||
| 296 | $this->module_model->checkCore($this->data); |
||
| 297 | $this->module_model->checkPhpVersion($this->data); |
||
| 298 | return true; |
||
| 299 | } catch (Exception $ex) { |
||
| 300 | $this->error = $ex->getMessage(); |
||
| 301 | return false; |
||
| 302 | } |
||
| 303 | } |
||
| 304 | |||
| 305 | /** |
||
| 306 | * Returns an array of files from a ZIP file |
||
| 307 | * @param string $file |
||
| 308 | * @return array |
||
| 309 | */ |
||
| 310 | public function getFilesFromZip($file) |
||
| 311 | { |
||
| 312 | try { |
||
| 313 | $files = $this->zip->set($file)->getList(); |
||
| 314 | } catch (Exception $e) { |
||
| 315 | return array(); |
||
| 316 | } |
||
| 317 | |||
| 318 | return count($files) < 2 ? array() : $files; |
||
| 319 | } |
||
| 320 | |||
| 321 | /** |
||
| 322 | * Set a module id |
||
| 323 | * @param string $file |
||
| 324 | * @return boolean |
||
| 325 | */ |
||
| 326 | protected function setModuleId($file) |
||
| 327 | { |
||
| 328 | $module_id = $this->getModuleIdFromZip($file); |
||
| 329 | |||
| 330 | try { |
||
| 331 | $this->module_model->checkModuleId($module_id); |
||
| 332 | } catch (Exception $ex) { |
||
| 333 | $this->error = $ex->getMessage(); |
||
| 334 | return false; |
||
| 335 | } |
||
| 336 | |||
| 337 | // Do not deal with enabled modules as it may cause fatal results |
||
| 338 | // Check if the module ID actually has enabled status in the database |
||
| 339 | // Alternative system methods are based on the scanned module folders so may return incorrect results |
||
| 340 | if ($this->isEnabledModule($module_id)) { |
||
|
0 ignored issues
–
show
It seems like
$module_id defined by $this->getModuleIdFromZip($file) on line 328 can also be of type false; however, gplcart\modules\installe...tall::isEnabledModule() does only seem to accept string, did you maybe forget to handle an error condition?
This check looks for type mismatches where the missing type is Consider the follow example <?php
function getDate($date)
{
if ($date !== null) {
return new DateTime($date);
}
return false;
}
This function either returns a new Loading history...
|
|||
| 341 | $this->error = $this->translation->text('Module @id is enabled and cannot be updated', array('@id' => $module_id)); |
||
| 342 | return false; |
||
| 343 | } |
||
| 344 | |||
| 345 | $this->module_id = $module_id; |
||
|
0 ignored issues
–
show
It seems like
$module_id can also be of type false. However, the property $module_id is declared as type string. Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
Loading history...
|
|||
| 346 | return true; |
||
| 347 | } |
||
| 348 | |||
| 349 | /** |
||
| 350 | * Check if a module ID has enabled status in the database |
||
| 351 | * @param string $module_id |
||
| 352 | * @return bool |
||
| 353 | */ |
||
| 354 | protected function isEnabledModule($module_id) |
||
| 355 | { |
||
| 356 | $sql = 'SELECT module_id FROM module WHERE module_id=? AND status > 0'; |
||
| 357 | $result = $this->db->fetchColumn($sql, array($module_id)); |
||
| 358 | return !empty($result); |
||
| 359 | } |
||
| 360 | |||
| 361 | /** |
||
| 362 | * Returns a module id from a zip file or false on error |
||
| 363 | * @param string $file |
||
| 364 | * @return boolean|string |
||
| 365 | */ |
||
| 366 | public function getModuleIdFromZip($file) |
||
| 367 | { |
||
| 368 | $list = $this->getFilesFromZip($file); |
||
| 369 | |||
| 370 | if (empty($list)) { |
||
| 371 | return false; |
||
| 372 | } |
||
| 373 | |||
| 374 | $folder = reset($list); |
||
| 375 | |||
| 376 | if (strrchr($folder, '/') !== '/') { |
||
| 377 | return false; |
||
| 378 | } |
||
| 379 | |||
| 380 | $nested = 0; |
||
| 381 | foreach ($list as $item) { |
||
| 382 | if (strpos($item, $folder) === 0) { |
||
| 383 | $nested++; |
||
| 384 | } |
||
| 385 | } |
||
| 386 | |||
| 387 | if (count($list) != $nested) { |
||
| 388 | return false; |
||
| 389 | } |
||
| 390 | |||
| 391 | return rtrim($folder, '/'); |
||
| 392 | } |
||
| 393 | |||
| 394 | /** |
||
| 395 | * Whether the module files have been updated |
||
| 396 | * @return bool |
||
| 397 | */ |
||
| 398 | public function isUpdate() |
||
| 399 | { |
||
| 400 | return isset($this->tempname); |
||
| 401 | } |
||
| 402 | |||
| 403 | } |
||
| 404 |
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.
Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..