[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * @package Joomla.Administrator 5 * @subpackage com_installer 6 * 7 * @copyright (C) 2014 Open Source Matters, Inc. <https://www.joomla.org> 8 * @license GNU General Public License version 2 or later; see LICENSE.txt 9 */ 10 11 namespace Joomla\Component\Installer\Administrator\Model; 12 13 use Exception; 14 use Joomla\CMS\Factory; 15 use Joomla\CMS\Filesystem\Folder; 16 use Joomla\CMS\Installer\Installer; 17 use Joomla\CMS\Language\Text; 18 use Joomla\CMS\MVC\Factory\MVCFactoryInterface; 19 use Joomla\CMS\Object\CMSObject; 20 use Joomla\CMS\Plugin\PluginHelper; 21 use Joomla\CMS\Router\Route; 22 use Joomla\CMS\Table\UpdateSite as UpdateSiteTable; 23 use Joomla\Component\Installer\Administrator\Helper\InstallerHelper; 24 use Joomla\Database\ParameterType; 25 use RuntimeException; 26 27 // phpcs:disable PSR1.Files.SideEffects 28 \defined('_JEXEC') or die; 29 // phpcs:enable PSR1.Files.SideEffects 30 31 /** 32 * Installer Update Sites Model 33 * 34 * @since 3.4 35 */ 36 class UpdatesitesModel extends InstallerModel 37 { 38 /** 39 * Constructor. 40 * 41 * @param array $config An optional associative array of configuration settings. 42 * @param MVCFactoryInterface $factory The factory. 43 * 44 * @since 1.6 45 * @see \Joomla\CMS\MVC\Model\ListModel 46 */ 47 public function __construct($config = [], MVCFactoryInterface $factory = null) 48 { 49 if (empty($config['filter_fields'])) { 50 $config['filter_fields'] = [ 51 'update_site_name', 52 'name', 53 'client_id', 54 'client', 55 'client_translated', 56 'status', 57 'type', 58 'type_translated', 59 'folder', 60 'folder_translated', 61 'update_site_id', 62 'enabled', 63 'supported' 64 ]; 65 } 66 67 parent::__construct($config, $factory); 68 } 69 70 /** 71 * Enable/Disable an extension. 72 * 73 * @param array $eid Extension ids to un/publish 74 * @param int $value Publish value 75 * 76 * @return boolean True on success 77 * 78 * @throws Exception on ACL error 79 * @since 3.4 80 * 81 */ 82 public function publish(&$eid = [], $value = 1) 83 { 84 if (!Factory::getUser()->authorise('core.edit.state', 'com_installer')) { 85 throw new Exception(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), 403); 86 } 87 88 $result = true; 89 90 // Ensure eid is an array of extension ids 91 if (!is_array($eid)) { 92 $eid = [$eid]; 93 } 94 95 // Get a table object for the extension type 96 $table = new UpdateSiteTable($this->getDatabase()); 97 98 // Enable the update site in the table and store it in the database 99 foreach ($eid as $i => $id) { 100 $table->load($id); 101 $table->enabled = $value; 102 103 if (!$table->store()) { 104 $this->setError($table->getError()); 105 $result = false; 106 } 107 } 108 109 return $result; 110 } 111 112 /** 113 * Deletes an update site. 114 * 115 * @param array $ids Extension ids to delete. 116 * 117 * @return void 118 * 119 * @throws Exception on ACL error 120 * @since 3.6 121 * 122 */ 123 public function delete($ids = []) 124 { 125 if (!Factory::getUser()->authorise('core.delete', 'com_installer')) { 126 throw new Exception(Text::_('JLIB_APPLICATION_ERROR_DELETE_NOT_PERMITTED'), 403); 127 } 128 129 // Ensure eid is an array of extension ids 130 if (!is_array($ids)) { 131 $ids = [$ids]; 132 } 133 134 $db = $this->getDatabase(); 135 $app = Factory::getApplication(); 136 137 $count = 0; 138 139 // Gets the update site names. 140 $query = $db->getQuery(true) 141 ->select($db->quoteName(['update_site_id', 'name'])) 142 ->from($db->quoteName('#__update_sites')) 143 ->whereIn($db->quoteName('update_site_id'), $ids); 144 $db->setQuery($query); 145 $updateSitesNames = $db->loadObjectList('update_site_id'); 146 147 // Gets Joomla core update sites Ids. 148 $joomlaUpdateSitesIds = $this->getJoomlaUpdateSitesIds(0); 149 150 // Enable the update site in the table and store it in the database 151 foreach ($ids as $i => $id) { 152 // Don't allow to delete Joomla Core update sites. 153 if (in_array((int) $id, $joomlaUpdateSitesIds)) { 154 $app->enqueueMessage(Text::sprintf('COM_INSTALLER_MSG_UPDATESITES_DELETE_CANNOT_DELETE', $updateSitesNames[$id]->name), 'error'); 155 continue; 156 } 157 158 // Delete the update site from all tables. 159 try { 160 $id = (int) $id; 161 $query = $db->getQuery(true) 162 ->delete($db->quoteName('#__update_sites')) 163 ->where($db->quoteName('update_site_id') . ' = :id') 164 ->bind(':id', $id, ParameterType::INTEGER); 165 $db->setQuery($query); 166 $db->execute(); 167 168 $query = $db->getQuery(true) 169 ->delete($db->quoteName('#__update_sites_extensions')) 170 ->where($db->quoteName('update_site_id') . ' = :id') 171 ->bind(':id', $id, ParameterType::INTEGER); 172 $db->setQuery($query); 173 $db->execute(); 174 175 $query = $db->getQuery(true) 176 ->delete($db->quoteName('#__updates')) 177 ->where($db->quoteName('update_site_id') . ' = :id') 178 ->bind(':id', $id, ParameterType::INTEGER); 179 $db->setQuery($query); 180 $db->execute(); 181 182 $count++; 183 } catch (RuntimeException $e) { 184 $app->enqueueMessage( 185 Text::sprintf( 186 'COM_INSTALLER_MSG_UPDATESITES_DELETE_ERROR', 187 $updateSitesNames[$id]->name, 188 $e->getMessage() 189 ), 190 'error' 191 ); 192 } 193 } 194 195 if ($count > 0) { 196 $app->enqueueMessage(Text::plural('COM_INSTALLER_MSG_UPDATESITES_N_DELETE_UPDATESITES_DELETED', $count), 'message'); 197 } 198 } 199 200 /** 201 * Fetch the Joomla update sites ids. 202 * 203 * @param integer $column Column to return. 0 for update site ids, 1 for extension ids. 204 * 205 * @return array Array with joomla core update site ids. 206 * 207 * @since 3.6.0 208 */ 209 protected function getJoomlaUpdateSitesIds($column = 0) 210 { 211 $db = $this->getDatabase(); 212 213 // Fetch the Joomla core update sites ids and their extension ids. We search for all except the core joomla extension with update sites. 214 $query = $db->getQuery(true) 215 ->select($db->quoteName(['use.update_site_id', 'e.extension_id'])) 216 ->from($db->quoteName('#__update_sites_extensions', 'use')) 217 ->join( 218 'LEFT', 219 $db->quoteName('#__update_sites', 'us'), 220 $db->quoteName('us.update_site_id') . ' = ' . $db->quoteName('use.update_site_id') 221 ) 222 ->join( 223 'LEFT', 224 $db->quoteName('#__extensions', 'e'), 225 $db->quoteName('e.extension_id') . ' = ' . $db->quoteName('use.extension_id') 226 ) 227 ->where('(' 228 . '(' . $db->quoteName('e.type') . ' = ' . $db->quote('file') . 229 ' AND ' . $db->quoteName('e.element') . ' = ' . $db->quote('joomla') . ')' 230 . ' OR (' . $db->quoteName('e.type') . ' = ' . $db->quote('package') . ' AND ' . $db->quoteName('e.element') 231 . ' = ' . $db->quote('pkg_en-GB') . ') OR (' . $db->quoteName('e.type') . ' = ' . $db->quote('component') 232 . ' AND ' . $db->quoteName('e.element') . ' = ' . $db->quote('com_joomlaupdate') . ')' 233 . ')'); 234 235 $db->setQuery($query); 236 237 return $db->loadColumn($column); 238 } 239 240 /** 241 * Rebuild update sites tables. 242 * 243 * @return void 244 * 245 * @throws Exception on ACL error 246 * @since 3.6 247 * 248 */ 249 public function rebuild(): void 250 { 251 if (!Factory::getUser()->authorise('core.admin', 'com_installer')) { 252 throw new Exception(Text::_('COM_INSTALLER_MSG_UPDATESITES_REBUILD_NOT_PERMITTED'), 403); 253 } 254 255 $db = $this->getDatabase(); 256 $app = Factory::getApplication(); 257 258 // Check if Joomla Extension plugin is enabled. 259 if (!PluginHelper::isEnabled('extension', 'joomla')) { 260 $query = $db->getQuery(true) 261 ->select($db->quoteName('extension_id')) 262 ->from($db->quoteName('#__extensions')) 263 ->where($db->quoteName('type') . ' = ' . $db->quote('plugin')) 264 ->where($db->quoteName('element') . ' = ' . $db->quote('joomla')) 265 ->where($db->quoteName('folder') . ' = ' . $db->quote('extension')); 266 $db->setQuery($query); 267 268 $pluginId = (int) $db->loadResult(); 269 270 $link = Route::_('index.php?option=com_plugins&task=plugin.edit&extension_id=' . $pluginId); 271 $app->enqueueMessage(Text::sprintf('COM_INSTALLER_MSG_UPDATESITES_REBUILD_EXTENSION_PLUGIN_NOT_ENABLED', $link), 'error'); 272 273 return; 274 } 275 276 $clients = [JPATH_SITE, JPATH_ADMINISTRATOR, JPATH_API]; 277 $extensionGroupFolders = ['components', 'modules', 'plugins', 'templates', 'language', 'manifests']; 278 279 $pathsToSearch = []; 280 281 // Identifies which folders to search for manifest files. 282 foreach ($clients as $clientPath) { 283 foreach ($extensionGroupFolders as $extensionGroupFolderName) { 284 // Components, modules, plugins, templates, languages and manifest (files, libraries, etc) 285 if ($extensionGroupFolderName !== 'plugins') { 286 foreach (glob($clientPath . '/' . $extensionGroupFolderName . '/*', GLOB_NOSORT | GLOB_ONLYDIR) as $extensionFolderPath) { 287 $pathsToSearch[] = $extensionFolderPath; 288 } 289 } else { 290 // Plugins (another directory level is needed) 291 foreach ( 292 glob( 293 $clientPath . '/' . $extensionGroupFolderName . '/*', 294 GLOB_NOSORT | GLOB_ONLYDIR 295 ) as $pluginGroupFolderPath 296 ) { 297 foreach (glob($pluginGroupFolderPath . '/*', GLOB_NOSORT | GLOB_ONLYDIR) as $extensionFolderPath) { 298 $pathsToSearch[] = $extensionFolderPath; 299 } 300 } 301 } 302 } 303 } 304 305 // Gets Joomla core update sites Ids. 306 $joomlaUpdateSitesIds = $this->getJoomlaUpdateSitesIds(0); 307 308 // First backup any custom extra_query for the sites 309 $query = $db->getQuery(true) 310 ->select('TRIM(' . $db->quoteName('location') . ') AS ' . $db->quoteName('location') . ', ' . $db->quoteName('extra_query')) 311 ->from($db->quoteName('#__update_sites')); 312 $db->setQuery($query); 313 $backupExtraQuerys = $db->loadAssocList('location'); 314 315 // Delete from all tables (except joomla core update sites). 316 $query = $db->getQuery(true) 317 ->delete($db->quoteName('#__update_sites')) 318 ->whereNotIn($db->quoteName('update_site_id'), $joomlaUpdateSitesIds); 319 $db->setQuery($query); 320 $db->execute(); 321 322 $query = $db->getQuery(true) 323 ->delete($db->quoteName('#__update_sites_extensions')) 324 ->whereNotIn($db->quoteName('update_site_id'), $joomlaUpdateSitesIds); 325 $db->setQuery($query); 326 $db->execute(); 327 328 $query = $db->getQuery(true) 329 ->delete($db->quoteName('#__updates')) 330 ->whereNotIn($db->quoteName('update_site_id'), $joomlaUpdateSitesIds); 331 $db->setQuery($query); 332 $db->execute(); 333 334 $count = 0; 335 336 // Gets Joomla core extension Ids. 337 $joomlaCoreExtensionIds = $this->getJoomlaUpdateSitesIds(1); 338 339 // Search for updateservers in manifest files inside the folders to search. 340 foreach ($pathsToSearch as $extensionFolderPath) { 341 $tmpInstaller = new Installer(); 342 $tmpInstaller->setDatabase($this->getDatabase()); 343 344 $tmpInstaller->setPath('source', $extensionFolderPath); 345 346 // Main folder manifests (higher priority) 347 $parentXmlfiles = Folder::files($tmpInstaller->getPath('source'), '.xml$', false, true); 348 349 // Search for children manifests (lower priority) 350 $allXmlFiles = Folder::files($tmpInstaller->getPath('source'), '.xml$', 1, true); 351 352 // Create a unique array of files ordered by priority 353 $xmlfiles = array_unique(array_merge($parentXmlfiles, $allXmlFiles)); 354 355 if (!empty($xmlfiles)) { 356 foreach ($xmlfiles as $file) { 357 // Is it a valid Joomla installation manifest file? 358 $manifest = $tmpInstaller->isManifest($file); 359 360 if ($manifest !== null) { 361 /** 362 * Search if the extension exists in the extensions table. Excluding Joomla 363 * core extensions and discovered but not yet installed extensions. 364 */ 365 366 $name = (string) $manifest->name; 367 $pkgName = (string) $manifest->packagename; 368 $type = (string) $manifest['type']; 369 370 $query = $db->getQuery(true) 371 ->select($db->quoteName('extension_id')) 372 ->from($db->quoteName('#__extensions')) 373 ->where( 374 [ 375 $db->quoteName('type') . ' = :type', 376 $db->quoteName('state') . ' != -1', 377 ] 378 ) 379 ->extendWhere( 380 'AND', 381 [ 382 $db->quoteName('name') . ' = :name', 383 $db->quoteName('name') . ' = :pkgname', 384 ], 385 'OR' 386 ) 387 ->whereNotIn($db->quoteName('extension_id'), $joomlaCoreExtensionIds) 388 ->bind(':name', $name) 389 ->bind(':pkgname', $pkgName) 390 ->bind(':type', $type); 391 $db->setQuery($query); 392 393 $eid = (int) $db->loadResult(); 394 395 if ($eid && $manifest->updateservers) { 396 // Set the manifest object and path 397 $tmpInstaller->manifest = $manifest; 398 $tmpInstaller->setPath('manifest', $file); 399 400 // Remove last extra_query as we are in a foreach 401 $tmpInstaller->extraQuery = ''; 402 403 if ( 404 $tmpInstaller->manifest->updateservers 405 && $tmpInstaller->manifest->updateservers->server 406 && isset($backupExtraQuerys[trim((string) $tmpInstaller->manifest->updateservers->server)]) 407 ) { 408 $tmpInstaller->extraQuery = $backupExtraQuerys[trim((string) $tmpInstaller->manifest->updateservers->server)]['extra_query']; 409 } 410 411 // Load the extension plugin (if not loaded yet). 412 PluginHelper::importPlugin('extension', 'joomla'); 413 414 // Fire the onExtensionAfterUpdate 415 $app->triggerEvent('onExtensionAfterUpdate', ['installer' => $tmpInstaller, 'eid' => $eid]); 416 417 $count++; 418 } 419 } 420 } 421 } 422 } 423 424 if ($count > 0) { 425 $app->enqueueMessage(Text::_('COM_INSTALLER_MSG_UPDATESITES_REBUILD_SUCCESS'), 'message'); 426 } else { 427 $app->enqueueMessage(Text::_('COM_INSTALLER_MSG_UPDATESITES_REBUILD_MESSAGE'), 'message'); 428 } 429 430 // Flush the system cache to ensure extra_query is correctly loaded next time. 431 $this->cleanCache('_system'); 432 } 433 434 /** 435 * Method to get an array of data items. 436 * 437 * @return mixed An array of data items on success, false on failure. 438 * 439 * @since 4.0.0 440 */ 441 public function getItems() 442 { 443 $items = parent::getItems(); 444 445 array_walk( 446 $items, 447 static function ($item) { 448 $data = new CMSObject($item); 449 $item->downloadKey = InstallerHelper::getDownloadKey($data); 450 } 451 ); 452 453 return $items; 454 } 455 456 /** 457 * Method to auto-populate the model state. 458 * 459 * Note. Calling getState in this method will result in recursion. 460 * 461 * @param string $ordering An optional ordering field. 462 * @param string $direction An optional direction (asc|desc). 463 * 464 * @return void 465 * 466 * @since 3.4 467 */ 468 protected function populateState($ordering = 'name', $direction = 'asc') 469 { 470 // Load the filter state. 471 $stateKeys = [ 472 'search' => 'string', 473 'client_id' => 'int', 474 'enabled' => 'string', 475 'type' => 'string', 476 'folder' => 'string', 477 'supported' => 'int', 478 ]; 479 480 foreach ($stateKeys as $key => $filterType) { 481 $stateKey = 'filter.' . $key; 482 483 switch ($filterType) { 484 case 'int': 485 case 'bool': 486 $default = null; 487 break; 488 489 default: 490 $default = ''; 491 break; 492 } 493 494 $stateValue = $this->getUserStateFromRequest( 495 $this->context . '.' . $stateKey, 496 'filter_' . $key, 497 $default, 498 $filterType 499 ); 500 501 $this->setState($stateKey, $stateValue); 502 } 503 504 parent::populateState($ordering, $direction); 505 } 506 507 protected function getStoreId($id = '') 508 { 509 $id .= ':' . $this->getState('search'); 510 $id .= ':' . $this->getState('client_id'); 511 $id .= ':' . $this->getState('enabled'); 512 $id .= ':' . $this->getState('type'); 513 $id .= ':' . $this->getState('folder'); 514 $id .= ':' . $this->getState('supported'); 515 516 return parent::getStoreId($id); 517 } 518 519 /** 520 * Method to get the database query 521 * 522 * @return \Joomla\Database\DatabaseQuery The database query 523 * 524 * @since 3.4 525 */ 526 protected function getListQuery() 527 { 528 $db = $this->getDatabase(); 529 $query = $db->getQuery(true) 530 ->select( 531 $db->quoteName( 532 [ 533 's.update_site_id', 534 's.name', 535 's.type', 536 's.location', 537 's.enabled', 538 's.checked_out', 539 's.checked_out_time', 540 's.extra_query', 541 'e.extension_id', 542 'e.name', 543 'e.type', 544 'e.element', 545 'e.folder', 546 'e.client_id', 547 'e.state', 548 'e.manifest_cache', 549 'u.name' 550 ], 551 [ 552 'update_site_id', 553 'update_site_name', 554 'update_site_type', 555 'location', 556 'enabled', 557 'checked_out', 558 'checked_out_time', 559 'extra_query', 560 'extension_id', 561 'name', 562 'type', 563 'element', 564 'folder', 565 'client_id', 566 'state', 567 'manifest_cache', 568 'editor' 569 ] 570 ) 571 ) 572 ->from($db->quoteName('#__update_sites', 's')) 573 ->join( 574 'INNER', 575 $db->quoteName('#__update_sites_extensions', 'se'), 576 $db->quoteName('se.update_site_id') . ' = ' . $db->quoteName('s.update_site_id') 577 ) 578 ->join( 579 'INNER', 580 $db->quoteName('#__extensions', 'e'), 581 $db->quoteName('e.extension_id') . ' = ' . $db->quoteName('se.extension_id') 582 ) 583 ->join( 584 'LEFT', 585 $db->quoteName('#__users', 'u'), 586 $db->quoteName('s.checked_out') . ' = ' . $db->quoteName('u.id') 587 ) 588 ->where($db->quoteName('state') . ' = 0'); 589 590 // Process select filters. 591 $supported = $this->getState('filter.supported'); 592 $enabled = $this->getState('filter.enabled'); 593 $type = $this->getState('filter.type'); 594 $clientId = $this->getState('filter.client_id'); 595 $folder = $this->getState('filter.folder'); 596 597 if ($enabled !== '') { 598 $enabled = (int) $enabled; 599 $query->where($db->quoteName('s.enabled') . ' = :enabled') 600 ->bind(':enabled', $enabled, ParameterType::INTEGER); 601 } 602 603 if ($type) { 604 $query->where($db->quoteName('e.type') . ' = :type') 605 ->bind(':type', $type); 606 } 607 608 if ($clientId !== null && $clientId !== '') { 609 $clientId = (int) $clientId; 610 $query->where($db->quoteName('e.client_id') . ' = :clientId') 611 ->bind(':clientId', $clientId, ParameterType::INTEGER); 612 } 613 614 if ($folder !== '' && in_array($type, ['plugin', 'library', ''], true)) { 615 $folderForBinding = $folder === '*' ? '' : $folder; 616 $query->where($db->quoteName('e.folder') . ' = :folder') 617 ->bind(':folder', $folderForBinding); 618 } 619 620 // Process search filter (update site id). 621 $search = $this->getState('filter.search'); 622 623 if (!empty($search) && stripos($search, 'id:') === 0) { 624 $uid = (int) substr($search, 3); 625 $query->where($db->quoteName('s.update_site_id') . ' = :siteId') 626 ->bind(':siteId', $uid, ParameterType::INTEGER); 627 } 628 629 if (is_numeric($supported)) { 630 switch ($supported) { 631 // Show Update Sites which support Download Keys 632 case 1: 633 $supportedIDs = InstallerHelper::getDownloadKeySupportedSites($enabled); 634 break; 635 636 // Show Update Sites which are missing Download Keys 637 case -1: 638 $supportedIDs = InstallerHelper::getDownloadKeyExistsSites(false, $enabled); 639 break; 640 641 // Show Update Sites which have valid Download Keys 642 case 2: 643 $supportedIDs = InstallerHelper::getDownloadKeyExistsSites(true, $enabled); 644 break; 645 } 646 647 if (!empty($supportedIDs)) { 648 // Don't remove array_values(). whereIn expect a zero-based array. 649 $query->whereIn($db->qn('s.update_site_id'), array_values($supportedIDs)); 650 } else { 651 // In case of an empty list of IDs we apply a fake filter to effectively return no data 652 $query->where($db->qn('s.update_site_id') . ' = 0'); 653 } 654 } 655 656 /** 657 * Note: The search for name, ordering and pagination are processed by the parent InstallerModel class (in 658 * extension.php). 659 */ 660 661 return $query; 662 } 663 }
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 |