/**
  * getShop uses the configuration file and
  * provided options to build a Shop ready for use by selenium
  * scripts
  *
  * @param  array  $options
  * @return a Shop instance
  *
  * $options is an array with the following keys:
  * - initial_state: an array that will be passed to $shop->getFixtureManager()->setupInitialState(),
  * 	 it is used to set the initial state of the shop for the test
  *
  * - temporary: boolean, determines whether the shop is temporary or not.
  *   a temporary shop is always a new shop, and will likely be destroyed at the end  of the tests.
  *   if set to false, the shop is installed to path_to_web_root.
  *   the target path will be deleted before installation and loaded from source again if it exists
  *   AND if the overwrite option is set to true
  *
  * - overwrite: boolean, whether to overwrite or not the target shop files
  *   when calling getShop with temporary === false - defaults to false
  *
  * - use_cache: whether or not the shop can be cached, i.e., installed once,
  *   initialized with correct initial_state, and then restored from a copy of the files
  *   and a dump of the database.
  *   This is OK in most cases, but since there is some trickery involved in doing this
  *   (replacing a few things in the DB, .htaccess file and config files)
  *   it is not recommended to use the option in some scenarios, such as a complicated multishop
  *   setup.
  *
  */
 public function getShop(array $options)
 {
     $inplace = getenv('PSTAF_INPLACE') === '1';
     $conf = $this->getNewConfiguration();
     if (isset($options['conf'])) {
         $conf->update($options['conf']);
     }
     $options['temporary'] = !empty($options['temporary']) && !$inplace;
     $options['overwrite'] = !empty($options['overwrite']);
     $options['use_cache'] = !empty($options['use_cache']) && !$inplace;
     if (!isset($options['initial_state']) || !is_array($options['initial_state'])) {
         $options['initial_state'] = [];
     }
     // this may become a file resource
     // if it is, then we must close it and unlock it once
     // we're done.
     $lock = null;
     // whether or not we need to dump the constructed shop
     // if not null, it is the path where we need to put the files
     // after they have been built
     $dump_shop_to = null;
     // $using_cache will be true iff a warm cache exists
     // so when $dump_shop_to stays null
     $using_cache = false;
     // First we determine the path to the source files that
     // we're gonna use to build the shop.
     // this is the base case
     $source_files_path = $conf->getAsAbsolutePath('shop.filesystem_path');
     // if caching is allowed, we first check
     // whether the source files exist or not
     if ($options['use_cache'] && $options['initial_state'] !== []) {
         // since the source files may still be under build
         // we acquire a lock on a file that describes the initial state
         $initial_state_key = $this->getInitialStateKey($options['initial_state']);
         // acquire lock
         $lock_path = FS::join($this->getWorkingDirectory(), "pstaf.{$initial_state_key}.istate.lock");
         $lock = fopen($lock_path, 'w');
         if (!$lock) {
             throw new \Exception(sprintf('Could not create lock file %s.', $lock_path));
         }
         flock($lock, LOCK_EX);
         $cache_files_path = FS::join($this->getWorkingDirectory(), "pstaf.{$initial_state_key}.istate.shop");
         if (is_dir($cache_files_path)) {
             // we're all set, release the lock and set source files path to the cached files
             flock($lock, LOCK_UN);
             fclose($lock);
             $lock = null;
             $source_files_path = $cache_files_path;
             $using_cache = true;
         } else {
             // well, we're out out of luck, we're going to need to build
             // the cache files ourselves
             // so we don't release the lock just yet, we'll do it when all
             // is nicely written
             // we remember this by setting $dump_shop_to to the path where cache files
             // will live
             $dump_shop_to = $cache_files_path;
             $using_cache = false;
         }
     }
     // At this point, we know where to take the files from, i.e.,
     // from $source_files_path
     // We now determine where to copy them to...
     $shop_name = basename($conf->getAsAbsolutePath('shop.filesystem_path'));
     // Temporary shop needs unique URL / folder-name
     if ($options['temporary']) {
         $suffix = '_tmpshpcpy_' . $this->getUID();
         $shop_name .= $suffix;
     } else {
         $suffix = '';
     }
     // Our shop URL
     $url = preg_replace('#[^/]+/?$#', $shop_name . '/', $conf->get('shop.front_office_url'));
     // Where the shop will live
     $target_files_path = FS::join($conf->getAsAbsolutePath('shop.path_to_web_root'), $shop_name);
     $target_same_as_source = realpath($source_files_path) === realpath($target_files_path);
     // We say we are doing a new installation if the target files are not there
     $new_install = !file_exists($target_files_path);
     if (!$new_install && $options['overwrite'] && !$target_same_as_source) {
         FS::webRmR($target_files_path, $url);
         $new_install = true;
     }
     // Finally put the shop files in place!
     if (!$target_same_as_source && $new_install) {
         FileManagement::copyShopFiles($source_files_path, $target_files_path);
     }
     // Update the configuration with the new values
     $conf->set('shop.front_office_url', $url);
     $conf->set('shop.filesystem_path', $target_files_path);
     $conf->set('shop.mysql_database', $conf->get('shop.mysql_database') . $suffix);
     if (!isset($options['browser'])) {
         // Prepare to fire up selenium
         $seleniumHost = SeleniumManager::getHost();
         $seleniumSettings = ['host' => $seleniumHost];
         // Hoorah! Build our shop
         $shop = new Shop($conf->get('shop'), $seleniumSettings);
     } else {
         $shop = new Shop($conf->get('shop'), null);
         $shop->setBrowser($options['browser']);
     }
     $shop->setOptionProvider($this->optionProvider);
     if ($inplace && !$new_install) {
         // nothing for now
     } elseif (!$using_cache && ($new_install || $options['overwrite'])) {
         $shop->getFixtureManager()->setupInitialState($options['initial_state']);
         if ($dump_shop_to) {
             $shop->getFileManager()->copyShopFilesTo($dump_shop_to);
             $shop->getDatabaseManager()->dumpTo(FS::join($dump_shop_to, 'pstaf.shopdb.sql'));
         }
     } elseif ($using_cache) {
         $dump_path = FS::join($source_files_path, 'pstaf.shopdb.sql');
         if (file_exists($dump_path)) {
             $shop->getDatabaseManager()->loadDump($dump_path)->changeShopUrlPhysicalURI("/{$shop_name}/");
         }
         $shop->getFileManager()->updateSettingsIncIfExists(['_DB_NAME_' => $conf->get('shop.mysql_database')])->changeHtaccessPhysicalURI("/{$shop_name}/");
     }
     $shop->setTemporary($options['temporary'] && !$inplace);
     if ($lock) {
         flock($lock, LOCK_UN);
         fclose($lock);
     }
     return $shop;
 }
 /**
  * Delete shop files.
  *
  * We put an auto-destruct script on the server and visit it over the web,
  * that way even pesky www-data owned cache files are removed.
  *
  * The alternative is to run the whole script as root, which is a known bad thing.
  *
  */
 public function deleteAllFiles()
 {
     FS::webRmR($this->getShop()->getFilesystemPath(), $this->getShop()->getFrontOfficeURL());
     return $this;
 }