[ 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_config 6 * 7 * @copyright (C) 2013 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\Config\Administrator\Model; 12 13 use Joomla\CMS\Access\Access; 14 use Joomla\CMS\Access\Rules; 15 use Joomla\CMS\Cache\Exception\CacheConnectingException; 16 use Joomla\CMS\Cache\Exception\UnsupportedCacheException; 17 use Joomla\CMS\Component\ComponentHelper; 18 use Joomla\CMS\Factory; 19 use Joomla\CMS\Filesystem\File; 20 use Joomla\CMS\Filesystem\Folder; 21 use Joomla\CMS\Filesystem\Path; 22 use Joomla\CMS\Filter\OutputFilter; 23 use Joomla\CMS\Http\HttpFactory; 24 use Joomla\CMS\Language\Text; 25 use Joomla\CMS\Log\Log; 26 use Joomla\CMS\Mail\Exception\MailDisabledException; 27 use Joomla\CMS\Mail\MailTemplate; 28 use Joomla\CMS\MVC\Model\FormModel; 29 use Joomla\CMS\Table\Asset; 30 use Joomla\CMS\Table\Table; 31 use Joomla\CMS\Uri\Uri; 32 use Joomla\CMS\User\UserHelper; 33 use Joomla\Database\DatabaseDriver; 34 use Joomla\Database\ParameterType; 35 use Joomla\Registry\Registry; 36 use Joomla\Utilities\ArrayHelper; 37 use PHPMailer\PHPMailer\Exception as phpMailerException; 38 39 // phpcs:disable PSR1.Files.SideEffects 40 \defined('_JEXEC') or die; 41 // phpcs:enable PSR1.Files.SideEffects 42 43 /** 44 * Model for the global configuration 45 * 46 * @since 3.2 47 */ 48 class ApplicationModel extends FormModel 49 { 50 /** 51 * Array of protected password fields from the configuration.php 52 * 53 * @var array 54 * @since 3.9.23 55 */ 56 private $protectedConfigurationFields = array('password', 'secret', 'smtppass', 'redis_server_auth', 'session_redis_server_auth'); 57 58 /** 59 * Method to get a form object. 60 * 61 * @param array $data Data for the form. 62 * @param boolean $loadData True if the form is to load its own data (default case), false if not. 63 * 64 * @return mixed A JForm object on success, false on failure 65 * 66 * @since 1.6 67 */ 68 public function getForm($data = array(), $loadData = true) 69 { 70 // Get the form. 71 $form = $this->loadForm('com_config.application', 'application', array('control' => 'jform', 'load_data' => $loadData)); 72 73 if (empty($form)) { 74 return false; 75 } 76 77 return $form; 78 } 79 80 /** 81 * Method to get the configuration data. 82 * 83 * This method will load the global configuration data straight from 84 * JConfig. If configuration data has been saved in the session, that 85 * data will be merged into the original data, overwriting it. 86 * 87 * @return array An array containing all global config data. 88 * 89 * @since 1.6 90 */ 91 public function getData() 92 { 93 // Get the config data. 94 $config = new \JConfig(); 95 $data = ArrayHelper::fromObject($config); 96 97 // Get the correct driver at runtime 98 $data['dbtype'] = $this->getDatabase()->getName(); 99 100 // Prime the asset_id for the rules. 101 $data['asset_id'] = 1; 102 103 // Get the text filter data 104 $params = ComponentHelper::getParams('com_config'); 105 $data['filters'] = ArrayHelper::fromObject($params->get('filters')); 106 107 // If no filter data found, get from com_content (update of 1.6/1.7 site) 108 if (empty($data['filters'])) { 109 $contentParams = ComponentHelper::getParams('com_content'); 110 $data['filters'] = ArrayHelper::fromObject($contentParams->get('filters')); 111 } 112 113 // Check for data in the session. 114 $temp = Factory::getApplication()->getUserState('com_config.config.global.data'); 115 116 // Merge in the session data. 117 if (!empty($temp)) { 118 // $temp can sometimes be an object, and we need it to be an array 119 if (is_object($temp)) { 120 $temp = ArrayHelper::fromObject($temp); 121 } 122 123 $data = array_merge($temp, $data); 124 } 125 126 // Correct error_reporting value, since we removed "development", the "maximum" should be set instead 127 // @TODO: This can be removed in 5.0 128 if (!empty($data['error_reporting']) && $data['error_reporting'] === 'development') { 129 $data['error_reporting'] = 'maximum'; 130 } 131 132 return $data; 133 } 134 135 /** 136 * Method to validate the db connection properties. 137 * 138 * @param array $data An array containing all global config data. 139 * 140 * @return array|boolean Array with the validated global config data or boolean false on a validation failure. 141 * 142 * @since 4.0.0 143 */ 144 public function validateDbConnection($data) 145 { 146 // Validate database connection encryption options 147 if ((int) $data['dbencryption'] === 0) { 148 // Reset unused options 149 if (!empty($data['dbsslkey'])) { 150 $data['dbsslkey'] = ''; 151 } 152 153 if (!empty($data['dbsslcert'])) { 154 $data['dbsslcert'] = ''; 155 } 156 157 if ((bool) $data['dbsslverifyservercert'] === true) { 158 $data['dbsslverifyservercert'] = false; 159 } 160 161 if (!empty($data['dbsslca'])) { 162 $data['dbsslca'] = ''; 163 } 164 165 if (!empty($data['dbsslcipher'])) { 166 $data['dbsslcipher'] = ''; 167 } 168 } else { 169 // Check localhost 170 if (strtolower($data['host']) === 'localhost') { 171 Factory::getApplication()->enqueueMessage(Text::_('COM_CONFIG_ERROR_DATABASE_ENCRYPTION_LOCALHOST'), 'error'); 172 173 return false; 174 } 175 176 // Check CA file and folder depending on database type if server certificate verification 177 if ((bool) $data['dbsslverifyservercert'] === true) { 178 if (empty($data['dbsslca'])) { 179 Factory::getApplication()->enqueueMessage( 180 Text::sprintf( 181 'COM_CONFIG_ERROR_DATABASE_ENCRYPTION_FILE_FIELD_EMPTY', 182 Text::_('COM_CONFIG_FIELD_DATABASE_ENCRYPTION_CA_LABEL') 183 ), 184 'error' 185 ); 186 187 return false; 188 } 189 190 if (!File::exists(Path::clean($data['dbsslca']))) { 191 Factory::getApplication()->enqueueMessage( 192 Text::sprintf( 193 'COM_CONFIG_ERROR_DATABASE_ENCRYPTION_FILE_FIELD_BAD', 194 Text::_('COM_CONFIG_FIELD_DATABASE_ENCRYPTION_CA_LABEL') 195 ), 196 'error' 197 ); 198 199 return false; 200 } 201 } else { 202 // Reset unused option 203 if (!empty($data['dbsslca'])) { 204 $data['dbsslca'] = ''; 205 } 206 } 207 208 // Check key and certificate if two-way encryption 209 if ((int) $data['dbencryption'] === 2) { 210 if (empty($data['dbsslkey'])) { 211 Factory::getApplication()->enqueueMessage( 212 Text::sprintf( 213 'COM_CONFIG_ERROR_DATABASE_ENCRYPTION_FILE_FIELD_EMPTY', 214 Text::_('COM_CONFIG_FIELD_DATABASE_ENCRYPTION_KEY_LABEL') 215 ), 216 'error' 217 ); 218 219 return false; 220 } 221 222 if (!File::exists(Path::clean($data['dbsslkey']))) { 223 Factory::getApplication()->enqueueMessage( 224 Text::sprintf( 225 'COM_CONFIG_ERROR_DATABASE_ENCRYPTION_FILE_FIELD_BAD', 226 Text::_('COM_CONFIG_FIELD_DATABASE_ENCRYPTION_KEY_LABEL') 227 ), 228 'error' 229 ); 230 231 return false; 232 } 233 234 if (empty($data['dbsslcert'])) { 235 Factory::getApplication()->enqueueMessage( 236 Text::sprintf( 237 'COM_CONFIG_ERROR_DATABASE_ENCRYPTION_FILE_FIELD_EMPTY', 238 Text::_('COM_CONFIG_FIELD_DATABASE_ENCRYPTION_CERT_LABEL') 239 ), 240 'error' 241 ); 242 243 return false; 244 } 245 246 if (!File::exists(Path::clean($data['dbsslcert']))) { 247 Factory::getApplication()->enqueueMessage( 248 Text::sprintf( 249 'COM_CONFIG_ERROR_DATABASE_ENCRYPTION_FILE_FIELD_BAD', 250 Text::_('COM_CONFIG_FIELD_DATABASE_ENCRYPTION_CERT_LABEL') 251 ), 252 'error' 253 ); 254 255 return false; 256 } 257 } else { 258 // Reset unused options 259 if (!empty($data['dbsslkey'])) { 260 $data['dbsslkey'] = ''; 261 } 262 263 if (!empty($data['dbsslcert'])) { 264 $data['dbsslcert'] = ''; 265 } 266 } 267 } 268 269 return $data; 270 } 271 272 /** 273 * Method to save the configuration data. 274 * 275 * @param array $data An array containing all global config data. 276 * 277 * @return boolean True on success, false on failure. 278 * 279 * @since 1.6 280 */ 281 public function save($data) 282 { 283 $app = Factory::getApplication(); 284 285 // Try to load the values from the configuration file 286 foreach ($this->protectedConfigurationFields as $fieldKey) { 287 if (!isset($data[$fieldKey])) { 288 $data[$fieldKey] = $app->get($fieldKey, ''); 289 } 290 } 291 292 // Check that we aren't setting wrong database configuration 293 $options = array( 294 'driver' => $data['dbtype'], 295 'host' => $data['host'], 296 'user' => $data['user'], 297 'password' => $data['password'], 298 'database' => $data['db'], 299 'prefix' => $data['dbprefix'], 300 ); 301 302 if ((int) $data['dbencryption'] !== 0) { 303 $options['ssl'] = [ 304 'enable' => true, 305 'verify_server_cert' => (bool) $data['dbsslverifyservercert'], 306 ]; 307 308 foreach (['cipher', 'ca', 'key', 'cert'] as $value) { 309 $confVal = trim($data['dbssl' . $value]); 310 311 if ($confVal !== '') { 312 $options['ssl'][$value] = $confVal; 313 } 314 } 315 } 316 317 try { 318 $revisedDbo = DatabaseDriver::getInstance($options); 319 $revisedDbo->getVersion(); 320 } catch (\Exception $e) { 321 $app->enqueueMessage(Text::sprintf('COM_CONFIG_ERROR_DATABASE_NOT_AVAILABLE', $e->getCode(), $e->getMessage()), 'error'); 322 323 return false; 324 } 325 326 if ((int) $data['dbencryption'] !== 0 && empty($revisedDbo->getConnectionEncryption())) { 327 if ($revisedDbo->isConnectionEncryptionSupported()) { 328 Factory::getApplication()->enqueueMessage(Text::_('COM_CONFIG_ERROR_DATABASE_ENCRYPTION_CONN_NOT_ENCRYPT'), 'error'); 329 } else { 330 Factory::getApplication()->enqueueMessage(Text::_('COM_CONFIG_ERROR_DATABASE_ENCRYPTION_SRV_NOT_SUPPORTS'), 'error'); 331 } 332 333 return false; 334 } 335 336 // Check if we can set the Force SSL option 337 if ((int) $data['force_ssl'] !== 0 && (int) $data['force_ssl'] !== (int) $app->get('force_ssl', '0')) { 338 try { 339 // Make an HTTPS request to check if the site is available in HTTPS. 340 $host = Uri::getInstance()->getHost(); 341 $options = new Registry(); 342 $options->set('userAgent', 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:41.0) Gecko/20100101 Firefox/41.0'); 343 344 // Do not check for valid server certificate here, leave this to the user, moreover disable using a proxy if any is configured. 345 $options->set( 346 'transport.curl', 347 array( 348 CURLOPT_SSL_VERIFYPEER => false, 349 CURLOPT_SSL_VERIFYHOST => false, 350 CURLOPT_PROXY => null, 351 CURLOPT_PROXYUSERPWD => null, 352 ) 353 ); 354 $response = HttpFactory::getHttp($options)->get('https://' . $host . Uri::root(true) . '/', array('Host' => $host), 10); 355 356 // If available in HTTPS check also the status code. 357 if (!in_array($response->code, array(200, 503, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 401), true)) { 358 throw new \RuntimeException(Text::_('COM_CONFIG_ERROR_SSL_NOT_AVAILABLE_HTTP_CODE')); 359 } 360 } catch (\RuntimeException $e) { 361 $data['force_ssl'] = 0; 362 363 // Also update the user state 364 $app->setUserState('com_config.config.global.data.force_ssl', 0); 365 366 // Inform the user 367 $app->enqueueMessage(Text::sprintf('COM_CONFIG_ERROR_SSL_NOT_AVAILABLE', $e->getMessage()), 'warning'); 368 } 369 } 370 371 // Save the rules 372 if (isset($data['rules'])) { 373 $rules = new Rules($data['rules']); 374 375 // Check that we aren't removing our Super User permission 376 // Need to get groups from database, since they might have changed 377 $myGroups = Access::getGroupsByUser(Factory::getUser()->get('id')); 378 $myRules = $rules->getData(); 379 $hasSuperAdmin = $myRules['core.admin']->allow($myGroups); 380 381 if (!$hasSuperAdmin) { 382 $app->enqueueMessage(Text::_('COM_CONFIG_ERROR_REMOVING_SUPER_ADMIN'), 'error'); 383 384 return false; 385 } 386 387 $asset = Table::getInstance('asset'); 388 389 if ($asset->loadByName('root.1')) { 390 $asset->rules = (string) $rules; 391 392 if (!$asset->check() || !$asset->store()) { 393 $app->enqueueMessage($asset->getError(), 'error'); 394 395 return false; 396 } 397 } else { 398 $app->enqueueMessage(Text::_('COM_CONFIG_ERROR_ROOT_ASSET_NOT_FOUND'), 'error'); 399 400 return false; 401 } 402 403 unset($data['rules']); 404 } 405 406 // Save the text filters 407 if (isset($data['filters'])) { 408 $registry = new Registry(array('filters' => $data['filters'])); 409 410 $extension = Table::getInstance('extension'); 411 412 // Get extension_id 413 $extensionId = $extension->find(array('name' => 'com_config')); 414 415 if ($extension->load((int) $extensionId)) { 416 $extension->params = (string) $registry; 417 418 if (!$extension->check() || !$extension->store()) { 419 $app->enqueueMessage($extension->getError(), 'error'); 420 421 return false; 422 } 423 } else { 424 $app->enqueueMessage(Text::_('COM_CONFIG_ERROR_CONFIG_EXTENSION_NOT_FOUND'), 'error'); 425 426 return false; 427 } 428 429 unset($data['filters']); 430 } 431 432 // Get the previous configuration. 433 $prev = new \JConfig(); 434 $prev = ArrayHelper::fromObject($prev); 435 436 // Merge the new data in. We do this to preserve values that were not in the form. 437 $data = array_merge($prev, $data); 438 439 /* 440 * Perform miscellaneous options based on configuration settings/changes. 441 */ 442 443 // Escape the offline message if present. 444 if (isset($data['offline_message'])) { 445 $data['offline_message'] = OutputFilter::ampReplace($data['offline_message']); 446 } 447 448 // Purge the database session table if we are changing to the database handler. 449 if ($prev['session_handler'] != 'database' && $data['session_handler'] == 'database') { 450 $db = $this->getDatabase(); 451 $query = $db->getQuery(true) 452 ->delete($db->quoteName('#__session')) 453 ->where($db->quoteName('time') . ' < ' . (time() - 1)); 454 $db->setQuery($query); 455 $db->execute(); 456 } 457 458 // Purge the database session table if we are disabling session metadata 459 if ($prev['session_metadata'] == 1 && $data['session_metadata'] == 0) { 460 try { 461 // If we are are using the session handler, purge the extra columns, otherwise truncate the whole session table 462 if ($data['session_handler'] === 'database') { 463 $revisedDbo->setQuery( 464 $revisedDbo->getQuery(true) 465 ->update('#__session') 466 ->set( 467 [ 468 $revisedDbo->quoteName('client_id') . ' = 0', 469 $revisedDbo->quoteName('guest') . ' = NULL', 470 $revisedDbo->quoteName('userid') . ' = NULL', 471 $revisedDbo->quoteName('username') . ' = NULL', 472 ] 473 ) 474 )->execute(); 475 } else { 476 $revisedDbo->truncateTable('#__session'); 477 } 478 } catch (\RuntimeException $e) { 479 /* 480 * The database API logs errors on failures so we don't need to add any error handling mechanisms here. 481 * Also, this data won't be added or checked anymore once the configuration is saved, so it'll purge itself 482 * through normal garbage collection anyway or if not using the database handler someone can purge the 483 * table on their own. Either way, carry on Soldier! 484 */ 485 } 486 } 487 488 // Ensure custom session file path exists or try to create it if changed 489 if (!empty($data['session_filesystem_path'])) { 490 $currentPath = $prev['session_filesystem_path'] ?? null; 491 492 if ($currentPath) { 493 $currentPath = Path::clean($currentPath); 494 } 495 496 $data['session_filesystem_path'] = Path::clean($data['session_filesystem_path']); 497 498 if ($currentPath !== $data['session_filesystem_path']) { 499 if (!Folder::exists($data['session_filesystem_path']) && !Folder::create($data['session_filesystem_path'])) { 500 try { 501 Log::add( 502 Text::sprintf( 503 'COM_CONFIG_ERROR_CUSTOM_SESSION_FILESYSTEM_PATH_NOTWRITABLE_USING_DEFAULT', 504 $data['session_filesystem_path'] 505 ), 506 Log::WARNING, 507 'jerror' 508 ); 509 } catch (\RuntimeException $logException) { 510 $app->enqueueMessage( 511 Text::sprintf( 512 'COM_CONFIG_ERROR_CUSTOM_SESSION_FILESYSTEM_PATH_NOTWRITABLE_USING_DEFAULT', 513 $data['session_filesystem_path'] 514 ), 515 'warning' 516 ); 517 } 518 519 $data['session_filesystem_path'] = $currentPath; 520 } 521 } 522 } 523 524 // Set the shared session configuration 525 if (isset($data['shared_session'])) { 526 $currentShared = $prev['shared_session'] ?? '0'; 527 528 // Has the user enabled shared sessions? 529 if ($data['shared_session'] == 1 && $currentShared == 0) { 530 // Generate a random shared session name 531 $data['session_name'] = UserHelper::genRandomPassword(16); 532 } 533 534 // Has the user disabled shared sessions? 535 if ($data['shared_session'] == 0 && $currentShared == 1) { 536 // Remove the session name value 537 unset($data['session_name']); 538 } 539 } 540 541 // Set the shared session configuration 542 if (isset($data['shared_session'])) { 543 $currentShared = $prev['shared_session'] ?? '0'; 544 545 // Has the user enabled shared sessions? 546 if ($data['shared_session'] == 1 && $currentShared == 0) { 547 // Generate a random shared session name 548 $data['session_name'] = UserHelper::genRandomPassword(16); 549 } 550 551 // Has the user disabled shared sessions? 552 if ($data['shared_session'] == 0 && $currentShared == 1) { 553 // Remove the session name value 554 unset($data['session_name']); 555 } 556 } 557 558 if (empty($data['cache_handler'])) { 559 $data['caching'] = 0; 560 } 561 562 /* 563 * Look for a custom cache_path 564 * First check if a path is given in the submitted data, then check if a path exists in the previous data, otherwise use the default 565 */ 566 if (!empty($data['cache_path'])) { 567 $path = $data['cache_path']; 568 } elseif (!empty($prev['cache_path'])) { 569 $path = $prev['cache_path']; 570 } else { 571 $path = JPATH_CACHE; 572 } 573 574 // Give a warning if the cache-folder can not be opened 575 if ($data['caching'] > 0 && $data['cache_handler'] == 'file' && @opendir($path) == false) { 576 $error = true; 577 578 // If a custom path is in use, try using the system default instead of disabling cache 579 if ($path !== JPATH_CACHE && @opendir(JPATH_CACHE) != false) { 580 try { 581 Log::add( 582 Text::sprintf('COM_CONFIG_ERROR_CUSTOM_CACHE_PATH_NOTWRITABLE_USING_DEFAULT', $path, JPATH_CACHE), 583 Log::WARNING, 584 'jerror' 585 ); 586 } catch (\RuntimeException $logException) { 587 $app->enqueueMessage( 588 Text::sprintf('COM_CONFIG_ERROR_CUSTOM_CACHE_PATH_NOTWRITABLE_USING_DEFAULT', $path, JPATH_CACHE), 589 'warning' 590 ); 591 } 592 593 $path = JPATH_CACHE; 594 $error = false; 595 596 $data['cache_path'] = ''; 597 } 598 599 if ($error) { 600 try { 601 Log::add(Text::sprintf('COM_CONFIG_ERROR_CACHE_PATH_NOTWRITABLE', $path), Log::WARNING, 'jerror'); 602 } catch (\RuntimeException $exception) { 603 $app->enqueueMessage(Text::sprintf('COM_CONFIG_ERROR_CACHE_PATH_NOTWRITABLE', $path), 'warning'); 604 } 605 606 $data['caching'] = 0; 607 } 608 } 609 610 // Did the user remove their custom cache path? Don't save the variable to the config 611 if (empty($data['cache_path'])) { 612 unset($data['cache_path']); 613 } 614 615 // Clean the cache if disabled but previously enabled or changing cache handlers; these operations use the `$prev` data already in memory 616 if ((!$data['caching'] && $prev['caching']) || $data['cache_handler'] !== $prev['cache_handler']) { 617 try { 618 Factory::getCache()->clean(); 619 } catch (CacheConnectingException $exception) { 620 try { 621 Log::add(Text::_('COM_CONFIG_ERROR_CACHE_CONNECTION_FAILED'), Log::WARNING, 'jerror'); 622 } catch (\RuntimeException $logException) { 623 $app->enqueueMessage(Text::_('COM_CONFIG_ERROR_CACHE_CONNECTION_FAILED'), 'warning'); 624 } 625 } catch (UnsupportedCacheException $exception) { 626 try { 627 Log::add(Text::_('COM_CONFIG_ERROR_CACHE_DRIVER_UNSUPPORTED'), Log::WARNING, 'jerror'); 628 } catch (\RuntimeException $logException) { 629 $app->enqueueMessage(Text::_('COM_CONFIG_ERROR_CACHE_DRIVER_UNSUPPORTED'), 'warning'); 630 } 631 } 632 } 633 634 /* 635 * Look for a custom tmp_path 636 * First check if a path is given in the submitted data, then check if a path exists in the previous data, otherwise use the default 637 */ 638 $defaultTmpPath = JPATH_ROOT . '/tmp'; 639 640 if (!empty($data['tmp_path'])) { 641 $path = $data['tmp_path']; 642 } elseif (!empty($prev['tmp_path'])) { 643 $path = $prev['tmp_path']; 644 } else { 645 $path = $defaultTmpPath; 646 } 647 648 $path = Path::clean($path); 649 650 // Give a warning if the tmp-folder is not valid or not writable 651 if (!is_dir($path) || !is_writable($path)) { 652 $error = true; 653 654 // If a custom path is in use, try using the system default tmp path 655 if ($path !== $defaultTmpPath && is_dir($defaultTmpPath) && is_writable($defaultTmpPath)) { 656 try { 657 Log::add( 658 Text::sprintf('COM_CONFIG_ERROR_CUSTOM_TEMP_PATH_NOTWRITABLE_USING_DEFAULT', $path, $defaultTmpPath), 659 Log::WARNING, 660 'jerror' 661 ); 662 } catch (\RuntimeException $logException) { 663 $app->enqueueMessage( 664 Text::sprintf('COM_CONFIG_ERROR_CUSTOM_TEMP_PATH_NOTWRITABLE_USING_DEFAULT', $path, $defaultTmpPath), 665 'warning' 666 ); 667 } 668 669 $error = false; 670 671 $data['tmp_path'] = $defaultTmpPath; 672 } 673 674 if ($error) { 675 try { 676 Log::add(Text::sprintf('COM_CONFIG_ERROR_TMP_PATH_NOTWRITABLE', $path), Log::WARNING, 'jerror'); 677 } catch (\RuntimeException $exception) { 678 $app->enqueueMessage(Text::sprintf('COM_CONFIG_ERROR_TMP_PATH_NOTWRITABLE', $path), 'warning'); 679 } 680 } 681 } 682 683 /* 684 * Look for a custom log_path 685 * First check if a path is given in the submitted data, then check if a path exists in the previous data, otherwise use the default 686 */ 687 $defaultLogPath = JPATH_ADMINISTRATOR . '/logs'; 688 689 if (!empty($data['log_path'])) { 690 $path = $data['log_path']; 691 } elseif (!empty($prev['log_path'])) { 692 $path = $prev['log_path']; 693 } else { 694 $path = $defaultLogPath; 695 } 696 697 $path = Path::clean($path); 698 699 // Give a warning if the log-folder is not valid or not writable 700 if (!is_dir($path) || !is_writable($path)) { 701 $error = true; 702 703 // If a custom path is in use, try using the system default log path 704 if ($path !== $defaultLogPath && is_dir($defaultLogPath) && is_writable($defaultLogPath)) { 705 try { 706 Log::add( 707 Text::sprintf('COM_CONFIG_ERROR_CUSTOM_LOG_PATH_NOTWRITABLE_USING_DEFAULT', $path, $defaultLogPath), 708 Log::WARNING, 709 'jerror' 710 ); 711 } catch (\RuntimeException $logException) { 712 $app->enqueueMessage( 713 Text::sprintf('COM_CONFIG_ERROR_CUSTOM_LOG_PATH_NOTWRITABLE_USING_DEFAULT', $path, $defaultLogPath), 714 'warning' 715 ); 716 } 717 718 $error = false; 719 $data['log_path'] = $defaultLogPath; 720 } 721 722 if ($error) { 723 try { 724 Log::add(Text::sprintf('COM_CONFIG_ERROR_LOG_PATH_NOTWRITABLE', $path), Log::WARNING, 'jerror'); 725 } catch (\RuntimeException $exception) { 726 $app->enqueueMessage(Text::sprintf('COM_CONFIG_ERROR_LOG_PATH_NOTWRITABLE', $path), 'warning'); 727 } 728 } 729 } 730 731 // Create the new configuration object. 732 $config = new Registry($data); 733 734 // Overwrite webservices cors settings 735 $app->set('cors', $data['cors']); 736 $app->set('cors_allow_origin', $data['cors_allow_origin']); 737 $app->set('cors_allow_headers', $data['cors_allow_headers']); 738 $app->set('cors_allow_methods', $data['cors_allow_methods']); 739 740 // Clear cache of com_config component. 741 $this->cleanCache('_system'); 742 743 $result = $app->triggerEvent('onApplicationBeforeSave', array($config)); 744 745 // Store the data. 746 if (in_array(false, $result, true)) { 747 throw new \RuntimeException(Text::_('COM_CONFIG_ERROR_UNKNOWN_BEFORE_SAVING')); 748 } 749 750 // Write the configuration file. 751 $result = $this->writeConfigFile($config); 752 753 // Trigger the after save event. 754 $app->triggerEvent('onApplicationAfterSave', array($config)); 755 756 return $result; 757 } 758 759 /** 760 * Method to unset the root_user value from configuration data. 761 * 762 * This method will load the global configuration data straight from 763 * JConfig and remove the root_user value for security, then save the configuration. 764 * 765 * @return boolean True on success, false on failure. 766 * 767 * @since 1.6 768 */ 769 public function removeroot() 770 { 771 $app = Factory::getApplication(); 772 773 // Get the previous configuration. 774 $prev = new \JConfig(); 775 $prev = ArrayHelper::fromObject($prev); 776 777 // Create the new configuration object, and unset the root_user property 778 unset($prev['root_user']); 779 $config = new Registry($prev); 780 781 $result = $app->triggerEvent('onApplicationBeforeSave', array($config)); 782 783 // Store the data. 784 if (in_array(false, $result, true)) { 785 throw new \RuntimeException(Text::_('COM_CONFIG_ERROR_UNKNOWN_BEFORE_SAVING')); 786 } 787 788 // Write the configuration file. 789 $result = $this->writeConfigFile($config); 790 791 // Trigger the after save event. 792 $app->triggerEvent('onApplicationAfterSave', array($config)); 793 794 return $result; 795 } 796 797 /** 798 * Method to write the configuration to a file. 799 * 800 * @param Registry $config A Registry object containing all global config data. 801 * 802 * @return boolean True on success, false on failure. 803 * 804 * @since 2.5.4 805 * @throws \RuntimeException 806 */ 807 private function writeConfigFile(Registry $config) 808 { 809 // Set the configuration file path. 810 $file = JPATH_CONFIGURATION . '/configuration.php'; 811 812 $app = Factory::getApplication(); 813 814 // Attempt to make the file writeable. 815 if (Path::isOwner($file) && !Path::setPermissions($file, '0644')) { 816 $app->enqueueMessage(Text::_('COM_CONFIG_ERROR_CONFIGURATION_PHP_NOTWRITABLE'), 'notice'); 817 } 818 819 // Attempt to write the configuration file as a PHP class named JConfig. 820 $configuration = $config->toString('PHP', array('class' => 'JConfig', 'closingtag' => false)); 821 822 if (!File::write($file, $configuration)) { 823 throw new \RuntimeException(Text::_('COM_CONFIG_ERROR_WRITE_FAILED')); 824 } 825 826 // Attempt to make the file unwriteable. 827 if (Path::isOwner($file) && !Path::setPermissions($file, '0444')) { 828 $app->enqueueMessage(Text::_('COM_CONFIG_ERROR_CONFIGURATION_PHP_NOTUNWRITABLE'), 'notice'); 829 } 830 831 return true; 832 } 833 834 /** 835 * Method to store the permission values in the asset table. 836 * 837 * This method will get an array with permission key value pairs and transform it 838 * into json and update the asset table in the database. 839 * 840 * @param string $permission Need an array with Permissions (component, rule, value and title) 841 * 842 * @return array|bool A list of result data or false on failure. 843 * 844 * @since 3.5 845 */ 846 public function storePermissions($permission = null) 847 { 848 $app = Factory::getApplication(); 849 $user = Factory::getUser(); 850 851 if (is_null($permission)) { 852 // Get data from input. 853 $permission = array( 854 'component' => $app->input->Json->get('comp'), 855 'action' => $app->input->Json->get('action'), 856 'rule' => $app->input->Json->get('rule'), 857 'value' => $app->input->Json->get('value'), 858 'title' => $app->input->Json->get('title', '', 'RAW') 859 ); 860 } 861 862 // We are creating a new item so we don't have an item id so don't allow. 863 if (substr($permission['component'], -6) === '.false') { 864 $app->enqueueMessage(Text::_('JLIB_RULES_SAVE_BEFORE_CHANGE_PERMISSIONS'), 'error'); 865 866 return false; 867 } 868 869 // Check if the user is authorized to do this. 870 if (!$user->authorise('core.admin', $permission['component'])) { 871 $app->enqueueMessage(Text::_('JERROR_ALERTNOAUTHOR'), 'error'); 872 873 return false; 874 } 875 876 $permission['component'] = empty($permission['component']) ? 'root.1' : $permission['component']; 877 878 // Current view is global config? 879 $isGlobalConfig = $permission['component'] === 'root.1'; 880 881 // Check if changed group has Super User permissions. 882 $isSuperUserGroupBefore = Access::checkGroup($permission['rule'], 'core.admin'); 883 884 // Check if current user belongs to changed group. 885 $currentUserBelongsToGroup = in_array((int) $permission['rule'], $user->groups) ? true : false; 886 887 // Get current user groups tree. 888 $currentUserGroupsTree = Access::getGroupsByUser($user->id, true); 889 890 // Check if current user belongs to changed group. 891 $currentUserSuperUser = $user->authorise('core.admin'); 892 893 // If user is not Super User cannot change the permissions of a group it belongs to. 894 if (!$currentUserSuperUser && $currentUserBelongsToGroup) { 895 $app->enqueueMessage(Text::_('JLIB_USER_ERROR_CANNOT_CHANGE_OWN_GROUPS'), 'error'); 896 897 return false; 898 } 899 900 // If user is not Super User cannot change the permissions of a group it belongs to. 901 if (!$currentUserSuperUser && in_array((int) $permission['rule'], $currentUserGroupsTree)) { 902 $app->enqueueMessage(Text::_('JLIB_USER_ERROR_CANNOT_CHANGE_OWN_PARENT_GROUPS'), 'error'); 903 904 return false; 905 } 906 907 // If user is not Super User cannot change the permissions of a Super User Group. 908 if (!$currentUserSuperUser && $isSuperUserGroupBefore && !$currentUserBelongsToGroup) { 909 $app->enqueueMessage(Text::_('JLIB_USER_ERROR_CANNOT_CHANGE_SUPER_USER'), 'error'); 910 911 return false; 912 } 913 914 // If user is not Super User cannot change the Super User permissions in any group it belongs to. 915 if ($isSuperUserGroupBefore && $currentUserBelongsToGroup && $permission['action'] === 'core.admin') { 916 $app->enqueueMessage(Text::_('JLIB_USER_ERROR_CANNOT_DEMOTE_SELF'), 'error'); 917 918 return false; 919 } 920 921 try { 922 /** @var Asset $asset */ 923 $asset = Table::getInstance('asset'); 924 $result = $asset->loadByName($permission['component']); 925 926 if ($result === false) { 927 $data = array($permission['action'] => array($permission['rule'] => $permission['value'])); 928 929 $rules = new Rules($data); 930 $asset->rules = (string) $rules; 931 $asset->name = (string) $permission['component']; 932 $asset->title = (string) $permission['title']; 933 934 // Get the parent asset id so we have a correct tree. 935 /** @var Asset $parentAsset */ 936 $parentAsset = Table::getInstance('Asset'); 937 938 if (strpos($asset->name, '.') !== false) { 939 $assetParts = explode('.', $asset->name); 940 $parentAsset->loadByName($assetParts[0]); 941 $parentAssetId = $parentAsset->id; 942 } else { 943 $parentAssetId = $parentAsset->getRootId(); 944 } 945 946 /** 947 * @todo: incorrect ACL stored 948 * When changing a permission of an item that doesn't have a row in the asset table the row a new row is created. 949 * This works fine for item <-> component <-> global config scenario and component <-> global config scenario. 950 * But doesn't work properly for item <-> section(s) <-> component <-> global config scenario, 951 * because a wrong parent asset id (the component) is stored. 952 * Happens when there is no row in the asset table (ex: deleted or not created on update). 953 */ 954 955 $asset->setLocation($parentAssetId, 'last-child'); 956 } else { 957 // Decode the rule settings. 958 $temp = json_decode($asset->rules, true); 959 960 // Check if a new value is to be set. 961 if (isset($permission['value'])) { 962 // Check if we already have an action entry. 963 if (!isset($temp[$permission['action']])) { 964 $temp[$permission['action']] = array(); 965 } 966 967 // Check if we already have a rule entry. 968 if (!isset($temp[$permission['action']][$permission['rule']])) { 969 $temp[$permission['action']][$permission['rule']] = array(); 970 } 971 972 // Set the new permission. 973 $temp[$permission['action']][$permission['rule']] = (int) $permission['value']; 974 975 // Check if we have an inherited setting. 976 if ($permission['value'] === '') { 977 unset($temp[$permission['action']][$permission['rule']]); 978 } 979 980 // Check if we have any rules. 981 if (!$temp[$permission['action']]) { 982 unset($temp[$permission['action']]); 983 } 984 } else { 985 // There is no value so remove the action as it's not needed. 986 unset($temp[$permission['action']]); 987 } 988 989 $asset->rules = json_encode($temp, JSON_FORCE_OBJECT); 990 } 991 992 if (!$asset->check() || !$asset->store()) { 993 $app->enqueueMessage(Text::_('JLIB_UNKNOWN'), 'error'); 994 995 return false; 996 } 997 } catch (\Exception $e) { 998 $app->enqueueMessage($e->getMessage(), 'error'); 999 1000 return false; 1001 } 1002 1003 // All checks done. 1004 $result = array( 1005 'text' => '', 1006 'class' => '', 1007 'result' => true, 1008 ); 1009 1010 // Show the current effective calculated permission considering current group, path and cascade. 1011 1012 try { 1013 // The database instance 1014 $db = $this->getDatabase(); 1015 1016 // Get the asset id by the name of the component. 1017 $query = $db->getQuery(true) 1018 ->select($db->quoteName('id')) 1019 ->from($db->quoteName('#__assets')) 1020 ->where($db->quoteName('name') . ' = :component') 1021 ->bind(':component', $permission['component']); 1022 1023 $db->setQuery($query); 1024 1025 $assetId = (int) $db->loadResult(); 1026 1027 // Fetch the parent asset id. 1028 $parentAssetId = null; 1029 1030 /** 1031 * @todo: incorrect info 1032 * When creating a new item (not saving) it uses the calculated permissions from the component (item <-> component <-> global config). 1033 * But if we have a section too (item <-> section(s) <-> component <-> global config) this is not correct. 1034 * Also, currently it uses the component permission, but should use the calculated permissions for a child of the component/section. 1035 */ 1036 1037 // If not in global config we need the parent_id asset to calculate permissions. 1038 if (!$isGlobalConfig) { 1039 // In this case we need to get the component rules too. 1040 $query->clear() 1041 ->select($db->quoteName('parent_id')) 1042 ->from($db->quoteName('#__assets')) 1043 ->where($db->quoteName('id') . ' = :assetid') 1044 ->bind(':assetid', $assetId, ParameterType::INTEGER); 1045 1046 $db->setQuery($query); 1047 1048 $parentAssetId = (int) $db->loadResult(); 1049 } 1050 1051 // Get the group parent id of the current group. 1052 $rule = (int) $permission['rule']; 1053 $query->clear() 1054 ->select($db->quoteName('parent_id')) 1055 ->from($db->quoteName('#__usergroups')) 1056 ->where($db->quoteName('id') . ' = :rule') 1057 ->bind(':rule', $rule, ParameterType::INTEGER); 1058 1059 $db->setQuery($query); 1060 1061 $parentGroupId = (int) $db->loadResult(); 1062 1063 // Count the number of child groups of the current group. 1064 $query->clear() 1065 ->select('COUNT(' . $db->quoteName('id') . ')') 1066 ->from($db->quoteName('#__usergroups')) 1067 ->where($db->quoteName('parent_id') . ' = :rule') 1068 ->bind(':rule', $rule, ParameterType::INTEGER); 1069 1070 $db->setQuery($query); 1071 1072 $totalChildGroups = (int) $db->loadResult(); 1073 } catch (\Exception $e) { 1074 $app->enqueueMessage($e->getMessage(), 'error'); 1075 1076 return false; 1077 } 1078 1079 // Clear access statistics. 1080 Access::clearStatics(); 1081 1082 // After current group permission is changed we need to check again if the group has Super User permissions. 1083 $isSuperUserGroupAfter = Access::checkGroup($permission['rule'], 'core.admin'); 1084 1085 // Get the rule for just this asset (non-recursive) and get the actual setting for the action for this group. 1086 $assetRule = Access::getAssetRules($assetId, false, false)->allow($permission['action'], $permission['rule']); 1087 1088 // Get the group, group parent id, and group global config recursive calculated permission for the chosen action. 1089 $inheritedGroupRule = Access::checkGroup($permission['rule'], $permission['action'], $assetId); 1090 1091 if (!empty($parentAssetId)) { 1092 $inheritedGroupParentAssetRule = Access::checkGroup($permission['rule'], $permission['action'], $parentAssetId); 1093 } else { 1094 $inheritedGroupParentAssetRule = null; 1095 } 1096 1097 $inheritedParentGroupRule = !empty($parentGroupId) ? Access::checkGroup($parentGroupId, $permission['action'], $assetId) : null; 1098 1099 // Current group is a Super User group, so calculated setting is "Allowed (Super User)". 1100 if ($isSuperUserGroupAfter) { 1101 $result['class'] = 'badge bg-success'; 1102 $result['text'] = '<span class="icon-lock icon-white" aria-hidden="true"></span>' . Text::_('JLIB_RULES_ALLOWED_ADMIN'); 1103 } else { 1104 // Not super user. 1105 // First get the real recursive calculated setting and add (Inherited) to it. 1106 1107 // If recursive calculated setting is "Denied" or null. Calculated permission is "Not Allowed (Inherited)". 1108 if ($inheritedGroupRule === null || $inheritedGroupRule === false) { 1109 $result['class'] = 'badge bg-danger'; 1110 $result['text'] = Text::_('JLIB_RULES_NOT_ALLOWED_INHERITED'); 1111 } else { 1112 // If recursive calculated setting is "Allowed". Calculated permission is "Allowed (Inherited)". 1113 $result['class'] = 'badge bg-success'; 1114 $result['text'] = Text::_('JLIB_RULES_ALLOWED_INHERITED'); 1115 } 1116 1117 // Second part: Overwrite the calculated permissions labels if there is an explicit permission in the current group. 1118 1119 /** 1120 * @todo: incorrect info 1121 * If a component has a permission that doesn't exists in global config (ex: frontend editing in com_modules) by default 1122 * we get "Not Allowed (Inherited)" when we should get "Not Allowed (Default)". 1123 */ 1124 1125 // If there is an explicit permission "Not Allowed". Calculated permission is "Not Allowed". 1126 if ($assetRule === false) { 1127 $result['class'] = 'badge bg-danger'; 1128 $result['text'] = Text::_('JLIB_RULES_NOT_ALLOWED'); 1129 } elseif ($assetRule === true) { 1130 // If there is an explicit permission is "Allowed". Calculated permission is "Allowed". 1131 $result['class'] = 'badge bg-success'; 1132 $result['text'] = Text::_('JLIB_RULES_ALLOWED'); 1133 } 1134 1135 // Third part: Overwrite the calculated permissions labels for special cases. 1136 1137 // Global configuration with "Not Set" permission. Calculated permission is "Not Allowed (Default)". 1138 if (empty($parentGroupId) && $isGlobalConfig === true && $assetRule === null) { 1139 $result['class'] = 'badge bg-danger'; 1140 $result['text'] = Text::_('JLIB_RULES_NOT_ALLOWED_DEFAULT'); 1141 } elseif ($inheritedGroupParentAssetRule === false || $inheritedParentGroupRule === false) { 1142 /** 1143 * Component/Item with explicit "Denied" permission at parent Asset (Category, Component or Global config) configuration. 1144 * Or some parent group has an explicit "Denied". 1145 * Calculated permission is "Not Allowed (Locked)". 1146 */ 1147 $result['class'] = 'badge bg-danger'; 1148 $result['text'] = '<span class="icon-lock icon-white" aria-hidden="true"></span>' . Text::_('JLIB_RULES_NOT_ALLOWED_LOCKED'); 1149 } 1150 } 1151 1152 // If removed or added super user from group, we need to refresh the page to recalculate all settings. 1153 if ($isSuperUserGroupBefore != $isSuperUserGroupAfter) { 1154 $app->enqueueMessage(Text::_('JLIB_RULES_NOTICE_RECALCULATE_GROUP_PERMISSIONS'), 'notice'); 1155 } 1156 1157 // If this group has child groups, we need to refresh the page to recalculate the child settings. 1158 if ($totalChildGroups > 0) { 1159 $app->enqueueMessage(Text::_('JLIB_RULES_NOTICE_RECALCULATE_GROUP_CHILDS_PERMISSIONS'), 'notice'); 1160 } 1161 1162 return $result; 1163 } 1164 1165 /** 1166 * Method to send a test mail which is called via an AJAX request 1167 * 1168 * @return boolean 1169 * 1170 * @since 3.5 1171 */ 1172 public function sendTestMail() 1173 { 1174 // Set the new values to test with the current settings 1175 $app = Factory::getApplication(); 1176 $user = Factory::getUser(); 1177 $input = $app->input->json; 1178 $smtppass = $input->get('smtppass', null, 'RAW'); 1179 1180 $app->set('smtpauth', $input->get('smtpauth')); 1181 $app->set('smtpuser', $input->get('smtpuser', '', 'STRING')); 1182 $app->set('smtphost', $input->get('smtphost')); 1183 $app->set('smtpsecure', $input->get('smtpsecure')); 1184 $app->set('smtpport', $input->get('smtpport')); 1185 $app->set('mailfrom', $input->get('mailfrom', '', 'STRING')); 1186 $app->set('fromname', $input->get('fromname', '', 'STRING')); 1187 $app->set('mailer', $input->get('mailer')); 1188 $app->set('mailonline', $input->get('mailonline')); 1189 1190 // Use smtppass only if it was submitted 1191 if ($smtppass !== null) { 1192 $app->set('smtppass', $smtppass); 1193 } 1194 1195 $mail = Factory::getMailer(); 1196 1197 // Prepare email and try to send it 1198 $mailer = new MailTemplate('com_config.test_mail', $user->getParam('language', $app->get('language')), $mail); 1199 $mailer->addTemplateData( 1200 array( 1201 'sitename' => $app->get('sitename'), 1202 'method' => Text::_('COM_CONFIG_SENDMAIL_METHOD_' . strtoupper($mail->Mailer)) 1203 ) 1204 ); 1205 $mailer->addRecipient($app->get('mailfrom'), $app->get('fromname')); 1206 1207 try { 1208 $mailSent = $mailer->send(); 1209 } catch (MailDisabledException | phpMailerException $e) { 1210 $app->enqueueMessage($e->getMessage(), 'error'); 1211 1212 return false; 1213 } 1214 1215 if ($mailSent === true) { 1216 $methodName = Text::_('COM_CONFIG_SENDMAIL_METHOD_' . strtoupper($mail->Mailer)); 1217 1218 // If JMail send the mail using PHP Mail as fallback. 1219 if ($mail->Mailer !== $app->get('mailer')) { 1220 $app->enqueueMessage(Text::sprintf('COM_CONFIG_SENDMAIL_SUCCESS_FALLBACK', $app->get('mailfrom'), $methodName), 'warning'); 1221 } else { 1222 $app->enqueueMessage(Text::sprintf('COM_CONFIG_SENDMAIL_SUCCESS', $app->get('mailfrom'), $methodName), 'message'); 1223 } 1224 1225 return true; 1226 } 1227 1228 $app->enqueueMessage(Text::_('COM_CONFIG_SENDMAIL_ERROR'), 'error'); 1229 1230 return false; 1231 } 1232 }
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 |