Exemple #1
0
 /**
  * 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);
 }
Exemple #2
0
 /**
  * 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);
 }
Exemple #3
0
 /**
  * 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;
 }
Exemple #4
0
 /**
  * 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;
 }
Exemple #6
0
 /**
  * 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;
 }
Exemple #7
0
 /**
  * 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);
 }
Exemple #8
0
 /**
  * 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;
 }
Exemple #11
0
 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));
 }
Exemple #12
0
 /**
  * Add 'soft hyphens' &shy; 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;
 }
Exemple #13
0
 /**
  * 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;
 }
Exemple #14
0
 /**
  * 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();
 }
Exemple #15
0
 /**
  * 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" . "&nbsp;&nbsp;* Ensure the {$platform} database is running\n" . "&nbsp;&nbsp;* Check the <code>database:</code> parameters are configured correctly in <code>app/config/config.yml</code>\n" . "&nbsp;&nbsp;&nbsp;&nbsp;* Database name is correct\n" . "&nbsp;&nbsp;&nbsp;&nbsp;* User name has access to the named database\n" . "&nbsp;&nbsp;&nbsp;&nbsp;* Password is correct\n";
         throw new LowlevelException($error);
     }
     // Resume normal error handling
     restore_error_handler();
 }
Exemple #16
0
 /**
  * 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;
 }