[ 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) 2008 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\Factory; 14 use Joomla\CMS\Filesystem\File; 15 use Joomla\CMS\Filesystem\Folder; 16 use Joomla\CMS\Filter\InputFilter; 17 use Joomla\CMS\Installer\Installer; 18 use Joomla\CMS\Installer\InstallerAdapter; 19 use Joomla\CMS\Installer\InstallerHelper; 20 use Joomla\CMS\Installer\Manifest\PackageManifest; 21 use Joomla\CMS\Language\Text; 22 use Joomla\CMS\Log\Log; 23 use Joomla\CMS\Table\Table; 24 use Joomla\CMS\Table\Update; 25 use Joomla\Database\Exception\ExecutionFailureException; 26 use Joomla\Database\ParameterType; 27 use Joomla\Event\Event; 28 29 // phpcs:disable PSR1.Files.SideEffects 30 \defined('JPATH_PLATFORM') or die; 31 // phpcs:enable PSR1.Files.SideEffects 32 33 /** 34 * Package installer 35 * 36 * @since 3.1 37 */ 38 class PackageAdapter extends InstallerAdapter 39 { 40 /** 41 * An array of extension IDs for each installed extension 42 * 43 * @var array 44 * @since 3.7.0 45 */ 46 protected $installedIds = array(); 47 48 /** 49 * The results of each installed extensions 50 * 51 * @var array 52 * @since 3.1 53 */ 54 protected $results = array(); 55 56 /** 57 * Flag if the adapter supports discover installs 58 * 59 * Adapters should override this and set to false if discover install is unsupported 60 * 61 * @var boolean 62 * @since 3.4 63 */ 64 protected $supportsDiscoverInstall = false; 65 66 /** 67 * Method to check if the extension is present in the filesystem, flags the route as update if so 68 * 69 * @return void 70 * 71 * @since 3.4 72 * @throws \RuntimeException 73 */ 74 protected function checkExtensionInFilesystem() 75 { 76 // If the package manifest already exists, then we will assume that the package is already installed. 77 if (file_exists(JPATH_MANIFESTS . '/packages/' . basename($this->parent->getPath('manifest')))) { 78 // Look for an update function or update tag 79 $updateElement = $this->manifest->update; 80 81 // Upgrade manually set or update function available or update tag detected 82 if ( 83 $updateElement || $this->parent->isUpgrade() 84 || ($this->parent->manifestClass && method_exists($this->parent->manifestClass, 'update')) 85 ) { 86 // Force this one 87 $this->parent->setOverwrite(true); 88 $this->parent->setUpgrade(true); 89 90 if ($this->currentExtensionId) { 91 // If there is a matching extension mark this as an update 92 $this->setRoute('update'); 93 } 94 } elseif (!$this->parent->isOverwrite()) { 95 // We didn't have overwrite set, find an update function or find an update tag so lets call it safe 96 throw new \RuntimeException( 97 Text::sprintf( 98 'JLIB_INSTALLER_ABORT_DIRECTORY', 99 Text::_('JLIB_INSTALLER_' . $this->route), 100 $this->type, 101 $this->parent->getPath('extension_root') 102 ) 103 ); 104 } 105 } 106 } 107 108 /** 109 * Method to copy the extension's base files from the `<files>` tag(s) and the manifest file 110 * 111 * @return void 112 * 113 * @since 3.4 114 * @throws \RuntimeException 115 */ 116 protected function copyBaseFiles() 117 { 118 $folder = (string) $this->getManifest()->files->attributes()->folder; 119 $source = $this->parent->getPath('source'); 120 121 if ($folder) { 122 $source .= '/' . $folder; 123 } 124 125 // Install all necessary files 126 if (!\count($this->getManifest()->files->children())) { 127 throw new \RuntimeException( 128 Text::sprintf( 129 'JLIB_INSTALLER_ABORT_PACK_INSTALL_NO_FILES', 130 Text::_('JLIB_INSTALLER_' . strtoupper($this->route)) 131 ) 132 ); 133 } 134 135 $dispatcher = Factory::getApplication()->getDispatcher(); 136 137 // Add a callback for the `onExtensionAfterInstall` event so we can receive the installed extension ID 138 if (!$dispatcher->hasListener([$this, 'onExtensionAfterInstall'], 'onExtensionAfterInstall')) { 139 $dispatcher->addListener('onExtensionAfterInstall', [$this, 'onExtensionAfterInstall']); 140 } 141 142 foreach ($this->getManifest()->files->children() as $child) { 143 $file = $source . '/' . (string) $child; 144 145 if (is_dir($file)) { 146 // If it's actually a directory then fill it up 147 $package = array(); 148 $package['dir'] = $file; 149 $package['type'] = InstallerHelper::detectType($file); 150 } else { 151 // If it's an archive 152 $package = InstallerHelper::unpack($file); 153 } 154 155 $tmpInstaller = new Installer(); 156 $tmpInstaller->setDatabase($this->getDatabase()); 157 $installResult = $tmpInstaller->install($package['dir']); 158 159 if (!$installResult) { 160 throw new \RuntimeException( 161 Text::sprintf( 162 'JLIB_INSTALLER_ABORT_PACK_INSTALL_ERROR_EXTENSION', 163 Text::_('JLIB_INSTALLER_' . strtoupper($this->route)), 164 basename($file) 165 ) 166 ); 167 } 168 169 $this->results[] = array( 170 'name' => (string) $tmpInstaller->manifest->name, 171 'result' => $installResult, 172 ); 173 } 174 } 175 176 /** 177 * Method to create the extension root path if necessary 178 * 179 * @return void 180 * 181 * @since 3.4 182 * @throws \RuntimeException 183 */ 184 protected function createExtensionRoot() 185 { 186 /* 187 * For packages, we only need the extension root if copying manifest files; this step will be handled 188 * at that point if necessary 189 */ 190 } 191 192 /** 193 * Method to finalise the installation processing 194 * 195 * @return void 196 * 197 * @since 3.4 198 * @throws \RuntimeException 199 */ 200 protected function finaliseInstall() 201 { 202 // Clobber any possible pending updates 203 /** @var Update $update */ 204 $update = Table::getInstance('update'); 205 $uid = $update->find( 206 array( 207 'element' => $this->element, 208 'type' => $this->type, 209 ) 210 ); 211 212 if ($uid) { 213 $update->delete($uid); 214 } 215 216 // Set the package ID for each of the installed extensions to track the relationship 217 if (!empty($this->installedIds)) { 218 $db = $this->getDatabase(); 219 $query = $db->getQuery(true) 220 ->update($db->quoteName('#__extensions')) 221 ->set($db->quoteName('package_id') . ' = :id') 222 ->whereIn($db->quoteName('extension_id'), $this->installedIds) 223 ->bind(':id', $this->extension->extension_id, ParameterType::INTEGER); 224 225 try { 226 $db->setQuery($query)->execute(); 227 } catch (ExecutionFailureException $e) { 228 Log::add(Text::_('JLIB_INSTALLER_ERROR_PACK_SETTING_PACKAGE_ID'), Log::WARNING, 'jerror'); 229 } 230 } 231 232 // Lastly, we will copy the manifest file to its appropriate place. 233 $manifest = array(); 234 $manifest['src'] = $this->parent->getPath('manifest'); 235 $manifest['dest'] = JPATH_MANIFESTS . '/packages/' . basename($this->parent->getPath('manifest')); 236 237 if (!$this->parent->copyFiles(array($manifest), true)) { 238 // Install failed, rollback changes 239 throw new \RuntimeException( 240 Text::sprintf( 241 'JLIB_INSTALLER_ABORT_COPY_SETUP', 242 Text::_('JLIB_INSTALLER_' . strtoupper($this->route)) 243 ) 244 ); 245 } 246 247 // If there is a manifest script, let's copy it. 248 if ($this->manifest_script) { 249 // First, we have to create a folder for the script if one isn't present 250 if (!file_exists($this->parent->getPath('extension_root'))) { 251 if (!Folder::create($this->parent->getPath('extension_root'))) { 252 throw new \RuntimeException( 253 Text::sprintf( 254 'JLIB_INSTALLER_ABORT_CREATE_DIRECTORY', 255 Text::_('JLIB_INSTALLER_' . $this->route), 256 $this->parent->getPath('extension_root') 257 ) 258 ); 259 } 260 261 /* 262 * Since we created the extension directory and will want to remove it if 263 * we have to roll back the installation, let's add it to the 264 * installation step stack 265 */ 266 267 $this->parent->pushStep( 268 array( 269 'type' => 'folder', 270 'path' => $this->parent->getPath('extension_root'), 271 ) 272 ); 273 } 274 275 $path['src'] = $this->parent->getPath('source') . '/' . $this->manifest_script; 276 $path['dest'] = $this->parent->getPath('extension_root') . '/' . $this->manifest_script; 277 278 if ($this->parent->isOverwrite() || !file_exists($path['dest'])) { 279 if (!$this->parent->copyFiles(array($path))) { 280 // Install failed, rollback changes 281 throw new \RuntimeException( 282 Text::sprintf( 283 'JLIB_INSTALLER_ABORT_MANIFEST', 284 Text::_('JLIB_INSTALLER_' . strtoupper($this->route)) 285 ) 286 ); 287 } 288 } 289 } 290 } 291 292 /** 293 * Method to finalise the uninstallation processing 294 * 295 * @return boolean 296 * 297 * @since 4.0.0 298 * @throws \RuntimeException 299 */ 300 protected function finaliseUninstall(): bool 301 { 302 $db = $this->getDatabase(); 303 304 // Remove the schema version 305 $query = $db->getQuery(true) 306 ->delete($db->quoteName('#__schemas')) 307 ->where($db->quoteName('extension_id') . ' = :extension_id') 308 ->bind(':extension_id', $this->extension->extension_id, ParameterType::INTEGER); 309 $db->setQuery($query); 310 $db->execute(); 311 312 // Clobber any possible pending updates 313 $update = Table::getInstance('update'); 314 $uid = $update->find( 315 [ 316 'element' => $this->extension->element, 317 'type' => $this->type, 318 ] 319 ); 320 321 if ($uid) { 322 $update->delete($uid); 323 } 324 325 File::delete(JPATH_MANIFESTS . '/packages/' . $this->extension->element . '.xml'); 326 327 $folder = $this->parent->getPath('extension_root'); 328 329 if (Folder::exists($folder)) { 330 Folder::delete($folder); 331 } 332 333 $this->extension->delete(); 334 335 return true; 336 } 337 338 /** 339 * Get the filtered extension element from the manifest 340 * 341 * @param string $element Optional element name to be converted 342 * 343 * @return string The filtered element 344 * 345 * @since 3.4 346 */ 347 public function getElement($element = null) 348 { 349 if (!$element) { 350 // Ensure the element is a string 351 $element = (string) $this->getManifest()->packagename; 352 353 // Filter the name for illegal characters 354 $element = 'pkg_' . InputFilter::getInstance()->clean($element, 'cmd'); 355 } 356 357 return $element; 358 } 359 360 /** 361 * Load language from a path 362 * 363 * @param string $path The path of the language. 364 * 365 * @return void 366 * 367 * @since 3.1 368 */ 369 public function loadLanguage($path) 370 { 371 $this->doLoadLanguage($this->getElement(), $path); 372 } 373 374 /** 375 * Handler for the `onExtensionAfterInstall` event 376 * 377 * @param Event $event The event 378 * 379 * @return void 380 * 381 * @since 3.7.0 382 */ 383 public function onExtensionAfterInstall(Event $event) 384 { 385 if ($event->getArgument('eid', false) !== false) { 386 $this->installedIds[] = $event->getArgument('eid'); 387 } 388 } 389 390 /** 391 * Method to parse optional tags in the manifest 392 * 393 * @return void 394 * 395 * @since 3.4 396 */ 397 protected function parseOptionalTags() 398 { 399 $this->parent->parseLanguages($this->getManifest()->languages); 400 } 401 402 /** 403 * Removes this extension's files 404 * 405 * @return void 406 * 407 * @since 4.0.0 408 * @throws \RuntimeException 409 */ 410 protected function removeExtensionFiles() 411 { 412 $manifest = new PackageManifest(JPATH_MANIFESTS . '/packages/' . $this->extension->element . '.xml'); 413 $error = false; 414 415 foreach ($manifest->filelist as $extension) { 416 $tmpInstaller = new Installer(); 417 $tmpInstaller->setDatabase($this->getDatabase()); 418 $tmpInstaller->setPackageUninstall(true); 419 420 $id = $this->_getExtensionId($extension->type, $extension->id, $extension->client, $extension->group); 421 422 if ($id) { 423 if (!$tmpInstaller->uninstall($extension->type, $id)) { 424 $error = true; 425 Log::add(Text::sprintf('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_NOT_PROPER', basename($extension->filename)), Log::WARNING, 'jerror'); 426 } 427 } else { 428 Log::add(Text::_('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_UNKNOWN_EXTENSION'), Log::WARNING, 'jerror'); 429 } 430 } 431 432 // Remove any language files 433 $this->parent->removeFiles($this->getManifest()->languages); 434 435 // Clean up manifest file after we're done if there were no errors 436 if ($error) { 437 throw new \RuntimeException(Text::_('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_MANIFEST_NOT_REMOVED')); 438 } 439 } 440 441 /** 442 * Method to do any prechecks and setup the install paths for the extension 443 * 444 * @return void 445 * 446 * @since 3.4 447 * @throws \RuntimeException 448 */ 449 protected function setupInstallPaths() 450 { 451 $packagepath = (string) $this->getManifest()->packagename; 452 453 if (empty($packagepath)) { 454 throw new \RuntimeException( 455 Text::sprintf( 456 'JLIB_INSTALLER_ABORT_PACK_INSTALL_NO_PACK', 457 Text::_('JLIB_INSTALLER_' . strtoupper($this->route)) 458 ) 459 ); 460 } 461 462 $this->parent->setPath('extension_root', JPATH_MANIFESTS . '/packages/' . $packagepath); 463 } 464 465 /** 466 * Method to do any prechecks and setup the uninstall job 467 * 468 * @return void 469 * 470 * @since 4.0.0 471 */ 472 protected function setupUninstall() 473 { 474 $manifestFile = JPATH_MANIFESTS . '/packages/' . $this->extension->element . '.xml'; 475 $manifest = new PackageManifest($manifestFile); 476 477 // Set the package root path 478 $this->parent->setPath('extension_root', JPATH_MANIFESTS . '/packages/' . $manifest->packagename); 479 480 // Set the source path for compatibility with the API 481 $this->parent->setPath('source', $this->parent->getPath('extension_root')); 482 483 // Because packages may not have their own folders we cannot use the standard method of finding an installation manifest 484 if (!file_exists($manifestFile)) { 485 throw new \RuntimeException(Text::_('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_MISSINGMANIFEST')); 486 } 487 488 $xml = simplexml_load_file($manifestFile); 489 490 if (!$xml) { 491 throw new \RuntimeException(Text::_('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_LOAD_MANIFEST')); 492 } 493 494 // Check for a valid XML root tag. 495 if ($xml->getName() !== 'extension') { 496 throw new \RuntimeException(Text::_('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_INVALID_MANIFEST')); 497 } 498 499 $this->setManifest($xml); 500 501 // Attempt to load the language file; might have uninstall strings 502 $this->loadLanguage(JPATH_SITE); 503 } 504 505 /** 506 * Method to store the extension to the database 507 * 508 * @return void 509 * 510 * @since 3.4 511 * @throws \RuntimeException 512 */ 513 protected function storeExtension() 514 { 515 if ($this->currentExtensionId) { 516 if (!$this->parent->isOverwrite()) { 517 // Install failed, roll back changes 518 throw new \RuntimeException( 519 Text::sprintf( 520 'JLIB_INSTALLER_ABORT_ALREADY_EXISTS', 521 Text::_('JLIB_INSTALLER_' . $this->route), 522 $this->name 523 ) 524 ); 525 } 526 527 $this->extension->load($this->currentExtensionId); 528 $this->extension->name = $this->name; 529 } else { 530 $this->extension->name = $this->name; 531 $this->extension->type = 'package'; 532 $this->extension->element = $this->element; 533 $this->extension->changelogurl = $this->changelogurl; 534 535 // There is no folder for packages 536 $this->extension->folder = ''; 537 $this->extension->enabled = 1; 538 $this->extension->protected = 0; 539 $this->extension->access = 1; 540 $this->extension->client_id = 0; 541 $this->extension->params = $this->parent->getParams(); 542 } 543 544 // Update the manifest cache for the entry 545 $this->extension->manifest_cache = $this->parent->generateManifestCache(); 546 547 if (!$this->extension->store()) { 548 // Install failed, roll back changes 549 throw new \RuntimeException( 550 Text::sprintf( 551 'JLIB_INSTALLER_ABORT_PACK_INSTALL_ROLLBACK', 552 $this->extension->getError() 553 ) 554 ); 555 } 556 557 // Since we have created a package item, we add it to the installation step stack 558 // so that if we have to rollback the changes we can undo it. 559 $this->parent->pushStep(array('type' => 'extension', 'id' => $this->extension->extension_id)); 560 } 561 562 /** 563 * Executes a custom install script method 564 * 565 * @param string $method The install method to execute 566 * 567 * @return boolean True on success 568 * 569 * @since 3.4 570 */ 571 protected function triggerManifestScript($method) 572 { 573 ob_start(); 574 ob_implicit_flush(false); 575 576 if ($this->parent->manifestClass && method_exists($this->parent->manifestClass, $method)) { 577 switch ($method) { 578 // The preflight method takes the route as a param 579 case 'preflight': 580 if ($this->parent->manifestClass->$method($this->route, $this) === false) { 581 // The script failed, rollback changes 582 throw new \RuntimeException( 583 Text::sprintf( 584 'JLIB_INSTALLER_ABORT_INSTALL_CUSTOM_INSTALL_FAILURE', 585 Text::_('JLIB_INSTALLER_' . $this->route) 586 ) 587 ); 588 } 589 590 break; 591 592 // The postflight method takes the route and a results array as params 593 case 'postflight': 594 $this->parent->manifestClass->$method($this->route, $this, $this->results); 595 596 break; 597 598 // The install, uninstall, and update methods only pass this object as a param 599 case 'install': 600 case 'uninstall': 601 case 'update': 602 if ($this->parent->manifestClass->$method($this) === false) { 603 if ($method !== 'uninstall') { 604 // The script failed, rollback changes 605 throw new \RuntimeException( 606 Text::sprintf( 607 'JLIB_INSTALLER_ABORT_INSTALL_CUSTOM_INSTALL_FAILURE', 608 Text::_('JLIB_INSTALLER_' . $this->route) 609 ) 610 ); 611 } 612 } 613 614 break; 615 } 616 } 617 618 // Append to the message object 619 $this->extensionMessage .= ob_get_clean(); 620 621 // If in postflight or uninstall, set the message for display 622 if (($method === 'uninstall' || $method === 'postflight') && $this->extensionMessage !== '') { 623 $this->parent->set('extension_message', $this->extensionMessage); 624 } 625 626 return true; 627 } 628 629 /** 630 * Gets the extension id. 631 * 632 * @param string $type The extension type. 633 * @param string $id The name of the extension (the element field). 634 * @param integer $client The application id (0: Joomla CMS site; 1: Joomla CMS administrator). 635 * @param string $group The extension group (mainly for plugins). 636 * 637 * @return integer 638 * 639 * @since 3.1 640 */ 641 protected function _getExtensionId($type, $id, $client, $group) 642 { 643 $db = $this->getDatabase(); 644 645 $query = $db->getQuery(true) 646 ->select($db->quoteName('extension_id')) 647 ->from($db->quoteName('#__extensions')) 648 ->where( 649 [ 650 $db->quoteName('type') . ' = :type', 651 $db->quoteName('element') . ' = :element', 652 ] 653 ) 654 ->bind(':type', $type) 655 ->bind(':element', $id); 656 657 switch ($type) { 658 case 'plugin': 659 // Plugins have a folder but not a client 660 $query->where('folder = :folder') 661 ->bind(':folder', $group); 662 663 break; 664 665 case 'library': 666 case 'package': 667 case 'component': 668 // Components, packages and libraries don't have a folder or client. 669 // Included for completeness. 670 break; 671 672 case 'language': 673 case 'module': 674 case 'template': 675 // Languages, modules and templates have a client but not a folder 676 $clientId = ApplicationHelper::getClientInfo($client, true)->id; 677 678 $query->where('client_id = :client_id') 679 ->bind(':client_id', $clientId, ParameterType::INTEGER); 680 681 break; 682 } 683 684 $db->setQuery($query); 685 686 // Note: For templates, libraries and packages their unique name is their key. 687 // This means they come out the same way they came in. 688 689 return $db->loadResult(); 690 } 691 692 /** 693 * Refreshes the extension table cache 694 * 695 * @return boolean Result of operation, true if updated, false on failure 696 * 697 * @since 3.1 698 */ 699 public function refreshManifestCache() 700 { 701 // Need to find to find where the XML file is since we don't store this normally 702 $manifestPath = JPATH_MANIFESTS . '/packages/' . $this->parent->extension->element . '.xml'; 703 $this->parent->manifest = $this->parent->isManifest($manifestPath); 704 $this->parent->setPath('manifest', $manifestPath); 705 706 $manifest_details = Installer::parseXMLInstallFile($this->parent->getPath('manifest')); 707 $this->parent->extension->manifest_cache = json_encode($manifest_details); 708 $this->parent->extension->name = $manifest_details['name']; 709 710 try { 711 return $this->parent->extension->store(); 712 } catch (\RuntimeException $e) { 713 Log::add(Text::_('JLIB_INSTALLER_ERROR_PACK_REFRESH_MANIFEST_CACHE'), Log::WARNING, 'jerror'); 714 715 return false; 716 } 717 } 718 }
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 |