[ 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) 2014 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; 11 12 use Joomla\CMS\Factory; 13 use Joomla\CMS\Filesystem\Folder; 14 use Joomla\CMS\Filter\InputFilter; 15 use Joomla\CMS\Installer\Manifest\PackageManifest; 16 use Joomla\CMS\Language\Text; 17 use Joomla\CMS\Log\Log; 18 use Joomla\CMS\Table\Extension; 19 use Joomla\CMS\Table\Table; 20 use Joomla\CMS\Table\TableInterface; 21 use Joomla\Database\DatabaseAwareInterface; 22 use Joomla\Database\DatabaseAwareTrait; 23 use Joomla\Database\DatabaseDriver; 24 use Joomla\DI\Container; 25 use Joomla\DI\ContainerAwareInterface; 26 use Joomla\DI\ContainerAwareTrait; 27 use Joomla\DI\Exception\ContainerNotFoundException; 28 use Joomla\DI\ServiceProviderInterface; 29 30 // phpcs:disable PSR1.Files.SideEffects 31 \defined('JPATH_PLATFORM') or die; 32 // phpcs:enable PSR1.Files.SideEffects 33 34 /** 35 * Abstract adapter for the installer. 36 * 37 * @since 3.4 38 */ 39 abstract class InstallerAdapter implements ContainerAwareInterface, DatabaseAwareInterface 40 { 41 use ContainerAwareTrait; 42 use DatabaseAwareTrait; 43 44 /** 45 * Changelog URL of extensions 46 * 47 * @var string 48 * @since 4.0.0 49 * */ 50 protected $changelogurl = null; 51 52 /** 53 * ID for the currently installed extension if present 54 * 55 * @var integer 56 * @since 3.4 57 */ 58 protected $currentExtensionId = null; 59 60 /** 61 * The unique identifier for the extension (e.g. mod_login) 62 * 63 * @var string 64 * @since 3.4 65 * */ 66 protected $element = null; 67 68 /** 69 * Extension object. 70 * 71 * @var Extension 72 * @since 3.4 73 * */ 74 protected $extension = null; 75 76 /** 77 * Messages rendered by custom scripts 78 * 79 * @var string 80 * @since 3.4 81 */ 82 protected $extensionMessage = ''; 83 84 /** 85 * Copy of the XML manifest file. 86 * 87 * Making this object public allows extensions to customize the manifest in custom scripts. 88 * 89 * @var \SimpleXMLElement 90 * @since 3.4 91 */ 92 public $manifest = null; 93 94 /** 95 * A path to the PHP file that the scriptfile declaration in the manifest refers to. 96 * 97 * @var string 98 * @since 3.4 99 */ 100 protected $manifest_script = null; 101 102 /** 103 * Name of the extension 104 * 105 * @var string 106 * @since 3.4 107 */ 108 protected $name = null; 109 110 /** 111 * Installer used with this adapter 112 * 113 * @var Installer 114 * @since 4.0.0 115 */ 116 protected $parent = null; 117 118 /** 119 * Install function routing 120 * 121 * @var string 122 * @since 3.4 123 */ 124 protected $route = 'install'; 125 126 /** 127 * Flag if the adapter supports discover installs 128 * 129 * Adapters should override this and set to false if discover install is unsupported 130 * 131 * @var boolean 132 * @since 3.4 133 */ 134 protected $supportsDiscoverInstall = true; 135 136 /** 137 * The type of adapter in use 138 * 139 * @var string 140 * @since 3.4 141 */ 142 protected $type; 143 144 /** 145 * Constructor 146 * 147 * @param Installer $parent Parent object 148 * @param DatabaseDriver $db Database object 149 * @param array $options Configuration Options 150 * 151 * @since 3.4 152 */ 153 public function __construct(Installer $parent, DatabaseDriver $db, array $options = array()) 154 { 155 $this->parent = $parent; 156 $this->setDatabase($db); 157 158 foreach ($options as $key => $value) { 159 if (property_exists($this, $key)) { 160 $this->$key = $value; 161 } 162 } 163 164 // Get a generic TableExtension instance for use if not already loaded 165 if (!($this->extension instanceof TableInterface)) { 166 $this->extension = Table::getInstance('extension'); 167 } 168 169 // Sanity check, make sure the type is set by taking the adapter name from the class name 170 if (!$this->type) { 171 // This assumes the adapter short class name in its namespace is `<foo>Adapter`, replace this logic in subclasses if needed 172 $reflection = new \ReflectionClass(\get_called_class()); 173 $this->type = str_replace('Adapter', '', $reflection->getShortName()); 174 } 175 176 // Extension type is stored as lowercase in the database 177 $this->type = strtolower($this->type); 178 } 179 180 /** 181 * Check if a package extension allows its child extensions to be uninstalled individually 182 * 183 * @param integer $packageId The extension ID of the package to check 184 * 185 * @return boolean 186 * 187 * @since 3.7.0 188 * @note This method defaults to true to emulate the behavior of 3.6 and earlier which did not support this lookup 189 */ 190 protected function canUninstallPackageChild($packageId) 191 { 192 $package = Table::getInstance('extension'); 193 194 // If we can't load this package ID, we have a corrupt database 195 if (!$package->load((int) $packageId)) { 196 return true; 197 } 198 199 $manifestFile = JPATH_MANIFESTS . '/packages/' . $package->element . '.xml'; 200 201 $xml = $this->parent->isManifest($manifestFile); 202 203 // If the manifest doesn't exist, we've got some major issues 204 if (!$xml) { 205 return true; 206 } 207 208 $manifest = new PackageManifest($manifestFile); 209 210 return $manifest->blockChildUninstall === false; 211 } 212 213 /** 214 * Method to check if the extension is already present in the database 215 * 216 * @return void 217 * 218 * @since 3.4 219 * @throws \RuntimeException 220 */ 221 protected function checkExistingExtension() 222 { 223 try { 224 $this->currentExtensionId = $this->extension->find( 225 array('element' => $this->element, 'type' => $this->type) 226 ); 227 228 // If it does exist, load it 229 if ($this->currentExtensionId) { 230 $this->extension->load(array('element' => $this->element, 'type' => $this->type)); 231 } 232 } catch (\RuntimeException $e) { 233 // Install failed, roll back changes 234 throw new \RuntimeException( 235 Text::sprintf( 236 'JLIB_INSTALLER_ABORT_ROLLBACK', 237 Text::_('JLIB_INSTALLER_' . $this->route), 238 $e->getMessage() 239 ), 240 $e->getCode(), 241 $e 242 ); 243 } 244 } 245 246 /** 247 * Method to check if the extension is present in the filesystem, flags the route as update if so 248 * 249 * @return void 250 * 251 * @since 3.4 252 * @throws \RuntimeException 253 */ 254 protected function checkExtensionInFilesystem() 255 { 256 if (file_exists($this->parent->getPath('extension_root')) && (!$this->parent->isOverwrite() || $this->parent->isUpgrade())) { 257 // Look for an update function or update tag 258 $updateElement = $this->getManifest()->update; 259 260 // Upgrade manually set or update function available or update tag detected 261 if ( 262 $updateElement || $this->parent->isUpgrade() 263 || ($this->parent->manifestClass && method_exists($this->parent->manifestClass, 'update')) 264 ) { 265 // Force this one 266 $this->parent->setOverwrite(true); 267 $this->parent->setUpgrade(true); 268 269 if ($this->currentExtensionId) { 270 // If there is a matching extension mark this as an update 271 $this->setRoute('update'); 272 } 273 } elseif (!$this->parent->isOverwrite()) { 274 // We didn't have overwrite set, find an update function or find an update tag so lets call it safe 275 throw new \RuntimeException( 276 Text::sprintf( 277 'JLIB_INSTALLER_ABORT_DIRECTORY', 278 Text::_('JLIB_INSTALLER_' . $this->route), 279 $this->type, 280 $this->parent->getPath('extension_root') 281 ) 282 ); 283 } 284 } 285 } 286 287 /** 288 * Method to copy the extension's base files from the `<files>` tag(s) and the manifest file 289 * 290 * @return void 291 * 292 * @since 3.4 293 * @throws \RuntimeException 294 */ 295 abstract protected function copyBaseFiles(); 296 297 /** 298 * Method to create the extension root path if necessary 299 * 300 * @return void 301 * 302 * @since 3.4 303 * @throws \RuntimeException 304 */ 305 protected function createExtensionRoot() 306 { 307 // If the extension directory does not exist, lets create it 308 $created = false; 309 310 if (!file_exists($this->parent->getPath('extension_root'))) { 311 if (!$created = Folder::create($this->parent->getPath('extension_root'))) { 312 throw new \RuntimeException( 313 Text::sprintf( 314 'JLIB_INSTALLER_ABORT_CREATE_DIRECTORY', 315 Text::_('JLIB_INSTALLER_' . $this->route), 316 $this->parent->getPath('extension_root') 317 ) 318 ); 319 } 320 } 321 322 /* 323 * Since we created the extension directory and will want to remove it if 324 * we have to roll back the installation, let's add it to the 325 * installation step stack 326 */ 327 328 if ($created) { 329 $this->parent->pushStep( 330 array( 331 'type' => 'folder', 332 'path' => $this->parent->getPath('extension_root'), 333 ) 334 ); 335 } 336 } 337 338 /** 339 * Generic discover_install method for extensions 340 * 341 * @return boolean True on success 342 * 343 * @since 3.4 344 */ 345 public function discover_install() 346 { 347 // Get the extension's description 348 $description = (string) $this->getManifest()->description; 349 350 if ($description) { 351 $this->parent->message = Text::_($description); 352 } else { 353 $this->parent->message = ''; 354 } 355 356 // Set the extension's name and element 357 $this->name = $this->getName(); 358 $this->element = $this->getElement(); 359 360 /* 361 * --------------------------------------------------------------------------------------------- 362 * Extension Precheck and Setup Section 363 * --------------------------------------------------------------------------------------------- 364 */ 365 366 // Setup the install paths and perform other prechecks as necessary 367 try { 368 $this->setupInstallPaths(); 369 } catch (\RuntimeException $e) { 370 // Install failed, roll back changes 371 $this->parent->abort($e->getMessage()); 372 373 return false; 374 } 375 376 /* 377 * --------------------------------------------------------------------------------------------- 378 * Installer Trigger Loading 379 * --------------------------------------------------------------------------------------------- 380 */ 381 382 $this->setupScriptfile(); 383 384 try { 385 $this->triggerManifestScript('preflight'); 386 } catch (\RuntimeException $e) { 387 // Install failed, roll back changes 388 $this->parent->abort($e->getMessage()); 389 390 return false; 391 } 392 393 /* 394 * --------------------------------------------------------------------------------------------- 395 * Database Processing Section 396 * --------------------------------------------------------------------------------------------- 397 */ 398 399 try { 400 $this->storeExtension(); 401 } catch (\RuntimeException $e) { 402 // Install failed, roll back changes 403 $this->parent->abort($e->getMessage()); 404 405 return false; 406 } 407 408 try { 409 $this->parseQueries(); 410 } catch (\RuntimeException $e) { 411 // Install failed, roll back changes 412 $this->parent->abort($e->getMessage()); 413 414 return false; 415 } 416 417 // Run the custom install method 418 try { 419 $this->triggerManifestScript('install'); 420 } catch (\RuntimeException $e) { 421 // Install failed, roll back changes 422 $this->parent->abort($e->getMessage()); 423 424 return false; 425 } 426 427 /* 428 * --------------------------------------------------------------------------------------------- 429 * Finalization and Cleanup Section 430 * --------------------------------------------------------------------------------------------- 431 */ 432 433 try { 434 $this->finaliseInstall(); 435 } catch (\RuntimeException $e) { 436 // Install failed, roll back changes 437 $this->parent->abort($e->getMessage()); 438 439 return false; 440 } 441 442 // And now we run the postflight 443 try { 444 $this->triggerManifestScript('postflight'); 445 } catch (\RuntimeException $e) { 446 // Install failed, roll back changes 447 $this->parent->abort($e->getMessage()); 448 449 return false; 450 } 451 452 return $this->extension->extension_id; 453 } 454 455 /** 456 * Method to handle database transactions for the installer 457 * 458 * @return boolean True on success 459 * 460 * @since 3.4 461 * @throws \RuntimeException 462 */ 463 protected function doDatabaseTransactions() 464 { 465 $route = $this->route === 'discover_install' ? 'install' : $this->route; 466 467 // Let's run the install queries for the component 468 if (isset($this->getManifest()->{$route}->sql)) { 469 $result = $this->parent->parseSQLFiles($this->getManifest()->{$route}->sql); 470 471 if ($result === false) { 472 // Only rollback if installing 473 if ($route === 'install') { 474 throw new \RuntimeException(Text::_('JLIB_INSTALLER_ABORT_INSTALL_ABORTED')); 475 } 476 477 return false; 478 } 479 480 // If installing with success and there is an uninstall script, add an installer rollback step to rollback if needed 481 if ($route === 'install' && isset($this->getManifest()->uninstall->sql)) { 482 $this->parent->pushStep(array('type' => 'query', 'script' => $this->getManifest()->uninstall->sql)); 483 } 484 } 485 486 return true; 487 } 488 489 /** 490 * Load language files 491 * 492 * @param string $extension The name of the extension 493 * @param string $source Path to the extension 494 * @param string $base Base path for the extension language 495 * 496 * @return void 497 * 498 * @since 3.4 499 */ 500 protected function doLoadLanguage($extension, $source, $base = JPATH_ADMINISTRATOR) 501 { 502 $lang = Factory::getLanguage(); 503 $lang->load($extension . '.sys', $source) || $lang->load($extension . '.sys', $base); 504 } 505 506 /** 507 * Method to finalise the installation processing 508 * 509 * @return void 510 * 511 * @since 4.0.0 512 * @throws \RuntimeException 513 */ 514 abstract protected function finaliseInstall(); 515 516 /** 517 * Method to finalise the uninstallation processing 518 * 519 * @return boolean 520 * 521 * @since 4.0.0 522 * @throws \RuntimeException 523 */ 524 abstract protected function finaliseUninstall(): bool; 525 526 /** 527 * Checks if the adapter supports discover_install 528 * 529 * @return boolean 530 * 531 * @since 3.4 532 */ 533 public function getDiscoverInstallSupported() 534 { 535 return $this->supportsDiscoverInstall; 536 } 537 538 /** 539 * Get the filtered extension element from the manifest 540 * 541 * @param string $element Optional element name to be converted 542 * 543 * @return string The filtered element 544 * 545 * @since 3.4 546 */ 547 public function getElement($element = null) 548 { 549 if (!$element) { 550 // Ensure the element is a string 551 $element = (string) $this->getManifest()->element; 552 } 553 554 if (!$element) { 555 $element = $this->getName(); 556 } 557 558 // Filter the name for illegal characters 559 return strtolower(InputFilter::getInstance()->clean($element, 'cmd')); 560 } 561 562 /** 563 * Get the manifest object. 564 * 565 * @return \SimpleXMLElement Manifest object 566 * 567 * @since 3.4 568 */ 569 public function getManifest() 570 { 571 return $this->manifest; 572 } 573 574 /** 575 * Get the filtered component name from the manifest 576 * 577 * @return string The filtered name 578 * 579 * @since 3.4 580 */ 581 public function getName() 582 { 583 // Ensure the name is a string 584 $name = (string) $this->getManifest()->name; 585 586 // Filter the name for illegal characters 587 $name = InputFilter::getInstance()->clean($name, 'string'); 588 589 return $name; 590 } 591 592 /** 593 * Retrieves the parent installer 594 * 595 * @return Installer 596 * 597 * @since 4.0.0 598 */ 599 public function getParent() 600 { 601 return $this->parent; 602 } 603 604 /** 605 * Get the install route being followed 606 * 607 * @return string The install route 608 * 609 * @since 3.4 610 */ 611 public function getRoute() 612 { 613 return $this->route; 614 } 615 616 /** 617 * Get the class name for the install adapter script. 618 * 619 * @return string The class name. 620 * 621 * @since 3.4 622 */ 623 protected function getScriptClassName() 624 { 625 // Support element names like 'en-GB' 626 $className = InputFilter::getInstance()->clean($this->element, 'cmd') . 'InstallerScript'; 627 628 // Cannot have - in class names 629 $className = str_replace('-', '', $className); 630 631 return $className; 632 } 633 634 /** 635 * Generic install method for extensions 636 * 637 * @return boolean|integer The extension ID on success, boolean false on failure 638 * 639 * @since 3.4 640 */ 641 public function install() 642 { 643 // Get the extension's description 644 $description = (string) $this->getManifest()->description; 645 $this->parent->message = ''; 646 647 if ($description) { 648 $this->parent->message = Text::_($description); 649 } 650 651 // Set the extension's name and element 652 $this->name = $this->getName(); 653 $this->element = $this->getElement(); 654 $this->changelogurl = (string) $this->getManifest()->changelogurl; 655 656 /* 657 * --------------------------------------------------------------------------------------------- 658 * Extension Precheck and Setup Section 659 * --------------------------------------------------------------------------------------------- 660 */ 661 662 // Setup the install paths and perform other prechecks as necessary 663 try { 664 $this->setupInstallPaths(); 665 } catch (\RuntimeException $e) { 666 // Install failed, roll back changes 667 $this->parent->abort($e->getMessage()); 668 669 return false; 670 } 671 672 // Check to see if an extension by the same name is already installed. 673 try { 674 $this->checkExistingExtension(); 675 } catch (\RuntimeException $e) { 676 // Install failed, roll back changes 677 $this->parent->abort($e->getMessage()); 678 679 return false; 680 } 681 682 // Check if the extension is present in the filesystem 683 try { 684 $this->checkExtensionInFilesystem(); 685 } catch (\RuntimeException $e) { 686 // Install failed, roll back changes 687 $this->parent->abort($e->getMessage()); 688 689 return false; 690 } 691 692 // If we are on the update route, run any custom setup routines 693 if ($this->route === 'update') { 694 try { 695 $this->setupUpdates(); 696 } catch (\RuntimeException $e) { 697 // Install failed, roll back changes 698 $this->parent->abort($e->getMessage()); 699 700 return false; 701 } 702 } 703 704 /* 705 * --------------------------------------------------------------------------------------------- 706 * Installer Trigger Loading 707 * --------------------------------------------------------------------------------------------- 708 */ 709 710 $this->setupScriptfile(); 711 712 try { 713 $this->triggerManifestScript('preflight'); 714 } catch (\RuntimeException $e) { 715 // Install failed, roll back changes 716 $this->parent->abort($e->getMessage()); 717 718 return false; 719 } 720 721 /* 722 * --------------------------------------------------------------------------------------------- 723 * Filesystem Processing Section 724 * --------------------------------------------------------------------------------------------- 725 */ 726 727 // If the extension directory does not exist, lets create it 728 try { 729 $this->createExtensionRoot(); 730 } catch (\RuntimeException $e) { 731 // Install failed, roll back changes 732 $this->parent->abort($e->getMessage()); 733 734 return false; 735 } 736 737 // Copy all necessary files 738 try { 739 $this->copyBaseFiles(); 740 } catch (\RuntimeException $e) { 741 // Install failed, roll back changes 742 $this->parent->abort($e->getMessage()); 743 744 return false; 745 } 746 747 // Parse optional tags 748 $this->parseOptionalTags(); 749 750 /* 751 * --------------------------------------------------------------------------------------------- 752 * Database Processing Section 753 * --------------------------------------------------------------------------------------------- 754 */ 755 756 try { 757 $this->storeExtension(); 758 } catch (\RuntimeException $e) { 759 // Install failed, roll back changes 760 $this->parent->abort($e->getMessage()); 761 762 return false; 763 } 764 765 try { 766 $this->parseQueries(); 767 } catch (\RuntimeException $e) { 768 // Install failed, roll back changes 769 $this->parent->abort($e->getMessage()); 770 771 return false; 772 } 773 774 // Run the custom method based on the route 775 try { 776 $this->triggerManifestScript($this->route); 777 } catch (\RuntimeException $e) { 778 // Install failed, roll back changes 779 $this->parent->abort($e->getMessage()); 780 781 return false; 782 } 783 784 /* 785 * --------------------------------------------------------------------------------------------- 786 * Finalization and Cleanup Section 787 * --------------------------------------------------------------------------------------------- 788 */ 789 790 try { 791 $this->finaliseInstall(); 792 } catch (\RuntimeException $e) { 793 // Install failed, roll back changes 794 $this->parent->abort($e->getMessage()); 795 796 return false; 797 } 798 799 // And now we run the postflight 800 try { 801 $this->triggerManifestScript('postflight'); 802 } catch (\RuntimeException $e) { 803 // Install failed, roll back changes 804 $this->parent->abort($e->getMessage()); 805 806 return false; 807 } 808 809 return $this->extension->extension_id; 810 } 811 812 /** 813 * Method to parse the queries specified in the `<sql>` tags 814 * 815 * @return void 816 * 817 * @since 3.4 818 * @throws \RuntimeException 819 */ 820 protected function parseQueries() 821 { 822 // Let's run the queries for the extension 823 if (\in_array($this->route, array('install', 'discover_install', 'uninstall'))) { 824 // This method may throw an exception, but it is caught by the parent caller 825 if (!$this->doDatabaseTransactions()) { 826 throw new \RuntimeException(Text::_('JLIB_INSTALLER_ABORT_INSTALL_ABORTED')); 827 } 828 829 // Set the schema version to be the latest update version 830 if ($this->getManifest()->update) { 831 $this->parent->setSchemaVersion($this->getManifest()->update->schemas, $this->extension->extension_id); 832 } 833 } elseif ($this->route === 'update') { 834 if ($this->getManifest()->update) { 835 $result = $this->parent->parseSchemaUpdates($this->getManifest()->update->schemas, $this->extension->extension_id); 836 837 if ($result === false) { 838 // Install failed, rollback changes 839 throw new \RuntimeException(Text::_('JLIB_INSTALLER_ABORT_INSTALL_ABORTED')); 840 } 841 } 842 } 843 } 844 845 /** 846 * Method to parse optional tags in the manifest 847 * 848 * @return void 849 * 850 * @since 3.1 851 */ 852 protected function parseOptionalTags() 853 { 854 // Some extensions may not have optional tags 855 } 856 857 /** 858 * Prepares the adapter for a discover_install task 859 * 860 * @return void 861 * 862 * @since 3.4 863 */ 864 public function prepareDiscoverInstall() 865 { 866 // Adapters may not support discover install or may have overridden the default task and aren't using this 867 } 868 869 /** 870 * Removes this extension's files 871 * 872 * @return void 873 * 874 * @since 4.0.0 875 * @throws \RuntimeException 876 */ 877 abstract protected function removeExtensionFiles(); 878 879 /** 880 * Set the manifest object. 881 * 882 * @param object $manifest The manifest object 883 * 884 * @return InstallerAdapter Instance of this class to support chaining 885 * 886 * @since 3.4 887 */ 888 public function setManifest($manifest) 889 { 890 $this->manifest = $manifest; 891 892 return $this; 893 } 894 895 /** 896 * Set the install route being followed 897 * 898 * @param string $route The install route being followed 899 * 900 * @return InstallerAdapter Instance of this class to support chaining 901 * 902 * @since 3.4 903 */ 904 public function setRoute($route) 905 { 906 $this->route = $route; 907 908 return $this; 909 } 910 911 /** 912 * Method to do any prechecks and setup the install paths for the extension 913 * 914 * @return void 915 * 916 * @since 3.4 917 */ 918 abstract protected function setupInstallPaths(); 919 920 /** 921 * Setup the manifest script file for those adapters that use it. 922 * 923 * @return void 924 * 925 * @since 3.4 926 */ 927 protected function setupScriptfile() 928 { 929 // If there is a manifest class file, lets load it; we'll copy it later (don't have dest yet) 930 $manifestScript = (string) $this->getManifest()->scriptfile; 931 932 // When no script file, do nothing 933 if (!$manifestScript) { 934 return; 935 } 936 937 // Build a child container, so we do not overwrite the global one 938 // and start from scratch when multiple extensions are installed 939 try { 940 $container = new Container($this->getContainer()); 941 } catch (ContainerNotFoundException $e) { 942 @trigger_error('Container must be set.', E_USER_DEPRECATED); 943 944 // Fallback to the global container 945 $container = new Container(Factory::getContainer()); 946 } 947 948 // The real location of the file 949 $manifestScriptFile = $this->parent->getPath('source') . '/' . $manifestScript; 950 951 $installer = null; 952 953 // Load the installer from the file 954 if (!file_exists($manifestScriptFile)) { 955 @trigger_error( 956 'Installer file must exist when defined. In version 5.0 this will crash.', 957 E_USER_DEPRECATED 958 ); 959 960 return; 961 } 962 963 require_once $manifestScriptFile; 964 965 // When the instance is a service provider, then register the container with it 966 if ($installer instanceof ServiceProviderInterface) { 967 $installer->register($container); 968 } 969 970 // When the returned object is an installer instance, use it directly 971 if ($installer instanceof InstallerScriptInterface) { 972 $container->set(InstallerScriptInterface::class, $installer); 973 } 974 975 // When none is set, then use the legacy way 976 if (!$container->has(InstallerScriptInterface::class)) { 977 @trigger_error( 978 'Legacy installer files are deprecated and will be removed in 6.0. Use a service provider instead.', 979 E_USER_DEPRECATED 980 ); 981 982 $classname = $this->getScriptClassName(); 983 984 \JLoader::register($classname, $manifestScriptFile); 985 986 if (!class_exists($classname)) { 987 return; 988 } 989 990 $container->set( 991 InstallerScriptInterface::class, 992 function (Container $container) use ($classname) { 993 return new LegacyInstallerScript(new $classname($this)); 994 } 995 ); 996 } 997 998 // Create a new instance 999 $this->parent->manifestClass = $container->get(InstallerScriptInterface::class); 1000 1001 // And set this so we can copy it later 1002 $this->manifest_script = $manifestScript; 1003 } 1004 1005 /** 1006 * Method to do any prechecks and setup the uninstall job 1007 * 1008 * @return void 1009 * 1010 * @since 4.0.0 1011 */ 1012 abstract protected function setupUninstall(); 1013 1014 /** 1015 * Method to setup the update routine for the adapter 1016 * 1017 * @return void 1018 * 1019 * @since 3.4 1020 */ 1021 protected function setupUpdates() 1022 { 1023 // Some extensions may not have custom setup routines for updates 1024 } 1025 1026 /** 1027 * Method to store the extension to the database 1028 * 1029 * @return void 1030 * 1031 * @since 3.4 1032 * @throws \RuntimeException 1033 */ 1034 abstract protected function storeExtension(); 1035 1036 /** 1037 * Executes a custom install script method 1038 * 1039 * @param string $method The install method to execute 1040 * 1041 * @return boolean True on success 1042 * 1043 * @since 3.4 1044 * @throws \RuntimeException 1045 */ 1046 protected function triggerManifestScript($method) 1047 { 1048 ob_start(); 1049 ob_implicit_flush(false); 1050 1051 if ($this->parent->manifestClass && method_exists($this->parent->manifestClass, $method)) { 1052 switch ($method) { 1053 // The preflight and postflight take the route as a param 1054 case 'preflight': 1055 case 'postflight': 1056 if ($this->parent->manifestClass->$method($this->route, $this) === false) { 1057 if ($method !== 'postflight') { 1058 // Clean and close the output buffer 1059 ob_end_clean(); 1060 1061 // The script failed, rollback changes 1062 throw new \RuntimeException( 1063 Text::sprintf( 1064 'JLIB_INSTALLER_ABORT_INSTALL_CUSTOM_INSTALL_FAILURE', 1065 Text::_('JLIB_INSTALLER_' . $this->route) 1066 ) 1067 ); 1068 } 1069 } 1070 break; 1071 1072 // The install, uninstall, and update methods only pass this object as a param 1073 case 'install': 1074 case 'uninstall': 1075 case 'update': 1076 if ($this->parent->manifestClass->$method($this) === false) { 1077 if ($method !== 'uninstall') { 1078 // Clean and close the output buffer 1079 ob_end_clean(); 1080 1081 // The script failed, rollback changes 1082 throw new \RuntimeException( 1083 Text::sprintf( 1084 'JLIB_INSTALLER_ABORT_INSTALL_CUSTOM_INSTALL_FAILURE', 1085 Text::_('JLIB_INSTALLER_' . $this->route) 1086 ) 1087 ); 1088 } 1089 } 1090 break; 1091 } 1092 } 1093 1094 // Append to the message object 1095 $this->extensionMessage .= ob_get_clean(); 1096 1097 // If in postflight or uninstall, set the message for display 1098 if (($method === 'uninstall' || $method === 'postflight') && $this->extensionMessage !== '') { 1099 $this->parent->set('extension_message', $this->extensionMessage); 1100 } 1101 1102 return true; 1103 } 1104 1105 /** 1106 * Generic update method for extensions 1107 * 1108 * @param integer $id The extension ID 1109 * 1110 * @return boolean True on success 1111 * 1112 * @since 4.0.0 1113 */ 1114 public function uninstall($id) 1115 { 1116 if (!$this->extension->load((int) $id)) { 1117 Log::add(Text::_('JLIB_INSTALLER_ERROR_UNKNOWN_EXTENSION'), Log::WARNING, 'jerror'); 1118 1119 return false; 1120 } 1121 1122 // Joomla 4: Locked extensions cannot be removed. 1123 if (isset($this->extension->locked) && $this->extension->locked) { 1124 Log::add(Text::_('JLIB_INSTALLER_ERROR_UNINSTALL_LOCKED_EXTENSION'), Log::WARNING, 'jerror'); 1125 1126 return false; 1127 } elseif (!isset($this->extension->locked) && $this->extension->protected) { 1128 // Joomla 3 ('locked' property does not exist yet): Protected extensions cannot be removed. 1129 Log::add(Text::_('JLIB_INSTALLER_ERROR_UNINSTALL_PROTECTED_EXTENSION'), Log::WARNING, 'jerror'); 1130 1131 return false; 1132 } 1133 1134 /* 1135 * Does this extension have a parent package? 1136 * If so, check if the package disallows individual extensions being uninstalled if the package is not being uninstalled 1137 */ 1138 if ($this->extension->package_id && !$this->parent->isPackageUninstall() && !$this->canUninstallPackageChild($this->extension->package_id)) { 1139 Log::add( 1140 Text::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE', $this->extension->name), 1141 Log::WARNING, 1142 'jerror' 1143 ); 1144 1145 return false; 1146 } 1147 1148 // Setup the uninstall job as required 1149 try { 1150 $this->setupUninstall(); 1151 } catch (\RuntimeException $e) { 1152 Log::add($e->getMessage(), Log::WARNING, 'jerror'); 1153 1154 return false; 1155 } 1156 1157 // Set the extension's name and element 1158 $this->name = $this->getName(); 1159 $this->element = $this->getElement(); 1160 1161 /* 1162 * --------------------------------------------------------------------------------------------- 1163 * Installer Trigger Loading and Uninstall 1164 * --------------------------------------------------------------------------------------------- 1165 */ 1166 1167 $this->setupScriptfile(); 1168 1169 try { 1170 $this->triggerManifestScript('preflight'); 1171 } catch (\RuntimeException $e) { 1172 Log::add($e->getMessage(), Log::WARNING, 'jerror'); 1173 1174 return false; 1175 } 1176 1177 try { 1178 $this->triggerManifestScript('uninstall'); 1179 } catch (\RuntimeException $e) { 1180 // Ignore errors for now 1181 } 1182 1183 // Tasks from here may fail but we will still attempt to finish the uninstall process 1184 $retval = true; 1185 1186 /* 1187 * --------------------------------------------------------------------------------------------- 1188 * Database Processing Section 1189 * --------------------------------------------------------------------------------------------- 1190 */ 1191 1192 try { 1193 $this->parseQueries(); 1194 } catch (\RuntimeException $e) { 1195 Log::add($e->getMessage(), Log::WARNING, 'jerror'); 1196 1197 $retval = false; 1198 } 1199 1200 /* 1201 * --------------------------------------------------------------------------------------------- 1202 * Filesystem Processing Section 1203 * --------------------------------------------------------------------------------------------- 1204 */ 1205 1206 try { 1207 $this->removeExtensionFiles(); 1208 } catch (\RuntimeException $e) { 1209 Log::add($e->getMessage(), Log::WARNING, 'jerror'); 1210 1211 $retval = false; 1212 } 1213 1214 /* 1215 * --------------------------------------------------------------------------------------------- 1216 * Finalization and Cleanup Section 1217 * --------------------------------------------------------------------------------------------- 1218 */ 1219 1220 try { 1221 $retval |= $this->finaliseUninstall(); 1222 } catch (\RuntimeException $e) { 1223 Log::add($e->getMessage(), Log::WARNING, 'jerror'); 1224 1225 $retval = false; 1226 } 1227 1228 // And now we run the postflight 1229 try { 1230 $this->triggerManifestScript('postflight'); 1231 } catch (\RuntimeException $e) { 1232 Log::add($e->getMessage(), Log::WARNING, 'jerror'); 1233 1234 $retval = false; 1235 } 1236 1237 return $retval; 1238 } 1239 1240 /** 1241 * Generic update method for extensions 1242 * 1243 * @return boolean|integer The extension ID on success, boolean false on failure 1244 * 1245 * @since 3.4 1246 */ 1247 public function update() 1248 { 1249 // Set the overwrite setting 1250 $this->parent->setOverwrite(true); 1251 $this->parent->setUpgrade(true); 1252 1253 // And make sure the route is set correctly 1254 $this->setRoute('update'); 1255 1256 // Now jump into the install method to run the update 1257 return $this->install(); 1258 } 1259 1260 /** 1261 * Proxy for db variable. 1262 * 1263 * @param string $name The name of the element 1264 * 1265 * @return mixed The value of the element if set, null otherwise 1266 * 1267 * @since 4.2.0 1268 * 1269 * @deprecated 5.0 Use getDatabase() instead of directly accessing db 1270 */ 1271 public function __get($name) 1272 { 1273 if ($name === 'db') { 1274 return $this->getDatabase(); 1275 } 1276 1277 // Default the variable 1278 if (!isset($this->$name)) { 1279 $this->$name = null; 1280 } 1281 1282 return $this->$name; 1283 } 1284 }
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 |