[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * Joomla! Content Management System 5 * 6 * @copyright (C) 2005 Open Source Matters, Inc. <https://www.joomla.org> 7 * @license GNU General Public License version 2 or later; see LICENSE.txt 8 */ 9 10 namespace Joomla\CMS\Installer\Adapter; 11 12 use Joomla\CMS\Application\ApplicationHelper; 13 use Joomla\CMS\Filesystem\Folder; 14 use Joomla\CMS\Installer\Installer; 15 use Joomla\CMS\Installer\InstallerAdapter; 16 use Joomla\CMS\Language\Text; 17 use Joomla\CMS\Log\Log; 18 use Joomla\CMS\Table\Table; 19 use Joomla\Database\ParameterType; 20 use Joomla\Utilities\ArrayHelper; 21 22 // phpcs:disable PSR1.Files.SideEffects 23 \defined('JPATH_PLATFORM') or die; 24 // phpcs:enable PSR1.Files.SideEffects 25 26 /** 27 * Module installer 28 * 29 * @since 3.1 30 */ 31 class ModuleAdapter extends InstallerAdapter 32 { 33 /** 34 * The install client ID 35 * 36 * @var integer 37 * @since 3.4 38 */ 39 protected $clientId; 40 41 /** 42 * `<scriptfile>` element of the extension manifest 43 * 44 * @var object 45 * @since 3.1 46 */ 47 protected $scriptElement = null; 48 49 /** 50 * Method to check if the extension is already present in the database 51 * 52 * @return void 53 * 54 * @since 3.4 55 * @throws \RuntimeException 56 */ 57 protected function checkExistingExtension() 58 { 59 try { 60 $this->currentExtensionId = $this->extension->find( 61 array( 62 'element' => $this->element, 63 'type' => $this->type, 64 'client_id' => $this->clientId, 65 ) 66 ); 67 } catch (\RuntimeException $e) { 68 // Install failed, roll back changes 69 throw new \RuntimeException( 70 Text::sprintf( 71 'JLIB_INSTALLER_ABORT_ROLLBACK', 72 Text::_('JLIB_INSTALLER_' . $this->route), 73 $e->getMessage() 74 ), 75 $e->getCode(), 76 $e 77 ); 78 } 79 } 80 81 /** 82 * Method to copy the extension's base files from the `<files>` tag(s) and the manifest file 83 * 84 * @return void 85 * 86 * @since 3.4 87 * @throws \RuntimeException 88 */ 89 protected function copyBaseFiles() 90 { 91 // Copy all necessary files 92 if ($this->parent->parseFiles($this->getManifest()->files, -1) === false) { 93 throw new \RuntimeException(Text::_('JLIB_INSTALLER_ABORT_MOD_COPY_FILES')); 94 } 95 96 // If there is a manifest script, let's copy it. 97 if ($this->manifest_script) { 98 $path['src'] = $this->parent->getPath('source') . '/' . $this->manifest_script; 99 $path['dest'] = $this->parent->getPath('extension_root') . '/' . $this->manifest_script; 100 101 if ($this->parent->isOverwrite() || !file_exists($path['dest'])) { 102 if (!$this->parent->copyFiles(array($path))) { 103 // Install failed, rollback changes 104 throw new \RuntimeException( 105 Text::sprintf( 106 'JLIB_INSTALLER_ABORT_MANIFEST', 107 Text::_('JLIB_INSTALLER_' . strtoupper($this->route)) 108 ) 109 ); 110 } 111 } 112 } 113 } 114 115 /** 116 * Custom discover method 117 * 118 * @return array Extension list of extensions available 119 * 120 * @since 3.1 121 */ 122 public function discover() 123 { 124 $results = array(); 125 $site_list = Folder::folders(JPATH_SITE . '/modules'); 126 $admin_list = Folder::folders(JPATH_ADMINISTRATOR . '/modules'); 127 $site_info = ApplicationHelper::getClientInfo('site', true); 128 $admin_info = ApplicationHelper::getClientInfo('administrator', true); 129 130 foreach ($site_list as $module) { 131 if (file_exists(JPATH_SITE . "/modules/$module/$module.xml")) { 132 $manifest_details = Installer::parseXMLInstallFile(JPATH_SITE . "/modules/$module/$module.xml"); 133 $extension = Table::getInstance('extension'); 134 $extension->set('type', 'module'); 135 $extension->set('client_id', $site_info->id); 136 $extension->set('element', $module); 137 $extension->set('folder', ''); 138 $extension->set('name', $module); 139 $extension->set('state', -1); 140 $extension->set('manifest_cache', json_encode($manifest_details)); 141 $extension->set('params', '{}'); 142 $results[] = clone $extension; 143 } 144 } 145 146 foreach ($admin_list as $module) { 147 if (file_exists(JPATH_ADMINISTRATOR . "/modules/$module/$module.xml")) { 148 $manifest_details = Installer::parseXMLInstallFile(JPATH_ADMINISTRATOR . "/modules/$module/$module.xml"); 149 $extension = Table::getInstance('extension'); 150 $extension->set('type', 'module'); 151 $extension->set('client_id', $admin_info->id); 152 $extension->set('element', $module); 153 $extension->set('folder', ''); 154 $extension->set('name', $module); 155 $extension->set('state', -1); 156 $extension->set('manifest_cache', json_encode($manifest_details)); 157 $extension->set('params', '{}'); 158 $results[] = clone $extension; 159 } 160 } 161 162 return $results; 163 } 164 165 /** 166 * Method to finalise the installation processing 167 * 168 * @return void 169 * 170 * @since 3.4 171 * @throws \RuntimeException 172 */ 173 protected function finaliseInstall() 174 { 175 // Clobber any possible pending updates 176 $update = Table::getInstance('update'); 177 $uid = $update->find( 178 array( 179 'element' => $this->element, 180 'type' => 'module', 181 'client_id' => $this->clientId, 182 ) 183 ); 184 185 if ($uid) { 186 $update->delete($uid); 187 } 188 189 // Lastly, we will copy the manifest file to its appropriate place. 190 if ($this->route !== 'discover_install') { 191 if (!$this->parent->copyManifest(-1)) { 192 // Install failed, rollback changes 193 throw new \RuntimeException( 194 Text::sprintf( 195 'JLIB_INSTALLER_ABORT_COPY_SETUP', 196 Text::_('JLIB_INSTALLER_' . strtoupper($this->route)) 197 ) 198 ); 199 } 200 } 201 } 202 203 /** 204 * Method to finalise the uninstallation processing 205 * 206 * @return boolean 207 * 208 * @since 4.0.0 209 * @throws \RuntimeException 210 */ 211 protected function finaliseUninstall(): bool 212 { 213 $extensionId = $this->extension->extension_id; 214 215 $db = $this->getDatabase(); 216 $retval = true; 217 218 // Remove the schema version 219 $query = $db->getQuery(true) 220 ->delete('#__schemas') 221 ->where('extension_id = :extension_id') 222 ->bind(':extension_id', $extensionId, ParameterType::INTEGER); 223 $db->setQuery($query); 224 $db->execute(); 225 226 $element = $this->extension->element; 227 $clientId = $this->extension->client_id; 228 229 // Let's delete all the module copies for the type we are uninstalling 230 $query->clear() 231 ->select($db->quoteName('id')) 232 ->from($db->quoteName('#__modules')) 233 ->where($db->quoteName('module') . ' = :element') 234 ->where($db->quoteName('client_id') . ' = :client_id') 235 ->bind(':element', $element) 236 ->bind(':client_id', $clientId, ParameterType::INTEGER); 237 $db->setQuery($query); 238 239 try { 240 $modules = $db->loadColumn(); 241 } catch (\RuntimeException $e) { 242 $modules = []; 243 } 244 245 // Do we have any module copies? 246 if (\count($modules)) { 247 // Ensure the list is sane 248 $modules = ArrayHelper::toInteger($modules); 249 $modID = implode(',', $modules); 250 251 // Wipe out any items assigned to menus 252 $query = $db->getQuery(true) 253 ->delete($db->quoteName('#__modules_menu')) 254 ->where($db->quoteName('moduleid') . ' IN (' . $modID . ')'); 255 $db->setQuery($query); 256 257 try { 258 $db->execute(); 259 } catch (\RuntimeException $e) { 260 Log::add(Text::sprintf('JLIB_INSTALLER_ERROR_MOD_UNINSTALL_EXCEPTION', $e->getMessage()), Log::WARNING, 'jerror'); 261 $retval = false; 262 } 263 264 // Wipe out any instances in the modules table 265 /** @var \Joomla\CMS\Table\Module $module */ 266 $module = Table::getInstance('Module'); 267 268 foreach ($modules as $modInstanceId) { 269 $module->load($modInstanceId); 270 271 if (!$module->delete()) { 272 Log::add(Text::sprintf('JLIB_INSTALLER_ERROR_MOD_UNINSTALL_EXCEPTION', $module->getError()), Log::WARNING, 'jerror'); 273 $retval = false; 274 } 275 } 276 } 277 278 // Now we will no longer need the module object, so let's delete it and free up memory 279 $this->extension->delete($this->extension->extension_id); 280 $query = $db->getQuery(true) 281 ->delete($db->quoteName('#__modules')) 282 ->where($db->quoteName('module') . ' = :element') 283 ->where($db->quoteName('client_id') . ' = :client_id') 284 ->bind(':element', $element) 285 ->bind(':client_id', $clientId, ParameterType::INTEGER); 286 $db->setQuery($query); 287 288 try { 289 // Clean up any other ones that might exist as well 290 $db->execute(); 291 } catch (\RuntimeException $e) { 292 // Ignore the error... 293 } 294 295 // Remove the installation folder 296 if (!Folder::delete($this->parent->getPath('extension_root'))) { 297 // Folder should raise an error 298 $retval = false; 299 } 300 301 return $retval; 302 } 303 304 /** 305 * Get the filtered extension element from the manifest 306 * 307 * @param string $element Optional element name to be converted 308 * 309 * @return string|null The filtered element 310 * 311 * @since 3.4 312 */ 313 public function getElement($element = null) 314 { 315 if ($element) { 316 return $element; 317 } 318 319 // Joomla 4 Module. 320 if ((string) $this->getManifest()->element) { 321 return (string) $this->getManifest()->element; 322 } 323 324 if (!\count($this->getManifest()->files->children())) { 325 return $element; 326 } 327 328 foreach ($this->getManifest()->files->children() as $file) { 329 if ((string) $file->attributes()->module) { 330 // Joomla 3 (legacy) Module. 331 return strtolower((string) $file->attributes()->module); 332 } 333 } 334 335 return $element; 336 } 337 338 /** 339 * Custom loadLanguage method 340 * 341 * @param string $path The path where we find language files 342 * 343 * @return void 344 * 345 * @since 3.4 346 */ 347 public function loadLanguage($path = null) 348 { 349 $source = $this->parent->getPath('source'); 350 $client = $this->parent->extension->client_id ? JPATH_ADMINISTRATOR : JPATH_SITE; 351 352 if (!$source) { 353 $this->parent->setPath('source', $client . '/modules/' . $this->parent->extension->element); 354 } 355 356 $this->setManifest($this->parent->getManifest()); 357 358 if ($this->getManifest()->files) { 359 $extension = $this->getElement(); 360 361 if ($extension) { 362 $source = $path ?: ($this->parent->extension->client_id ? JPATH_ADMINISTRATOR : JPATH_SITE) . '/modules/' . $extension; 363 $folder = (string) $this->getManifest()->files->attributes()->folder; 364 365 if ($folder && file_exists($path . '/' . $folder)) { 366 $source = $path . '/' . $folder; 367 } 368 369 $client = (string) $this->getManifest()->attributes()->client; 370 $this->doLoadLanguage($extension, $source, \constant('JPATH_' . strtoupper($client))); 371 } 372 } 373 } 374 375 /** 376 * Method to parse optional tags in the manifest 377 * 378 * @return void 379 * 380 * @since 3.4 381 */ 382 protected function parseOptionalTags() 383 { 384 // Parse optional tags 385 $this->parent->parseMedia($this->getManifest()->media, $this->clientId); 386 $this->parent->parseLanguages($this->getManifest()->languages, $this->clientId); 387 } 388 389 /** 390 * Prepares the adapter for a discover_install task 391 * 392 * @return void 393 * 394 * @since 3.4 395 */ 396 public function prepareDiscoverInstall() 397 { 398 $client = ApplicationHelper::getClientInfo($this->parent->extension->client_id); 399 $manifestPath = $client->path . '/modules/' . $this->parent->extension->element . '/' . $this->parent->extension->element . '.xml'; 400 $this->parent->manifest = $this->parent->isManifest($manifestPath); 401 $this->parent->setPath('manifest', $manifestPath); 402 $this->setManifest($this->parent->getManifest()); 403 } 404 405 /** 406 * Refreshes the extension table cache 407 * 408 * @return boolean Result of operation, true if updated, false on failure. 409 * 410 * @since 3.1 411 */ 412 public function refreshManifestCache() 413 { 414 $client = ApplicationHelper::getClientInfo($this->parent->extension->client_id); 415 $manifestPath = $client->path . '/modules/' . $this->parent->extension->element . '/' . $this->parent->extension->element . '.xml'; 416 $this->parent->manifest = $this->parent->isManifest($manifestPath); 417 $this->parent->setPath('manifest', $manifestPath); 418 $manifest_details = Installer::parseXMLInstallFile($this->parent->getPath('manifest')); 419 $this->parent->extension->manifest_cache = json_encode($manifest_details); 420 $this->parent->extension->name = $manifest_details['name']; 421 422 if ($this->parent->extension->store()) { 423 return true; 424 } else { 425 Log::add(Text::_('JLIB_INSTALLER_ERROR_MOD_REFRESH_MANIFEST_CACHE'), Log::WARNING, 'jerror'); 426 427 return false; 428 } 429 } 430 431 /** 432 * Removes this extension's files 433 * 434 * @return void 435 * 436 * @since 4.0.0 437 * @throws \RuntimeException 438 */ 439 protected function removeExtensionFiles() 440 { 441 $this->parent->removeFiles($this->getManifest()->media); 442 $this->parent->removeFiles($this->getManifest()->languages, $this->extension->client_id); 443 } 444 445 /** 446 * Method to do any prechecks and setup the install paths for the extension 447 * 448 * @return void 449 * 450 * @since 3.4 451 * @throws \RuntimeException 452 */ 453 protected function setupInstallPaths() 454 { 455 // Get the target application 456 $cname = (string) $this->getManifest()->attributes()->client; 457 458 if ($cname) { 459 // Attempt to map the client to a base path 460 $client = ApplicationHelper::getClientInfo($cname, true); 461 462 if ($client === false) { 463 throw new \RuntimeException( 464 Text::sprintf( 465 'JLIB_INSTALLER_ABORT_MOD_UNKNOWN_CLIENT', 466 Text::_('JLIB_INSTALLER_' . $this->route), 467 $client->name 468 ) 469 ); 470 } 471 472 $basePath = $client->path; 473 $this->clientId = $client->id; 474 } else { 475 // No client attribute was found so we assume the site as the client 476 $basePath = JPATH_SITE; 477 $this->clientId = 0; 478 } 479 480 // Set the installation path 481 if (empty($this->element)) { 482 throw new \RuntimeException( 483 Text::sprintf( 484 'JLIB_INSTALLER_ABORT_MOD_INSTALL_NOFILE', 485 Text::_('JLIB_INSTALLER_' . $this->route) 486 ) 487 ); 488 } 489 490 $this->parent->setPath('extension_root', $basePath . '/modules/' . $this->element); 491 } 492 493 /** 494 * Method to do any prechecks and setup the uninstall job 495 * 496 * @return void 497 * 498 * @since 4.0.0 499 */ 500 protected function setupUninstall() 501 { 502 // Get the extension root path 503 $element = $this->extension->element; 504 $client = ApplicationHelper::getClientInfo($this->extension->client_id); 505 506 if ($client === false) { 507 throw new \RuntimeException( 508 Text::sprintf( 509 'JLIB_INSTALLER_ERROR_MOD_UNINSTALL_UNKNOWN_CLIENT', 510 $this->extension->client_id 511 ) 512 ); 513 } 514 515 $this->parent->setPath('extension_root', $client->path . '/modules/' . $element); 516 517 $this->parent->setPath('source', $this->parent->getPath('extension_root')); 518 519 // Get the module's manifest object 520 // We do findManifest to avoid problem when uninstalling a list of extensions: getManifest cache its manifest file. 521 $this->parent->findManifest(); 522 $this->setManifest($this->parent->getManifest()); 523 524 // Attempt to load the language file; might have uninstall strings 525 $this->loadLanguage(($this->extension->client_id ? JPATH_ADMINISTRATOR : JPATH_SITE) . '/modules/' . $element); 526 } 527 528 /** 529 * Method to store the extension to the database 530 * 531 * @return void 532 * 533 * @since 3.4 534 * @throws \RuntimeException 535 */ 536 protected function storeExtension() 537 { 538 // Discover installs are stored a little differently 539 if ($this->route === 'discover_install') { 540 $manifest_details = Installer::parseXMLInstallFile($this->parent->getPath('manifest')); 541 542 $this->extension->manifest_cache = json_encode($manifest_details); 543 $this->extension->state = 0; 544 $this->extension->name = $manifest_details['name']; 545 $this->extension->enabled = 1; 546 $this->extension->params = $this->parent->getParams(); 547 548 if (!$this->extension->store()) { 549 // Install failed, roll back changes 550 throw new \RuntimeException(Text::_('JLIB_INSTALLER_ERROR_MOD_DISCOVER_STORE_DETAILS')); 551 } 552 553 return; 554 } 555 556 // Was there a module already installed with the same name? 557 if ($this->currentExtensionId) { 558 if (!$this->parent->isOverwrite()) { 559 // Install failed, roll back changes 560 throw new \RuntimeException( 561 Text::sprintf( 562 'JLIB_INSTALLER_ABORT_MOD_INSTALL_ALLREADY_EXISTS', 563 Text::_('JLIB_INSTALLER_' . $this->route), 564 $this->name 565 ) 566 ); 567 } 568 569 // Load the entry and update the manifest_cache 570 $this->extension->load($this->currentExtensionId); 571 572 // Update name 573 $this->extension->name = $this->name; 574 575 // Update namespace 576 $this->extension->namespace = (string) $this->manifest->namespace; 577 578 // Update changelogurl 579 $this->extension->changelogurl = (string) $this->manifest->changelogurl; 580 581 // Update manifest 582 $this->extension->manifest_cache = $this->parent->generateManifestCache(); 583 584 if (!$this->extension->store()) { 585 // Install failed, roll back changes 586 throw new \RuntimeException( 587 Text::sprintf( 588 'JLIB_INSTALLER_ABORT_MOD_ROLLBACK', 589 Text::_('JLIB_INSTALLER_' . $this->route), 590 $this->extension->getError() 591 ) 592 ); 593 } 594 } else { 595 $this->extension->name = $this->name; 596 $this->extension->type = 'module'; 597 $this->extension->element = $this->element; 598 $this->extension->namespace = (string) $this->manifest->namespace; 599 $this->extension->changelogurl = $this->changelogurl; 600 601 // There is no folder for modules 602 $this->extension->folder = ''; 603 $this->extension->enabled = 1; 604 $this->extension->protected = 0; 605 $this->extension->access = $this->clientId == 1 ? 2 : 0; 606 $this->extension->client_id = $this->clientId; 607 $this->extension->params = $this->parent->getParams(); 608 609 // Update the manifest cache for the entry 610 $this->extension->manifest_cache = $this->parent->generateManifestCache(); 611 612 if (!$this->extension->store()) { 613 // Install failed, roll back changes 614 throw new \RuntimeException( 615 Text::sprintf( 616 'JLIB_INSTALLER_ABORT_MOD_ROLLBACK', 617 Text::_('JLIB_INSTALLER_' . $this->route), 618 $this->extension->getError() 619 ) 620 ); 621 } 622 623 // Since we have created a module item, we add it to the installation step stack 624 // so that if we have to rollback the changes we can undo it. 625 $this->parent->pushStep( 626 array( 627 'type' => 'extension', 628 'extension_id' => $this->extension->extension_id, 629 ) 630 ); 631 632 // Create unpublished module 633 $name = preg_replace('#[\*?]#', '', Text::_($this->name)); 634 635 /** @var \Joomla\CMS\Table\Module $module */ 636 $module = Table::getInstance('module'); 637 $module->title = $name; 638 $module->content = ''; 639 $module->module = $this->element; 640 $module->access = '1'; 641 $module->showtitle = '1'; 642 $module->params = ''; 643 $module->client_id = $this->clientId; 644 $module->language = '*'; 645 646 $module->store(); 647 } 648 } 649 650 /** 651 * Custom rollback method 652 * - Roll back the menu item 653 * 654 * @param array $arg Installation step to rollback 655 * 656 * @return boolean True on success 657 * 658 * @since 3.1 659 */ 660 protected function _rollback_menu($arg) 661 { 662 // Get database connector object 663 $db = $this->getDatabase(); 664 665 $moduleId = $arg['id']; 666 667 // Remove the entry from the #__modules_menu table 668 $query = $db->getQuery(true) 669 ->delete($db->quoteName('#__modules_menu')) 670 ->where($db->quoteName('moduleid') . ' = :module_id') 671 ->bind(':module_id', $moduleId, ParameterType::INTEGER); 672 $db->setQuery($query); 673 674 try { 675 return $db->execute(); 676 } catch (\RuntimeException $e) { 677 return false; 678 } 679 } 680 681 /** 682 * Custom rollback method 683 * - Roll back the module item 684 * 685 * @param array $arg Installation step to rollback 686 * 687 * @return boolean True on success 688 * 689 * @since 3.1 690 */ 691 protected function _rollback_module($arg) 692 { 693 // Get database connector object 694 $db = $this->getDatabase(); 695 696 $moduleId = $arg['id']; 697 698 // Remove the entry from the #__modules table 699 $query = $db->getQuery(true) 700 ->delete($db->quoteName('#__modules')) 701 ->where($db->quoteName('id') . ' = :module_id') 702 ->bind(':module_id', $moduleId, ParameterType::INTEGER); 703 $db->setQuery($query); 704 705 try { 706 return $db->execute(); 707 } catch (\RuntimeException $e) { 708 return false; 709 } 710 } 711 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Sep 7 05:41:13 2022 | Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer |