[ 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_joomlaupdate 6 * 7 * @copyright (C) 2012 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\Joomlaupdate\Administrator\Controller; 12 13 use Joomla\CMS\Factory; 14 use Joomla\CMS\Filesystem\File; 15 use Joomla\CMS\Language\Text; 16 use Joomla\CMS\Log\Log; 17 use Joomla\CMS\MVC\Controller\BaseController; 18 use Joomla\CMS\Response\JsonResponse; 19 use Joomla\CMS\Session\Session; 20 use Joomla\Component\Joomlaupdate\Administrator\Model\UpdateModel; 21 22 // phpcs:disable PSR1.Files.SideEffects 23 \defined('_JEXEC') or die; 24 // phpcs:enable PSR1.Files.SideEffects 25 26 /** 27 * The Joomla! update controller for the Update view 28 * 29 * @since 2.5.4 30 */ 31 class UpdateController extends BaseController 32 { 33 /** 34 * Performs the download of the update package 35 * 36 * @return void 37 * 38 * @since 2.5.4 39 */ 40 public function download() 41 { 42 $this->checkToken(); 43 44 $options['format'] = '{DATE}\t{TIME}\t{LEVEL}\t{CODE}\t{MESSAGE}'; 45 $options['text_file'] = 'joomla_update.php'; 46 Log::addLogger($options, Log::INFO, array('Update', 'databasequery', 'jerror')); 47 $user = $this->app->getIdentity(); 48 49 try { 50 Log::add(Text::sprintf('COM_JOOMLAUPDATE_UPDATE_LOG_START', $user->id, $user->name, \JVERSION), Log::INFO, 'Update'); 51 } catch (\RuntimeException $exception) { 52 // Informational log only 53 } 54 55 /** @var \Joomla\Component\Joomlaupdate\Administrator\Model\UpdateModel $model */ 56 $model = $this->getModel('Update'); 57 $result = $model->download(); 58 $file = $result['basename']; 59 60 $message = null; 61 $messageType = null; 62 63 // The validation was not successful so abort. 64 if ($result['check'] === false) { 65 $message = Text::_('COM_JOOMLAUPDATE_VIEW_UPDATE_CHECKSUM_WRONG'); 66 $messageType = 'error'; 67 $url = 'index.php?option=com_joomlaupdate'; 68 69 $this->app->setUserState('com_joomlaupdate.file', null); 70 $this->setRedirect($url, $message, $messageType); 71 72 try { 73 Log::add($message, Log::ERROR, 'Update'); 74 } catch (\RuntimeException $exception) { 75 // Informational log only 76 } 77 78 return; 79 } 80 81 if ($file) { 82 $this->app->setUserState('com_joomlaupdate.file', $file); 83 $url = 'index.php?option=com_joomlaupdate&task=update.install&' . $this->app->getSession()->getFormToken() . '=1'; 84 85 try { 86 Log::add(Text::sprintf('COM_JOOMLAUPDATE_UPDATE_LOG_FILE', $file), Log::INFO, 'Update'); 87 } catch (\RuntimeException $exception) { 88 // Informational log only 89 } 90 } else { 91 $this->app->setUserState('com_joomlaupdate.file', null); 92 $url = 'index.php?option=com_joomlaupdate'; 93 $message = Text::_('COM_JOOMLAUPDATE_VIEW_UPDATE_DOWNLOADFAILED'); 94 $messageType = 'error'; 95 } 96 97 $this->setRedirect($url, $message, $messageType); 98 } 99 100 /** 101 * Start the installation of the new Joomla! version 102 * 103 * @return void 104 * 105 * @since 2.5.4 106 */ 107 public function install() 108 { 109 $this->checkToken('get'); 110 $this->app->setUserState('com_joomlaupdate.oldversion', JVERSION); 111 112 $options['format'] = '{DATE}\t{TIME}\t{LEVEL}\t{CODE}\t{MESSAGE}'; 113 $options['text_file'] = 'joomla_update.php'; 114 Log::addLogger($options, Log::INFO, array('Update', 'databasequery', 'jerror')); 115 116 try { 117 Log::add(Text::_('COM_JOOMLAUPDATE_UPDATE_LOG_INSTALL'), Log::INFO, 'Update'); 118 } catch (\RuntimeException $exception) { 119 // Informational log only 120 } 121 122 /** @var \Joomla\Component\Joomlaupdate\Administrator\Model\UpdateModel $model */ 123 $model = $this->getModel('Update'); 124 125 $file = $this->app->getUserState('com_joomlaupdate.file', null); 126 $model->createRestorationFile($file); 127 128 $this->display(); 129 } 130 131 /** 132 * Finalise the upgrade by running the necessary scripts 133 * 134 * @return void 135 * 136 * @since 2.5.4 137 */ 138 public function finalise() 139 { 140 /* 141 * Finalize with login page. Used for pre-token check versions 142 * to allow updates without problems but with a maximum of security. 143 */ 144 if (!Session::checkToken('get')) { 145 $this->setRedirect('index.php?option=com_joomlaupdate&view=update&layout=finaliseconfirm'); 146 147 return; 148 } 149 150 $options['format'] = '{DATE}\t{TIME}\t{LEVEL}\t{CODE}\t{MESSAGE}'; 151 $options['text_file'] = 'joomla_update.php'; 152 Log::addLogger($options, Log::INFO, array('Update', 'databasequery', 'jerror')); 153 154 try { 155 Log::add(Text::_('COM_JOOMLAUPDATE_UPDATE_LOG_FINALISE'), Log::INFO, 'Update'); 156 } catch (\RuntimeException $exception) { 157 // Informational log only 158 } 159 160 /** @var \Joomla\Component\Joomlaupdate\Administrator\Model\UpdateModel $model */ 161 $model = $this->getModel('Update'); 162 163 $model->finaliseUpgrade(); 164 165 $url = 'index.php?option=com_joomlaupdate&task=update.cleanup&' . Session::getFormToken() . '=1'; 166 $this->setRedirect($url); 167 } 168 169 /** 170 * Clean up after ourselves 171 * 172 * @return void 173 * 174 * @since 2.5.4 175 */ 176 public function cleanup() 177 { 178 /* 179 * Cleanup with login page. Used for pre-token check versions to be able to update 180 * from =< 3.2.7 to allow updates without problems but with a maximum of security. 181 */ 182 if (!Session::checkToken('get')) { 183 $this->setRedirect('index.php?option=com_joomlaupdate&view=update&layout=finaliseconfirm'); 184 185 return; 186 } 187 188 $options['format'] = '{DATE}\t{TIME}\t{LEVEL}\t{CODE}\t{MESSAGE}'; 189 $options['text_file'] = 'joomla_update.php'; 190 Log::addLogger($options, Log::INFO, array('Update', 'databasequery', 'jerror')); 191 192 try { 193 Log::add(Text::_('COM_JOOMLAUPDATE_UPDATE_LOG_CLEANUP'), Log::INFO, 'Update'); 194 } catch (\RuntimeException $exception) { 195 // Informational log only 196 } 197 198 /** @var \Joomla\Component\Joomlaupdate\Administrator\Model\UpdateModel $model */ 199 $model = $this->getModel('Update'); 200 201 $model->cleanUp(); 202 203 $url = 'index.php?option=com_joomlaupdate&view=joomlaupdate&layout=complete'; 204 $this->setRedirect($url); 205 206 try { 207 Log::add(Text::sprintf('COM_JOOMLAUPDATE_UPDATE_LOG_COMPLETE', \JVERSION), Log::INFO, 'Update'); 208 } catch (\RuntimeException $exception) { 209 // Informational log only 210 } 211 } 212 213 /** 214 * Purges updates. 215 * 216 * @return void 217 * 218 * @since 3.0 219 */ 220 public function purge() 221 { 222 // Check for request forgeries 223 $this->checkToken('request'); 224 225 // Purge updates 226 /** @var \Joomla\Component\Joomlaupdate\Administrator\Model\UpdateModel $model */ 227 $model = $this->getModel('Update'); 228 $model->purge(); 229 230 $url = 'index.php?option=com_joomlaupdate'; 231 $this->setRedirect($url, $model->_message); 232 } 233 234 /** 235 * Uploads an update package to the temporary directory, under a random name 236 * 237 * @return void 238 * 239 * @since 3.6.0 240 */ 241 public function upload() 242 { 243 // Check for request forgeries 244 $this->checkToken(); 245 246 // Did a non Super User tried to upload something (a.k.a. pathetic hacking attempt)? 247 $this->app->getIdentity()->authorise('core.admin') or jexit(Text::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN')); 248 249 /** @var \Joomla\Component\Joomlaupdate\Administrator\Model\UpdateModel $model */ 250 $model = $this->getModel('Update'); 251 252 try { 253 $model->upload(); 254 } catch (\RuntimeException $e) { 255 $url = 'index.php?option=com_joomlaupdate'; 256 $this->setRedirect($url, $e->getMessage(), 'error'); 257 258 return; 259 } 260 261 $token = Session::getFormToken(); 262 $url = 'index.php?option=com_joomlaupdate&task=update.captive&' . $token . '=1'; 263 $this->setRedirect($url); 264 } 265 266 /** 267 * Checks there is a valid update package and redirects to the captive view for super admin authentication. 268 * 269 * @return void 270 * 271 * @since 3.6.0 272 */ 273 public function captive() 274 { 275 // Check for request forgeries 276 $this->checkToken('get'); 277 278 // Did a non Super User tried to upload something (a.k.a. pathetic hacking attempt)? 279 if (!$this->app->getIdentity()->authorise('core.admin')) { 280 throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 403); 281 } 282 283 // Do I really have an update package? 284 $tempFile = $this->app->getUserState('com_joomlaupdate.temp_file', null); 285 286 if (empty($tempFile) || !File::exists($tempFile)) { 287 throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 403); 288 } 289 290 $this->input->set('view', 'upload'); 291 $this->input->set('layout', 'captive'); 292 293 $this->display(); 294 } 295 296 /** 297 * Checks the admin has super administrator privileges and then proceeds with the update. 298 * 299 * @return void 300 * 301 * @since 3.6.0 302 */ 303 public function confirm() 304 { 305 // Check for request forgeries 306 $this->checkToken(); 307 308 // Did a non Super User tried to upload something (a.k.a. pathetic hacking attempt)? 309 if (!$this->app->getIdentity()->authorise('core.admin')) { 310 throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 403); 311 } 312 313 /** @var \Joomla\Component\Joomlaupdate\Administrator\Model\UpdateModel $model */ 314 $model = $this->getModel('Update'); 315 316 // Get the captive file before the session resets 317 $tempFile = $this->app->getUserState('com_joomlaupdate.temp_file', null); 318 319 // Do I really have an update package? 320 if (!$model->captiveFileExists()) { 321 throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 403); 322 } 323 324 // Try to log in 325 $credentials = array( 326 'username' => $this->input->post->get('username', '', 'username'), 327 'password' => $this->input->post->get('passwd', '', 'raw'), 328 'secretkey' => $this->input->post->get('secretkey', '', 'raw'), 329 ); 330 331 $result = $model->captiveLogin($credentials); 332 333 if (!$result) { 334 $model->removePackageFiles(); 335 336 throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 403); 337 } 338 339 // Set the update source in the session 340 $this->app->setUserState('com_joomlaupdate.file', basename($tempFile)); 341 342 try { 343 Log::add(Text::sprintf('COM_JOOMLAUPDATE_UPDATE_LOG_FILE', $tempFile), Log::INFO, 'Update'); 344 } catch (\RuntimeException $exception) { 345 // Informational log only 346 } 347 348 // Redirect to the actual update page 349 $url = 'index.php?option=com_joomlaupdate&task=update.install&' . Session::getFormToken() . '=1'; 350 $this->setRedirect($url); 351 } 352 353 /** 354 * Method to display a view. 355 * 356 * @param boolean $cachable If true, the view output will be cached 357 * @param array $urlparams An array of safe URL parameters and their variable types, for valid values see {@link \JFilterInput::clean()}. 358 * 359 * @return static This object to support chaining. 360 * 361 * @since 2.5.4 362 */ 363 public function display($cachable = false, $urlparams = array()) 364 { 365 // Get the document object. 366 $document = $this->app->getDocument(); 367 368 // Set the default view name and format from the Request. 369 $vName = $this->input->get('view', 'update'); 370 $vFormat = $document->getType(); 371 $lName = $this->input->get('layout', 'default', 'string'); 372 373 // Get and render the view. 374 if ($view = $this->getView($vName, $vFormat)) { 375 // Get the model for the view. 376 /** @var \Joomla\Component\Joomlaupdate\Administrator\Model\UpdateModel $model */ 377 $model = $this->getModel('Update'); 378 379 // Push the model into the view (as default). 380 $view->setModel($model, true); 381 $view->setLayout($lName); 382 383 // Push document object into the view. 384 $view->document = $document; 385 $view->display(); 386 } 387 388 return $this; 389 } 390 391 /** 392 * Checks the admin has super administrator privileges and then proceeds with the final & cleanup steps. 393 * 394 * @return void 395 * 396 * @since 3.6.3 397 */ 398 public function finaliseconfirm() 399 { 400 // Check for request forgeries 401 $this->checkToken(); 402 403 // Did a non Super User try do this? 404 if (!$this->app->getIdentity()->authorise('core.admin')) { 405 throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 403); 406 } 407 408 // Get the model 409 /** @var \Joomla\Component\Joomlaupdate\Administrator\Model\UpdateModel $model */ 410 $model = $this->getModel('Update'); 411 412 // Try to log in 413 $credentials = array( 414 'username' => $this->input->post->get('username', '', 'username'), 415 'password' => $this->input->post->get('passwd', '', 'raw'), 416 'secretkey' => $this->input->post->get('secretkey', '', 'raw'), 417 ); 418 419 $result = $model->captiveLogin($credentials); 420 421 // The login fails? 422 if (!$result) { 423 $this->setMessage(Text::_('JGLOBAL_AUTH_INVALID_PASS'), 'warning'); 424 $this->setRedirect('index.php?option=com_joomlaupdate&view=update&layout=finaliseconfirm'); 425 426 return; 427 } 428 429 // Redirect back to the actual finalise page 430 $this->setRedirect('index.php?option=com_joomlaupdate&task=update.finalise&' . Session::getFormToken() . '=1'); 431 } 432 433 /** 434 * Fetch Extension update XML proxy. Used to prevent Access-Control-Allow-Origin errors. 435 * Prints a JSON string. 436 * Called from JS. 437 * 438 * @since 3.10.0 439 * @deprecated 5.0 Use batchextensioncompatibility instead. 440 * 441 * @return void 442 */ 443 public function fetchExtensionCompatibility() 444 { 445 $extensionID = $this->input->get('extension-id', '', 'DEFAULT'); 446 $joomlaTargetVersion = $this->input->get('joomla-target-version', '', 'DEFAULT'); 447 $joomlaCurrentVersion = $this->input->get('joomla-current-version', '', JVERSION); 448 $extensionVersion = $this->input->get('extension-version', '', 'DEFAULT'); 449 450 /** @var \Joomla\Component\Joomlaupdate\Administrator\Model\UpdateModel $model */ 451 $model = $this->getModel('Update'); 452 $upgradeCompatibilityStatus = $model->fetchCompatibility($extensionID, $joomlaTargetVersion); 453 $currentCompatibilityStatus = $model->fetchCompatibility($extensionID, $joomlaCurrentVersion); 454 $upgradeUpdateVersion = false; 455 $currentUpdateVersion = false; 456 457 $upgradeWarning = 0; 458 459 if ($upgradeCompatibilityStatus->state == 1 && !empty($upgradeCompatibilityStatus->compatibleVersions)) { 460 $upgradeUpdateVersion = end($upgradeCompatibilityStatus->compatibleVersions); 461 } 462 463 if ($currentCompatibilityStatus->state == 1 && !empty($currentCompatibilityStatus->compatibleVersions)) { 464 $currentUpdateVersion = end($currentCompatibilityStatus->compatibleVersions); 465 } 466 467 if ($upgradeUpdateVersion !== false) { 468 $upgradeOldestVersion = $upgradeCompatibilityStatus->compatibleVersions[0]; 469 470 if ($currentUpdateVersion !== false) { 471 // If there are updates compatible with both CMS versions use these 472 $bothCompatibleVersions = array_values( 473 array_intersect($upgradeCompatibilityStatus->compatibleVersions, $currentCompatibilityStatus->compatibleVersions) 474 ); 475 476 if (!empty($bothCompatibleVersions)) { 477 $upgradeOldestVersion = $bothCompatibleVersions[0]; 478 $upgradeUpdateVersion = end($bothCompatibleVersions); 479 } 480 } 481 482 if (version_compare($upgradeOldestVersion, $extensionVersion, '>')) { 483 // Installed version is empty or older than the oldest compatible update: Update required 484 $resultGroup = 2; 485 } else { 486 // Current version is compatible 487 $resultGroup = 3; 488 } 489 490 if ($currentUpdateVersion !== false && version_compare($upgradeUpdateVersion, $currentUpdateVersion, '<')) { 491 // Special case warning when version compatible with target is lower than current 492 $upgradeWarning = 2; 493 } 494 } elseif ($currentUpdateVersion !== false) { 495 // No compatible version for target version but there is a compatible version for current version 496 $resultGroup = 1; 497 } else { 498 // No update server available 499 $resultGroup = 1; 500 } 501 502 // Do we need to capture 503 $combinedCompatibilityStatus = array( 504 'upgradeCompatibilityStatus' => (object) array( 505 'state' => $upgradeCompatibilityStatus->state, 506 'compatibleVersion' => $upgradeUpdateVersion 507 ), 508 'currentCompatibilityStatus' => (object) array( 509 'state' => $currentCompatibilityStatus->state, 510 'compatibleVersion' => $currentUpdateVersion 511 ), 512 'resultGroup' => $resultGroup, 513 'upgradeWarning' => $upgradeWarning, 514 ); 515 516 $this->app = Factory::getApplication(); 517 $this->app->mimeType = 'application/json'; 518 $this->app->charSet = 'utf-8'; 519 $this->app->setHeader('Content-Type', $this->app->mimeType . '; charset=' . $this->app->charSet); 520 $this->app->sendHeaders(); 521 522 try { 523 echo new JsonResponse($combinedCompatibilityStatus); 524 } catch (\Exception $e) { 525 echo $e; 526 } 527 528 $this->app->close(); 529 } 530 531 /** 532 * Determines the compatibility information for a number of extensions. 533 * 534 * Called by the Joomla Update JavaScript (PreUpdateChecker.checkNextChunk). 535 * 536 * @return void 537 * @since 4.2.0 538 * 539 */ 540 public function batchextensioncompatibility() 541 { 542 $joomlaTargetVersion = $this->input->post->get('joomla-target-version', '', 'DEFAULT'); 543 $joomlaCurrentVersion = $this->input->post->get('joomla-current-version', JVERSION); 544 $extensionInformation = $this->input->post->get('extensions', []); 545 546 /** @var \Joomla\Component\Joomlaupdate\Administrator\Model\UpdateModel $model */ 547 $model = $this->getModel('Update'); 548 549 $extensionResults = []; 550 $leftover = []; 551 $startTime = microtime(true); 552 553 foreach ($extensionInformation as $information) { 554 // Only process an extension if we have spent less than 5 seconds already 555 $currentTime = microtime(true); 556 557 if ($currentTime - $startTime > 5.0) { 558 $leftover[] = $information; 559 560 continue; 561 } 562 563 // Get the extension information and fetch its compatibility information 564 $extensionID = $information['eid'] ?: ''; 565 $extensionVersion = $information['version'] ?: ''; 566 $upgradeCompatibilityStatus = $model->fetchCompatibility($extensionID, $joomlaTargetVersion); 567 $currentCompatibilityStatus = $model->fetchCompatibility($extensionID, $joomlaCurrentVersion); 568 $upgradeUpdateVersion = false; 569 $currentUpdateVersion = false; 570 $upgradeWarning = 0; 571 572 if ($upgradeCompatibilityStatus->state == 1 && !empty($upgradeCompatibilityStatus->compatibleVersions)) { 573 $upgradeUpdateVersion = end($upgradeCompatibilityStatus->compatibleVersions); 574 } 575 576 if ($currentCompatibilityStatus->state == 1 && !empty($currentCompatibilityStatus->compatibleVersions)) { 577 $currentUpdateVersion = end($currentCompatibilityStatus->compatibleVersions); 578 } 579 580 if ($upgradeUpdateVersion !== false) { 581 $upgradeOldestVersion = $upgradeCompatibilityStatus->compatibleVersions[0]; 582 583 if ($currentUpdateVersion !== false) { 584 // If there are updates compatible with both CMS versions use these 585 $bothCompatibleVersions = array_values( 586 array_intersect($upgradeCompatibilityStatus->compatibleVersions, $currentCompatibilityStatus->compatibleVersions) 587 ); 588 589 if (!empty($bothCompatibleVersions)) { 590 $upgradeOldestVersion = $bothCompatibleVersions[0]; 591 $upgradeUpdateVersion = end($bothCompatibleVersions); 592 } 593 } 594 595 if (version_compare($upgradeOldestVersion, $extensionVersion, '>')) { 596 // Installed version is empty or older than the oldest compatible update: Update required 597 $resultGroup = 2; 598 } else { 599 // Current version is compatible 600 $resultGroup = 3; 601 } 602 603 if ($currentUpdateVersion !== false && version_compare($upgradeUpdateVersion, $currentUpdateVersion, '<')) { 604 // Special case warning when version compatible with target is lower than current 605 $upgradeWarning = 2; 606 } 607 } elseif ($currentUpdateVersion !== false) { 608 // No compatible version for target version but there is a compatible version for current version 609 $resultGroup = 1; 610 } else { 611 // No update server available 612 $resultGroup = 1; 613 } 614 615 // Do we need to capture 616 $extensionResults[] = [ 617 'id' => $extensionID, 618 'upgradeCompatibilityStatus' => (object) [ 619 'state' => $upgradeCompatibilityStatus->state, 620 'compatibleVersion' => $upgradeUpdateVersion 621 ], 622 'currentCompatibilityStatus' => (object) [ 623 'state' => $currentCompatibilityStatus->state, 624 'compatibleVersion' => $currentUpdateVersion 625 ], 626 'resultGroup' => $resultGroup, 627 'upgradeWarning' => $upgradeWarning, 628 ]; 629 } 630 631 $this->app->mimeType = 'application/json'; 632 $this->app->charSet = 'utf-8'; 633 $this->app->setHeader('Content-Type', $this->app->mimeType . '; charset=' . $this->app->charSet); 634 $this->app->sendHeaders(); 635 636 try { 637 $return = [ 638 'compatibility' => $extensionResults, 639 'extensions' => $leftover, 640 ]; 641 642 echo new JsonResponse($return); 643 } catch (\Exception $e) { 644 echo $e; 645 } 646 647 $this->app->close(); 648 } 649 650 /** 651 * Fetch and report updates in \JSON format, for AJAX requests 652 * 653 * @return void 654 * 655 * @since 3.10.10 656 */ 657 public function ajax() 658 { 659 if (!Session::checkToken('get')) { 660 $this->app->setHeader('status', 403, true); 661 $this->app->sendHeaders(); 662 echo Text::_('JINVALID_TOKEN_NOTICE'); 663 $this->app->close(); 664 } 665 666 /** @var UpdateModel $model */ 667 $model = $this->getModel('Update'); 668 $updateInfo = $model->getUpdateInformation(); 669 670 $update = []; 671 $update[] = ['version' => $updateInfo['latest']]; 672 673 echo json_encode($update); 674 675 $this->app->close(); 676 } 677 }
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 |