[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/libraries/src/Installer/Adapter/ -> PackageAdapter.php (source)

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


Generated: Wed Sep 7 05:41:13 2022 Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer