private function loadThemeConfig(Config $config, $theme, $childTheme) { $themePath = $this->app['resources']->getPath('themebase') . '/' . $theme; if (file_exists($themePath)) { $configFile = $themePath . '/config.yml'; // insert parent path right after child path. $twigPath = $config->get('twigpath'); if ($twigPath) { $childThemePath = $this->app['resources']->getPath('themebase') . '/' . $childTheme; $key = array_search($childThemePath, $twigPath); if ($key !== false) { array_splice($twigPath, $key, 1, array($childThemePath, $themePath)); } $config->set('twigpath', $twigPath); } if (file_exists($configFile)) { $themeConfig = self::mergeConfigFile($configFile); if ($themeConfig) { // load parent theme config, but without overwriting, because child prevails $config->set('theme', Arr::mergeRecursiveDistinct($themeConfig, $config->get('theme'))); // multiple levels allowed if (!empty($themeConfig['parent'])) { $this->loadThemeConfig($config, $themeConfig['parent'], $theme); } } } } }
/** * Update packages. * * @param $packages array Indexed array of package names to update * @param $options array [Optional] changed option set * * @throws \Bolt\Exception\PackageManagerException * * @return int 0 on success or a positive error code on failure */ public function execute(array $packages = [], array $options = []) { /** @var $composer \Composer\Composer */ $composer = $this->getComposer(); $io = $this->getIO(); $packageManagerOptions = $this->app['extend.action.options']; // Handle passed in options if (!empty($options)) { $options = Arr::mergeRecursiveDistinct($packageManagerOptions, $options); } else { $options = $packageManagerOptions; } $install = Installer::create($io, $composer); $config = $composer->getConfig(); $optimize = $config->get('optimize-autoloader'); // Set preferred install method $prefer = $this->getPreferedTarget($config->get('preferred-install')); try { $install->setDryRun($options['dryrun'])->setVerbose($options['verbose'])->setPreferSource($prefer['source'])->setPreferDist($prefer['dist'])->setDevMode(!$options['nodev'])->setDumpAutoloader(!$options['noautoloader'])->setRunScripts(!$options['noscripts'])->setOptimizeAutoloader($optimize)->setUpdate(true)->setUpdateWhitelist($packages)->setWhitelistDependencies($options['withdependencies'])->setIgnorePlatformRequirements($options['ignoreplatformreqs'])->setPreferStable($options['preferstable'])->setPreferLowest($options['preferlowest'])->disablePlugins(); return $install->run(); } catch (\Exception $e) { $msg = __CLASS__ . '::' . __FUNCTION__ . ' recieved an error from Composer: ' . $e->getMessage() . ' in ' . $e->getFile() . '::' . $e->getLine(); $this->app['logger.system']->critical($msg, ['event' => 'exception', 'exception' => $e]); throw new PackageManagerException($e->getMessage(), $e->getCode(), $e); } }
/** * Constructor. * * @param array $config */ public function __construct(array $config, $rooturl) { $this->rooturl = $rooturl; $default = $this->getDefaultConfig(); $this->config = Arr::mergeRecursiveDistinct($default, $config); $this->setupProviderConfig(); }
/** * Get the return value from the callable. * * Parameters can be can handled in three ways: * - null - Nothing passed to the callback * - Indexed array - Value of each element will be passed to function in order * - Associative array - Key names will attempt to match to the callable function variable names */ protected function invokeCallable(callable $callback, $callbackArguments) { if ($callbackArguments === null) { return call_user_func($callback); } if (Arr::isIndexedArray($callbackArguments)) { return call_user_func_array($callback, (array) $callbackArguments); } $orderedArgs = $this->getArguments($callback, $callbackArguments); return call_user_func_array($callback, $orderedArgs); }
/** * Merge in a yaml file to the config. * * @param YamlFile $file */ private function addConfig(YamlFile $file) { $app = $this->getContainer(); try { $newConfig = $file->parse(); } catch (RuntimeException $e) { $app['logger.flash']->error($e->getMessage()); $app['logger.system']->error($e->getMessage(), ['event' => 'exception', 'exception' => $e]); throw $e; } if (is_array($newConfig)) { $this->config = Arr::mergeRecursiveDistinct($this->config, $newConfig); } }
public function testMergeRecusrsiveDistinct() { $arr1 = array('key' => 'orig value'); $arr2 = array('key' => 'new value'); $this->assertEquals(array('key' => 'new value'), Arr::mergeRecursiveDistinct($arr1, $arr2)); // Needs an exclusion for accept_file_types $arr1 = array('accept_file_types' => 'jpg'); $arr2 = array('accept_file_types' => 'jpg,png'); Arr::mergeRecursiveDistinct($arr1, $arr2); $this->assertEquals(array('accept_file_types' => 'jpg'), $arr1); // Test Recusrsion $arr1 = array('key' => array('test' => 'new value')); $arr2 = array('key' => array('test' => 'nested new value')); $this->assertEquals(array('key' => array('test' => 'nested new value')), Arr::mergeRecursiveDistinct($arr1, $arr2)); }
/** * Call this in register method. * * @internal */ protected final function extendRepositoryMapping() { $app = $this->getContainer(); $app['storage'] = $app->share($app->extend('storage', function ($entityManager) use($app) { foreach ($this->registerRepositoryMappings() as $alias => $map) { if (Arr::isIndexedArray($map)) { // Usually caused by [entity, repo] instead of [entity => repo] throw new \RuntimeException(sprintf('Repository mapping for %s `%s` is not an associative array.', __CLASS__, $alias)); } $app['storage.repositories'] += $map; $app['storage.metadata']->setDefaultAlias($app['schema.prefix'] . $alias, key($map)); $entityManager->setRepository(key($map), current($map)); } return $entityManager; })); }
/** * Build a GET query if required. * * @param array $redirect * @param FormData $formData * * @return string */ protected function getRedirectQuery(array $redirect, FormData $formData) { if (!isset($redirect['query']) || empty($redirect['query'])) { return ''; } $query = array(); if (is_array($redirect['query'])) { if (Arr::isIndexedArray($redirect['query'])) { foreach ($redirect['query'] as $param) { $query[$param] = $formData->get($param); } } else { foreach ($redirect['query'] as $id => $param) { $query[$id] = $formData->get($param); } } } else { $param = $redirect['query']; $query[$param] = $formData->get($param); } return '?' . http_build_query($query); }
/** * merge_with_meta_yml */ private function merge_with_meta_yml() { $this->metas = Arr::mergeRecursiveDistinct($this->default_settings, $this->parseConfigYaml('meta.yml')); }
/** * Parse a Contenttype's filed and determine the grouping * * @param array $fields * @param array $generalConfig * * @return array */ protected function parseFieldsAndGroups(array $fields, array $generalConfig) { $acceptableFileTypes = $generalConfig['accept_file_types']; $currentGroup = 'ungrouped'; $groups = []; $hasGroups = false; foreach ($fields as $key => $field) { unset($fields[$key]); $key = str_replace('-', '_', strtolower(Str::makeSafe($key, true))); // If field is a "file" type, make sure the 'extensions' are set, and it's an array. if ($field['type'] == 'file' || $field['type'] == 'filelist') { if (empty($field['extensions'])) { $field['extensions'] = $acceptableFileTypes; } $field['extensions'] = (array) $field['extensions']; } // If field is an "image" type, make sure the 'extensions' are set, and it's an array. if ($field['type'] == 'image' || $field['type'] == 'imagelist') { if (empty($field['extensions'])) { $field['extensions'] = array_intersect(['gif', 'jpg', 'jpeg', 'png'], $acceptableFileTypes); } $field['extensions'] = (array) $field['extensions']; } // Make indexed arrays into associative for select fields // e.g.: [ 'yes', 'no' ] => { 'yes': 'yes', 'no': 'no' } if ($field['type'] === 'select' && isset($field['values']) && is_array($field['values']) && Arr::isIndexedArray($field['values'])) { $field['values'] = array_combine($field['values'], $field['values']); } if (!empty($field['group'])) { $hasGroups = true; } // Make sure we have these keys and every field has a group set. $field = array_replace(['class' => '', 'default' => '', 'group' => $currentGroup, 'label' => '', 'variant' => ''], $field); // Collect group data for rendering. // Make sure that once you started with group all following have that group, too. $currentGroup = $field['group']; $groups[$currentGroup] = 1; $fields[$key] = $field; // Repeating fields checks if ($field['type'] === 'repeater') { $fields[$key] = $this->parseFieldRepeaters($fields, $key); if ($fields[$key] === null) { unset($fields[$key]); } } } // Make sure the 'uses' of the slug is an array. if (isset($fields['slug']) && isset($fields['slug']['uses'])) { $fields['slug']['uses'] = (array) $fields['slug']['uses']; } return [$fields, $hasGroups ? array_keys($groups) : []]; }
/** * Update / insert taxonomy for a given content-unit. * * @param Content $content * @param integer $contentId * @param array $taxonomy */ protected function updateTaxonomy($content, $contentId, $taxonomy) { $contenttype = $content->contenttype; $tablename = $this->getTablename('taxonomy'); $configTaxonomies = $this->app['config']->get('taxonomy'); // Make sure $contenttypeslug is a 'slug' if (is_array($contenttype)) { $contenttypeslug = $contenttype['slug']; } else { $contenttypeslug = $contenttype; } // If our contenttype has no taxonomies, there's nothing for us to do here. if (!isset($contenttype['taxonomy'])) { return; } foreach ($contenttype['taxonomy'] as $taxonomytype) { // Set 'newvalues to 'empty array' if not defined if (!empty($taxonomy[$taxonomytype])) { $newslugs = $this->getIndexedTaxonomy($content, $taxonomy[$taxonomytype]); } else { $newslugs = array(); } // Get the current values from the DB. $query = sprintf("SELECT id, slug, sortorder FROM %s WHERE content_id=? AND contenttype=? AND taxonomytype=?", $tablename); $currentvalues = $this->app['db']->executeQuery($query, array($contentId, $contenttypeslug, $taxonomytype), array(\PDO::PARAM_INT, \PDO::PARAM_STR, \PDO::PARAM_STR))->fetchAll(); if (!empty($currentvalues)) { $currentsortorder = $currentvalues[0]['sortorder']; $currentvalues = Arr::makeValuePairs($currentvalues, 'id', 'slug'); } else { $currentsortorder = 0; $currentvalues = array(); } // Add the ones not yet present. foreach ($newslugs as $slug) { // If it's like 'desktop#10', split it into value and sortorder. list($slug, $sortorder) = explode('#', $slug . "#"); // @todo clean up and/or refactor // If you save this content via anything other than the Bolt // backend (see Content->setFromPost), then taxonomies that // behave like groupings, will have their sortorders reset to 0. if ($configTaxonomies[$taxonomytype]['behaves_like'] == 'grouping' && empty($sortorder) && $sortorder !== '0') { $sortorder = $currentsortorder; } if (empty($sortorder)) { $sortorder = 0; } // Make sure we have a 'name'. if (isset($configTaxonomies[$taxonomytype]['options'][$slug])) { $name = $configTaxonomies[$taxonomytype]['options'][$slug]; } else { $name = $slug; } // Make sure the slug is also set correctly if (!isset($configTaxonomies[$taxonomytype]['options'][$slug])) { // Assume we passed a value, instead of a slug. Turn it back into a proper slug if (isset($configTaxonomies[$taxonomytype]['options']) && is_array($configTaxonomies[$taxonomytype]['options']) && array_search($slug, $configTaxonomies[$taxonomytype]['options'])) { $slug = array_search($slug, $configTaxonomies[$taxonomytype]['options']); } else { // make sure it's at least a slug-like value. $slug = $this->app['slugify']->slugify($slug); } } if ((!in_array($slug, $currentvalues) || $currentsortorder != $sortorder) && !empty($slug)) { // Insert it! $row = array('content_id' => $contentId, 'contenttype' => $contenttypeslug, 'taxonomytype' => $taxonomytype, 'slug' => $slug, 'name' => $name, 'sortorder' => (int) $sortorder); $this->app['db']->insert($tablename, $row); } } // Convert new slugs to lowercase to compare in the delete process $newSlugsNormalised = array(); foreach ($newslugs as $slug) { // If it's like 'desktop#10', split it into value and sortorder. list($slug, $sortorder) = explode('#', $slug . "#"); $slug = $this->app['slugify']->slugify($slug); if (!empty($sortorder)) { $slug = $slug . '#' . $sortorder; } $newSlugsNormalised[] = $slug; } // Delete the ones that have been removed. foreach ($currentvalues as $id => $slug) { // Make it look like 'desktop#10' $valuewithorder = $slug . "#" . $currentsortorder; $slugkey = '/' . $configTaxonomies[$taxonomytype]['slug'] . '/' . $slug; if (!in_array($slug, $newSlugsNormalised) && !in_array($valuewithorder, $newSlugsNormalised) && !array_key_exists($slugkey, $newSlugsNormalised)) { $this->app['db']->delete($tablename, array('id' => $id)); } } } }
/** * @param array $providerConfig * * @return array */ private function setDefaultConfig(array $providerConfig) { $default = ['label' => ['sign_in' => null, 'associate' => null], 'keys' => ['client_id' => null, 'client_secret' => null], 'scopes' => null, 'enabled' => false]; return Arr::mergeRecursiveDistinct($default, $providerConfig); }
/** * If we're using local extensions, install/require the merge plugin. * * @param array $composerJson * * @return array */ private function setJsonLocal(array $composerJson) { $local = $this->app['filesystem']->getFilesystem('extensions')->getDir('local')->exists(); if ($local === false) { return $composerJson; } $defaults = ['extra' => ['merge-plugin' => ['include' => ['local/*/*/composer.json']]], 'require' => ['wikimedia/composer-merge-plugin' => '^1.3']]; $composerJson = Arr::mergeRecursiveDistinct($composerJson, $defaults); ksort($composerJson); return $composerJson; }
public function testNonArraysAreNotIndexedOrAssociative() { $this->assertFalse(Arr::isIndexed('derp')); $this->assertFalse(Arr::isAssociative('derp')); }
/** * Read and parse the config.yml and config_local.yml configuration files. * * @return array */ protected function parseGeneral() { // Read the config and merge it. (note: We use temp variables to prevent // "Only variables should be passed by reference") $tempconfig = $this->parseConfigYaml('config.yml'); $tempconfiglocal = $this->parseConfigYaml('config_local.yml'); $general = Arr::mergeRecursiveDistinct($tempconfig, $tempconfiglocal); // Make sure old settings for 'contentsCss' are still picked up correctly if (isset($general['wysiwyg']['ck']['contentsCss'])) { $general['wysiwyg']['ck']['contentsCss'] = array(1 => $general['wysiwyg']['ck']['contentsCss']); } // Make sure old settings for 'accept_file_types' are not still picked up. Before 1.5.4 we used to store them // as a regex-like string, and we switched to an array. If we find the old style, fall back to the defaults. if (isset($general['accept_file_types']) && !is_array($general['accept_file_types'])) { unset($general['accept_file_types']); } // Merge the array with the defaults. Setting the required values that aren't already set. $general = Arr::mergeRecursiveDistinct($this->defaultConfig, $general); // Make sure the cookie_domain for the sessions is set properly. if (empty($general['cookies_domain'])) { if (isset($_SERVER['HTTP_HOST'])) { $hostname = $_SERVER['HTTP_HOST']; } elseif (isset($_SERVER['SERVER_NAME'])) { $hostname = $_SERVER['SERVER_NAME']; } else { $hostname = ''; } // Don't set the domain for a cookie on a "TLD" - like 'localhost', or if the server_name is an IP-address if (strpos($hostname, '.') > 0 && preg_match("/[a-z0-9]/i", $hostname)) { if (preg_match("/^www[0-9]*./", $hostname)) { $general['cookies_domain'] = '.' . preg_replace("/^www[0-9]*./", '', $hostname); } else { $general['cookies_domain'] = '.' . $hostname; } // Make sure we don't have consecutive '.'-s in the cookies_domain. $general['cookies_domain'] = str_replace('..', '.', $general['cookies_domain']); } else { $general['cookies_domain'] = ''; } } // Make sure Bolt's mount point is OK: $general['branding']['path'] = '/' . Str::makeSafe($general['branding']['path']); $general['database'] = $this->parseDatabase($general['database']); return $general; }
/** * Build a GET query if required. * * @param array $redirect * @param array $formdata */ private function getRedirectQuery(array $redirect, $formdata) { $query = array(); if (Arr::isIndexedArray($redirect['query'])) { foreach ($redirect['query'] as $param) { $query[$param] = $this->getNormalisedData($formdata[$param]); } } else { $query = $redirect['query']; } return '?' . http_build_query($query); }
/** * Make a new querystring while preserving current query parameters with the * option to override values. * * @param array $overrides A (key,value)-array with elements to override in * the current query string. * @param bool $buildQuery Returns a querystring if set to true, otherwise * returns the array with (key,value)-pairs. * @return mixed query parameters in either array or string form. * * @see \Bolt\Helpers\Arr::mergeRecursiveDistinct() */ private function makeQueryParameters($overrides = [], $buildQuery = true) { $queryParameters = $this->request->query->all(); // todo: (optional) cleanup. There is a default set of fields we can // expect using this Extension and jsonapi. Or we could ignore // them like we already do. // Using Bolt's Helper Arr class for merging and overriding values. $queryParameters = Arr::mergeRecursiveDistinct($queryParameters, $overrides); $queryParameters = $this->unfixBoltStorageRequest($queryParameters); if ($buildQuery) { // No need to urlencode these, afaik. $querystring = urldecode(http_build_query($queryParameters)); if (!empty($querystring)) { $querystring = '?' . $querystring; } return $querystring; } return $queryParameters; }
/** * Load and process a give config file. * * @param string $configfile Fully qualified file path */ private function loadConfigFile($configfile) { $yamlparser = new Yaml\Parser(); $newConfig = $yamlparser->parse(file_get_contents($configfile) . "\n"); // Don't error on empty config files if (is_array($newConfig)) { $this->config = Arr::mergeRecursiveDistinct($this->config, $newConfig); } }
/** * Insert an individual Contenttype record into the database * * @param string $filename * @param string $contenttypeslug * @param array $values * * @return boolean */ private function insertRecord($filename, $contenttypeslug, array $values) { // Determine a/the slug $slug = isset($values['slug']) ? $values['slug'] : substr($this->app['slugify']->slugify($values['title']), 0, 127); if (!$this->isRecordUnique($contenttypeslug, $slug)) { $this->setWarning(true)->setWarningMessage("File '{$filename}' has an exiting Contenttype '{$contenttypeslug}' with the slug '{$slug}'! Skipping record."); return false; } // Get a status if (isset($values['status'])) { $status = $values['status']; } else { $status = $this->contenttypes[$contenttypeslug]['default_status']; } // Transform the 'publish' action to a 'published' status $status = $status === 'publish' ? 'published' : $status; // Insist on a title field if (!isset($values['title'])) { $this->setWarning(true)->setWarningMessage("File '{$filename}' has a '{$contenttypeslug}' with a missing title field! Skipping record."); return false; } // Set up default meta $meta = ['slug' => $slug, 'datecreated' => date('Y-m-d H:i:s'), 'datepublish' => $status == 'published' ? date('Y-m-d H:i:s') : null, 'ownerid' => 1]; $values = Arr::mergeRecursiveDistinct($values, $meta); $record = $this->app['storage']->getEmptyContent($contenttypeslug); $record->setValues($values); if ($this->app['storage']->saveContent($record) === false) { $this->setWarning(true)->setWarningMessage("Failed to imported record with title: {$values['title']} from '{$filename}'! Skipping record."); return false; } else { $this->setNotice(true)->setNoticeMessage("Imported record with title: {$values['title']}."); return true; } }
/** * Build an OR group that is added to the AND. * * @param QueryBuilder $qb * @param string $parentColumnName * @param array $options * * @return CompositeExpression */ protected function buildWhereOr(QueryBuilder $qb, $parentColumnName, array $options) { $orX = $qb->expr()->orX(); foreach ($options as $columnName => $option) { if (empty($options[$columnName])) { continue; } elseif (Arr::isIndexedArray($options)) { $key = $parentColumnName . '_' . $columnName; $orX->add("{$parentColumnName} = :{$key}"); $qb->setParameter($key, $option); } else { $orX->add("{$columnName} = :{$columnName}"); $qb->setParameter($columnName, $option); } } return $orX; }
/** * Enforce the default JSON settings. * * @param array $json * * @return array */ private function setJsonDefaults(array $json) { $rootPath = $this->app['resources']->getPath('root'); $extensionsPath = $this->app['resources']->getPath('extensions'); $srcPath = $this->app['resources']->getPath('src'); $webPath = $this->app['resources']->getPath('web'); $pathToRoot = Path::makeRelative($rootPath, $extensionsPath); $pathToWeb = Path::makeRelative($webPath, $extensionsPath); $eventPath = Path::makeRelative($srcPath . '/Composer/EventListener', $extensionsPath); /** @deprecated Handle BC on 'stability' key until 4.0 */ $minimumStability = $this->app['config']->get('general/extensions/stability') ?: $this->app['config']->get('general/extensions/composer/minimum-stability', 'stable'); // Enforce standard settings $defaults = ['name' => 'bolt/extensions', 'description' => 'Bolt extension installation interface', 'license' => 'MIT', 'repositories' => ['packagist' => false, 'bolt' => ['type' => 'composer', 'url' => $this->app['extend.site'] . 'satis/']], 'minimum-stability' => $minimumStability, 'prefer-stable' => true, 'config' => ['discard-changes' => true, 'preferred-install' => 'dist'], 'provide' => ['bolt/bolt' => Bolt\Version::forComposer()], 'extra' => ['bolt-web-path' => $pathToWeb, 'bolt-root-path' => $pathToRoot], 'autoload' => ['psr-4' => ['Bolt\\Composer\\EventListener\\' => $eventPath]], 'scripts' => ['post-autoload-dump' => 'Bolt\\Composer\\EventListener\\PackageEventListener::dump', 'post-package-install' => 'Bolt\\Composer\\EventListener\\PackageEventListener::handle', 'post-package-update' => 'Bolt\\Composer\\EventListener\\PackageEventListener::handle']]; $json = Arr::replaceRecursive($json, $defaults); ksort($json); return $json; }