/** * Event fired on database connection failure. * * @param FailedConnectionEvent $args * * @throws BootException */ public function failConnect(FailedConnectionEvent $args) { $e = $args->getException(); $this->logger->debug($e->getMessage(), ['event' => 'exception', 'exception' => $e]); /* * Using Driver here since Platform may try to connect * to the database, which has failed since we are here. */ $platform = $args->getDriver()->getName(); $platform = Str::replaceFirst('pdo_', '', $platform); $response = $this->exceptionController->databaseConnect($platform, $e); throw new BootException($e->getMessage(), $e->getCode(), $e, $response); }
/** * Event fired on database connection failure. * * @param FailedConnectionEvent $args * * @throws LowLevelDatabaseException */ public function failConnect(FailedConnectionEvent $args) { $e = $args->getException(); $this->logger->debug($e->getMessage(), ['event' => 'exception', 'exception' => $e]); // Trap double exceptions set_exception_handler(function () { }); /* * Using Driver here since Platform may try to connect * to the database, which has failed since we are here. */ $platform = $args->getDriver()->getName(); $platform = Str::replaceFirst('pdo_', '', $platform); throw LowLevelDatabaseException::failedConnect($platform, $e); }
/** * Parse and fine-tune the database configuration. * * @param array $options * * @return array */ protected function parseDatabase(array $options) { // Make sure prefix ends with underscore if (substr($options['prefix'], strlen($options['prefix']) - 1) !== '_') { $options['prefix'] .= '_'; } // Parse master connection parameters $master = $this->parseConnectionParams($options); // Merge master connection into options $options = array_replace($options, $master); // Add platform specific random functions $driver = Str::replaceFirst('pdo_', '', $options['driver']); if ($driver === 'sqlite') { $options['driver'] = 'pdo_sqlite'; $options['randomfunction'] = 'RANDOM()'; } elseif (in_array($driver, ['mysql', 'mysqli'])) { $options['driver'] = 'pdo_mysql'; $options['randomfunction'] = 'RAND()'; } elseif (in_array($driver, ['pgsql', 'postgres', 'postgresql'])) { $options['driver'] = 'pdo_pgsql'; $options['randomfunction'] = 'RANDOM()'; } // Specify the wrapper class for the connection $options['wrapperClass'] = '\\Bolt\\Storage\\Database\\Connection'; // Parse SQLite separately since it has to figure out database path if ($driver === 'sqlite') { return $this->parseSqliteOptions($options); } // If no slaves return with single connection if (empty($options['slaves'])) { return $options; } // Specify we want a master slave connection $options['wrapperClass'] = '\\Bolt\\Storage\\Database\\MasterSlaveConnection'; // Add master connection where MasterSlaveConnection looks for it. $options['master'] = $master; // Parse each slave connection parameters foreach ($options['slaves'] as $name => $slave) { $options['slaves'][$name] = $this->parseConnectionParams($slave, $master); } return $options; }
/** * Get the parameter for the 'order by' part of a query. * * This is tightly coupled to $this->getContent() * * @param array $contenttype * @param string $orderValue * * @return string */ private function decodeQueryOrder($contenttype, $orderValue) { $order = false; if ($orderValue === false || $orderValue === '') { if ($this->isValidColumn($contenttype['sort'], $contenttype, true)) { $order = $this->getEscapedSortorder($contenttype['sort'], false); } } else { $parOrder = Str::makeSafe($orderValue); if ($parOrder == 'RANDOM') { $dboptions = $this->app['db']->getParams(); $order = $dboptions['randomfunction']; } elseif ($this->isValidColumn($parOrder, $contenttype, true)) { $order = $this->getEscapedSortorder($parOrder, false); } } return $order; }
/** * Helper function to insert some HTML into the body section of an HTML * page, right before the </body> tag. * * @param string $tag * @param string $html * * @return string */ static function insertEndOfBody($tag, $html) { // first, attempt to insert it before the </body> tag, matching indentation. if (preg_match("~([ \t]*)</body~mi", $html, $matches)) { // Try to insert it just before </head> $replacement = sprintf("%s\t%s\n%s", $matches[1], $tag, $matches[0]); $html = Str::replaceFirst($matches[0], $replacement, $html); } else { // Since we're serving tag soup, just append it. $html .= $tag . "\n"; } return $html; }
/** * Helper function to insert some HTML after the last javascript include. * First in the head section, but if there is no script in the head, place * it anywhere. * * @param string $tag * @param string $html * @param bool $insidehead * * @return string */ public function insertAfterJs($tag, $html, $insidehead = true) { // Set $context: only the part until </head>, or entire document. if ($insidehead) { $pos = strpos($html, "</head>"); $context = substr($html, 0, $pos); } else { $context = $html; } // then, attempt to insert it after the last <script> tag within context, matching indentation. if (preg_match_all("~^([ \t]*)(.*)</script>~mi", $context, $matches)) { // matches[0] has some elements, the last index is -1, because zero indexed. $last = count($matches[0]) - 1; $replacement = sprintf("%s\n%s%s", $matches[0][$last], $matches[1][$last], $tag); $html = Str::replaceFirst($matches[0][$last], $replacement, $html); } elseif ($insidehead) { // Second attempt: entire document $html = $this->insertAfterJs($tag, $html, false); } else { // Just insert it at the end of the head section. $html = $this->insertEndOfHead($tag, $html); } return $html; }
/** * Helper function to insert some HTML after the last javascript include. * First in the head section, but if there is no script in the head, place * it anywhere. * * @param AssetInterface $asset * @param string $rawHtml * @param boolean $insidehead * * @return string */ protected function jsTagsAfter($asset, $rawHtml, $insidehead = false) { if ($insidehead) { $pos = strpos($rawHtml, '</head>'); $context = substr($rawHtml, 0, $pos); } else { $context = $rawHtml; } // This match tag is a unique case if ($matches = $this->getMatches($context, '(.*)</script>', false, true)) { // Attempt to insert it after the last <script> tag within context, matching indentation. $last = count($matches[0]) - 1; $replacement = sprintf("%s\n%s%s", $matches[0][$last], $matches[1][$last], (string) $asset); return Str::replaceFirst($matches[0][$last], $replacement, $rawHtml); } elseif ($insidehead) { // Second attempt: entire document return $this->jsTagsAfter($asset, $rawHtml, false); } return $this->headTagEnd($asset, $rawHtml); }
/** * Set a Contenttype record values from a HTTP POST. * * @param array $values * @param string $contenttype * * @throws \Exception * * @return void */ public function setFromPost($values, $contenttype) { $values = Input::cleanPostedData($values); if (!$this->id) { // this is a new record: current user becomes the owner. $user = $this->app['users']->getCurrentUser(); $this['ownerid'] = $user['id']; } // If the owner is set explicitly, check if the current user is allowed // to do this. if (isset($values['ownerid'])) { if ($this['ownerid'] != $values['ownerid']) { if (!$this->app['users']->isAllowed("contenttype:{$contenttype['slug']}:change-ownership:{$this->id}")) { throw new \Exception("Changing ownership is not allowed."); } $this['ownerid'] = intval($values['ownerid']); } } // Make sure we have a proper status. if (!in_array($values['status'], ['published', 'timed', 'held', 'draft'])) { if ($this['status']) { $values['status'] = $this['status']; } else { $values['status'] = "draft"; } } // Make sure we only get the current taxonomies, not those that were fetched from the DB. $this->taxonomy = []; if (!empty($values['taxonomy'])) { foreach ($values['taxonomy'] as $taxonomytype => $value) { if (!is_array($value)) { $value = explode(",", $value); } if (isset($values['taxonomy-order'][$taxonomytype])) { foreach ($value as $k => $v) { $value[$k] = $v . "#" . $values['taxonomy-order'][$taxonomytype]; } } $this->taxonomy[$taxonomytype] = $value; } unset($values['taxonomy']); unset($values['taxonomy-order']); } // Get the relations from the POST-ed values. // @todo use $this->setRelation() for this if (!empty($values['relation'])) { $this->relation = $values['relation']; unset($values['relation']); } else { $this->relation = []; } // @todo check for allowed file types. // Handle file-uploads. if (!empty($_FILES)) { foreach ($_FILES as $key => $file) { if (empty($file['name'][0])) { continue; // Skip 'empty' uploads. } $paths = $this->app['resources']->getPaths(); $filename = sprintf('%sfiles/%s/%s', $paths['rootpath'], date('Y-m'), Str::makeSafe($file['name'][0], false, '[]{}()')); $basename = sprintf('/%s/%s', date('Y-m'), Str::makeSafe($file['name'][0], false, "[]{}()")); if ($file['error'][0] != UPLOAD_ERR_OK) { $message = 'Error occured during upload: ' . $file['error'][0] . " - {$filename}"; $this->app['logger.system']->error($message, ['event' => 'upload']); continue; } if (substr($key, 0, 11) != 'fileupload-') { $message = "Skipped an upload that wasn't for content: {$filename}"; $this->app['logger.system']->error($message, ['event' => 'upload']); continue; } $fieldname = substr($key, 11); $fileSystem = new Filesystem(); // Make sure the folder exists. $fileSystem->mkdir(dirname($filename)); // Check if we don't have doubles. if (is_file($filename)) { while (is_file($filename)) { $filename = $this->upcountName($filename); $basename = $this->upcountName($basename); } } if (is_writable(dirname($filename))) { // Yes, we can create the file! move_uploaded_file($file['tmp_name'][0], $filename); $values[$fieldname] = $basename; $this->app['logger.system']->info("Upload: uploaded file '{$basename}'.", ['event' => 'upload']); } else { $this->app['logger.system']->error("Upload: couldn't write upload '{$basename}'.", ['event' => 'upload']); } } } $this->setValues($values); }
/** * Copy of the Bolt\Config::parseFieldsAndGroups method cause it is not a public one. * We need to use this method to parse child fields like a real one. * * @see \Bolt\Config::parseFieldsAndGroups */ private function parseFieldsAndGroups(array $fields, array $generalConfig) { $acceptableFileTypes = $generalConfig['accept_file_types']; $currentGroup = 'ungrouped'; $groups = array(); $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; } if (!is_array($field['extensions'])) { $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(array('gif', 'jpg', 'jpeg', 'png'), $acceptableFileTypes); } if (!is_array($field['extensions'])) { $field['extensions'] = array($field['extensions']); } } // If field is a "Select" type, make sure the array is a "hash" (as opposed to a "map") // For example: [ 'yes', 'no' ] => { 'yes': 'yes', 'no': 'no' } // The reason that we do this, is because if you set values to ['blue', 'green'], that is // what you'd expect to see in the database. Not '0' and '1', which is what would happen, // if we didn't "correct" it here. // @see used hack: http://stackoverflow.com/questions/173400/how-to-check-if-php-array-is-associative-or-sequential if ($field['type'] == 'select' && isset($field['values']) && is_array($field['values']) && array_values($field['values']) === $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(array('label' => '', 'variant' => '', 'default' => '', 'pattern' => '', 'group' => $currentGroup), $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; // Prefix class with "form-control" $field['class'] = 'form-control' . (isset($field['class']) ? ' ' . $field['class'] : ''); $fields[$key] = $field; } // Make sure the 'uses' of the slug is an array. if (isset($fields['slug']) && isset($fields['slug']['uses']) && !is_array($fields['slug']['uses'])) { $fields['slug']['uses'] = array($fields['slug']['uses']); } return array($fields, $hasGroups ? array_keys($groups) : false); }
/** * Get the parameter for the 'order by' part of a query. * * This is tightly coupled to $this->getContent() * * @param array $contenttype * @param string $orderValue * * @return string */ private function decodeQueryOrder($contenttype, $orderValue) { $order = false; if ($orderValue === false || $orderValue === '') { if ($this->isValidColumn($contenttype['sort'], $contenttype, true)) { $order = $contenttype['sort']; } } else { $parOrder = Str::makeSafe($orderValue); if ($parOrder == 'RANDOM') { // Unsupported return false; } elseif ($this->isValidColumn($parOrder, $contenttype, true)) { $order = $parOrder; } } return $order; }
public function testReplaceFirst() { $input = "this is a test string this is a test string"; $this->assertEquals("one is a test string this is a test string", Str::replaceFirst('this', 'one', $input)); }
/** * Add 'soft hyphens' ­ to a string, so that it won't break layout in HTML when * using strings without spaces or dashes. * * @param string $str * * @return string */ public function shy($str) { if (is_string($str)) { $str = Str::shyphenate($str); } return $str; }
/** * Gather related (present) files. * * Matches: foo(_local)?\.*(.dist)? * * i.e., if we're editing config.yml, we also want to check for * config.yml.dist and config_local.yml * * @param FileInterface $file * * @return FileInterface[] */ private function getRelatedFiles(FileInterface $file) { // Match foo(_local).*(.dist) $base = $file->getFilename(); if (Str::endsWith($base, '.dist')) { $base = substr($base, 0, -5); } $ext = pathinfo($base, PATHINFO_EXTENSION); $base = Str::replaceLast(".{$ext}", '', $base); $base = Str::replaceLast('_local', '', $base); $dir = $file->getParent(); $related = []; foreach ([".{$ext}", "_local.{$ext}", ".{$ext}.dist"] as $tail) { $f = $dir->getFile($base . $tail); if ($f->getFilename() !== $file->getFilename() && $f->exists()) { $related[] = $f; } } return $related; }
/** * Sanity checks for doubles in in contenttypes. * * @return bool */ public function checkConfig() { $slugs = []; $passed = true; foreach ($this->data['contenttypes'] as $key => $ct) { // Make sure that there are no hyphens in the contenttype name, advise to change to underscores if (strpos($key, '-') !== false) { $error = Trans::__('contenttypes.generic.invalid-hyphen', ['%contenttype%' => $key]); $this->app['logger.flash']->error($error); $original = $this->data['contenttypes'][$key]; $key = str_replace('-', '_', strtolower(Str::makeSafe($key, true))); $this->data['contenttypes'][$key] = $original; $passed = false; } /** * Make sure any field that has a 'uses' parameter actually points to a field that exists. * * For example, this will show a notice: * entries: * name: Entries * singular_name: Entry * fields: * title: * type: text * class: large * slug: * type: slug * uses: name */ foreach ($ct['fields'] as $fieldname => $field) { // Verify that the contenttype doesn't try to add fields that are reserved. if ($fieldname != 'slug' && in_array($fieldname, $this->reservedFieldNames)) { $error = Trans::__('contenttypes.generic.reserved-name', ['%contenttype%' => $key, '%field%' => $fieldname]); $this->app['logger.flash']->danger($error); $passed = false; } // Check 'uses'. If it's an array, split it up, and check the separate parts. We also need to check // for the fields that are always present, like 'id'. if (!empty($field['uses']) && is_array($field['uses'])) { foreach ((array) $field['uses'] as $useField) { if (!empty($field['uses']) && empty($ct['fields'][$useField]) && !in_array($useField, $this->reservedFieldNames)) { $error = Trans::__('contenttypes.generic.wrong-use-field', ['%contenttype%' => $key, '%field%' => $fieldname, '%uses%' => $useField]); $this->app['logger.flash']->warning($error); $passed = false; } } } // Make sure that there are no hyphens in the field names, advise to change to underscores if (!isset($field['type']) || !$this->fields->has($field['type'])) { $error = Trans::__('contenttypes.generic.no-proper-type', ['%contenttype%' => $key, '%field%' => $fieldname, '%type%' => $field['type']]); $this->app['logger.flash']->warning($error); unset($ct['fields'][$fieldname]); $passed = false; } } /** * Make sure any contenttype that has a 'relation' defined points to a contenttype that exists. */ if (isset($ct['relations'])) { foreach ($ct['relations'] as $relKey => $relData) { // For BC we check if relation uses hyphen and re-map to underscores if (strpos($relKey, '-') !== false) { $newRelKey = str_replace('-', '_', strtolower(Str::makeSafe($relKey, true))); unset($this->data['contenttypes'][$key]['relations'][$relKey]); $this->data['contenttypes'][$key]['relations'][$newRelKey] = $relData; $relKey = $newRelKey; } if (!isset($this->data['contenttypes'][$relKey])) { $error = Trans::__('contenttypes.generic.invalid-relation', ['%contenttype%' => $key, '%relation%' => $relKey]); $this->app['logger.flash']->error($error); unset($this->data['contenttypes'][$key]['relations'][$relKey]); $passed = false; } } } // Keep a running score of used slugs. if (!isset($slugs[$ct['slug']])) { $slugs[$ct['slug']] = 0; } $slugs[$ct['slug']]++; if (!isset($slugs[$ct['singular_slug']])) { $slugs[$ct['singular_slug']] = 0; } if ($ct['singular_slug'] != $ct['slug']) { $slugs[$ct['singular_slug']]++; } } // Sanity checks for taxonomy.yml foreach ($this->data['taxonomy'] as $key => $taxo) { // Show some helpful warnings if slugs or keys are not set correctly. if ($taxo['slug'] != $key) { $error = Trans::__("The identifier and slug for '%taxonomytype%' are the not the same ('%slug%' vs. '%taxonomytype%'). Please edit taxonomy.yml, and make them match to prevent inconsistencies between database storage and your templates.", ['%taxonomytype%' => $key, '%slug%' => $taxo['slug']]); $this->app['logger.flash']->warning($error); $passed = false; } } // if there aren't any other errors, check for duplicates across contenttypes. if (!$this->app['logger.flash']->has('error')) { foreach ($slugs as $slug => $count) { if ($count > 1) { $error = Trans::__("The slug '%slug%' is used in more than one contenttype. Please edit contenttypes.yml, and make them distinct.", ['%slug%' => $slug]); $this->app['logger.flash']->warning($error); $passed = false; } } } return $passed && $this->checkTaxonomy(); }
/** * Set up the DBAL connection now to check for a proper connection to the database. * * @throws LowlevelException */ protected function checkDatabaseConnection() { // [SECURITY]: If we get an error trying to connect to database, we throw a new // LowLevelException with general information to avoid leaking connection information. try { $this['db']->connect(); // A ConnectionException or DriverException could be thrown, we'll catch DBALException to be safe. } catch (DBALException $e) { // Trap double exceptions caused by throwing a new LowlevelException set_exception_handler(array('\\Bolt\\Exception\\LowlevelException', 'nullHandler')); /* * Using Driver here since Platform may try to connect * to the database, which has failed since we are here. */ $platform = $this['db']->getDriver()->getName(); $platform = Str::replaceFirst('pdo_', '', $platform); $error = "Bolt could not connect to the configured database.\n\n" . "Things to check:\n" . " * Ensure the {$platform} database is running\n" . " * Check the <code>database:</code> parameters are configured correctly in <code>app/config/config.yml</code>\n" . " * Database name is correct\n" . " * User name has access to the named database\n" . " * Password is correct\n"; throw new LowlevelException($error); } // Resume normal error handling restore_error_handler(); }
/** * Return a 'safe string' version of a given string. * * @see function Bolt\Library::safeString() * * @param string $str * @param boolean $strict * @param string $extrachars * * @return string */ public function safeString($str, $strict = false, $extrachars = '') { return Str::makeSafe($str, $strict, $extrachars); }
/** * {@inheritdoc} */ public final function getVendor() { if ($this->vendor === null) { $namespace = $this->getNamespace(); $name = Str::replaceFirst('Bolt\\Extension\\', '', $namespace); $pos = strpos($name, '\\'); $this->vendor = $pos === false ? $name : substr($name, 0, $pos); } return $this->vendor; }