/** * Retrieves the plain-text content from the current raw content. */ protected function getTextContent() { if (!isset($this->plainTextContent)) { $this->plainTextContent = Xss::filter($this->getRawContent(), array()); } return $this->plainTextContent; }
/** * Determines if a string of text is considered "simple". * * @param string $string * The string of text to check "simple" criteria on. * @param int|FALSE $length * The length of characters used to determine whether or not $string is * considered "simple". Set explicitly to FALSE to disable this criteria. * @param array|FALSE $allowed_tags * An array of allowed tag elements. Set explicitly to FALSE to disable this * criteria. * @param bool $html * A variable, passed by reference, that indicates whether or not the * string contains HTML. * * @return bool * Returns TRUE if the $string is considered "simple", FALSE otherwise. */ public static function isSimple($string, $length = 250, $allowed_tags = NULL, &$html = FALSE) { // Typecast to a string (if an object). $string_clone = (string) $string; // Use the advanced drupal_static() pattern. static $drupal_static_fast; if (!isset($drupal_static_fast)) { $drupal_static_fast['strings'] =& drupal_static(__METHOD__); } $strings =& $drupal_static_fast['strings']; if (!isset($strings[$string_clone])) { $plain_string = strip_tags($string_clone); $simple = TRUE; if ($allowed_tags !== FALSE) { $filtered_string = Xss::filter($string_clone, $allowed_tags); $html = $filtered_string !== $plain_string; $simple = $simple && $string_clone === $filtered_string; } if ($length !== FALSE) { $simple = $simple && strlen($plain_string) <= intval($length); } $strings[$string_clone] = $simple; } return $strings[$string_clone]; }
/** * {@inheritdoc} */ public function process($text, $langcode) { $allowed_tags = array_filter($this->settings['restrictions']['allowed'], function ($value) { return is_array($value) || (bool) $value !== FALSE; }); return new FilterProcessResult(Xss::filter($text, array_keys($allowed_tags))); }
/** * Retrieves the plain-text content from the current raw content. */ protected function getTextContent() { if (!isset($this->plainTextContent)) { $raw_content = $this->getRawContent(); // Strip everything between the HEAD tags. $raw_content = preg_replace('@<head>(.+?)</head>@si', '', $raw_content); $this->plainTextContent = Xss::filter($raw_content, array()); } return $this->plainTextContent; }
/** * Tests execution order of hook_form_alter() and hook_form_FORM_ID_alter(). */ function testExecutionOrder() { $this->drupalGet('form-test/alter'); // Ensure that the order is first by module, then for a given module, the // id-specific one after the generic one. $expected = array('block_form_form_test_alter_form_alter() executed.', 'form_test_form_alter() executed.', 'form_test_form_form_test_alter_form_alter() executed.', 'system_form_form_test_alter_form_alter() executed.'); $content = preg_replace('/\\s+/', ' ', Xss::filter($this->content, array())); $this->assert(strpos($content, implode(' ', $expected)) !== FALSE, 'Form alter hooks executed in the expected order.'); }
/** * Returns the array of recipient handler labels. * @todo documentation */ public function getOptions() { $handlers = $this->getDefinitions(); $allowed_values = array(); foreach ($handlers as $handler => $settings) { $allowed_values[$handler] = Xss::filter($settings['title']); } return $allowed_values; }
/** * Ensure that custom field content is XSS filtered. */ public function testCustomFieldXss() { $view = Views::getView('test_view'); $view->setDisplay(); // Alter the text of the field to include XSS. $text = '<script>alert("kittens")</script>'; $view->displayHandlers->get('default')->overrideOption('fields', array('name' => array('id' => 'name', 'table' => 'views_test_data', 'field' => 'name', 'relationship' => 'none', 'alter' => array('text' => $text)))); $this->executeView($view); $this->assertEqual(Xss::filter($text), $view->style_plugin->getField(0, 'name')); }
function at_core_submit_custom_css($values, $generated_files_path) { $custom_css = ''; if (!empty($values['settings_custom_css'])) { // sanitize user entered data $custom_css = Xss::filter($values['settings_custom_css']); } $file_name = 'custom-css.css'; $filepath = $generated_files_path . '/' . $file_name; file_unmanaged_save_data($custom_css, $filepath, FILE_EXISTS_REPLACE); }
/** * {@inheritdoc} */ public function process($text, $langcode) { $restrictions = $this->getHtmlRestrictions(); // Split the work into two parts. For filtering HTML tags out of the content // we rely on the well-tested Xss::filter() code. Since there is no '*' tag // that needs to be removed from the list. unset($restrictions['allowed']['*']); $text = Xss::filter($text, array_keys($restrictions['allowed'])); // After we've done tag filtering, we do attribute and attribute value // filtering as the second part. return new FilterProcessResult($this->filterAttributes($text)); }
/** * {@inheritdoc} */ public function checkoutInfo(JobInterface $job) { $tuid = $job->getSetting('translator'); if ($tuid && ($translator = User::load($tuid))) { $form['job_status'] = array('#type' => 'item', '#title' => t('Job status'), '#markup' => t('Translation job is assigned to %name.', array('%name' => $translator->getUsername()))); } else { $form['job_status'] = array('#type' => 'item', '#title' => t('Job status'), '#markup' => t('Translation job is not assigned to any user.')); } if ($job->getSetting('job_comment')) { $form['job_comment'] = array('#type' => 'item', '#title' => t('Job comment'), '#markup' => Xss::filter($job->getSetting('job_comment'))); } return $form; }
/** * Filters an HTML string to prevent XSS vulnerabilities. * * Like \Drupal\Component\Utility\Xss::filterAdmin(), but with a shorter list * of allowed tags. * * Used for items entered by administrators, like field descriptions, allowed * values, where some (mainly inline) mark-up may be desired (so * \Drupal\Component\Utility\SafeMarkup::checkPlain() is not acceptable). * * @param string $string * The string with raw HTML in it. * * @return \Drupal\Component\Utility\SafeMarkup * An XSS safe version of $string, or an empty string if $string is not * valid UTF-8. */ public function fieldFilterXss($string) { // All known XSS vectors are filtered out by // \Drupal\Component\Utility\Xss::filter(), all tags in the markup are // allowed intentionally by the trait, and no danger is added in by // \Drupal\Component\Utility\HTML::normalize(). Since the normalized value // is essentially the same markup, designate this string as safe as well. // This method is an internal part of field sanitization, so the resultant, // sanitized string should be printable as is. // // @todo Free this memory in https://www.drupal.org/node/2505963. return SafeMarkup::set(Html::normalize(Xss::filter($string, $this->allowedTags()))); }
/** * {@inheritdoc} */ public function zen() { $principles = $this->principleManager->getAllPrinciples(); $title = t('My mind is empty.'); if (count($principles) > 0) { // Get a random item from the array of principles $k = array_rand($principles); $principle = $principles[$k]; $title = Xss::filter($principle->title); } $build = array('#type' => 'markup', '#markup' => $title); return new Response(\Drupal::service('renderer')->renderRoot($build)); }
/** * Overrides \Drupal\Component\Utility\SafeStringTrait::create(). * * @return string|\Drupal\Component\Utility\SafeStringInterface * A safe string filtered with the allowed tag list and normalized. * * @see \Drupal\Core\Field\FieldFilteredString::allowedTags() * @see \Drupal\Component\Utility\Xss::filter() * @see \Drupal\Component\Utility\Html::normalize() */ public static function create($string) { $string = (string) $string; if ($string === '') { return ''; } $safe_string = new static(); // All known XSS vectors are filtered out by // \Drupal\Component\Utility\Xss::filter(), all tags in the markup are // allowed intentionally by the trait, and no danger is added in by // \Drupal\Component\Utility\HTML::normalize(). Since the normalized value // is essentially the same markup, designate this string as safe as well. // This method is an internal part of field sanitization, so the resultant, // sanitized string should be printable as is. $safe_string->string = Html::normalize(Xss::filter($string, static::allowedTags())); return $safe_string; }
/** * {@inheritdoc} */ public function process($text, $langcode) { $result = new FilterProcessResult($text); if (stristr($text, 'data-caption') !== FALSE) { $dom = Html::load($text); $xpath = new \DOMXPath($dom); foreach ($xpath->query('//*[@data-caption]') as $node) { // Read the data-caption attribute's value, then delete it. $caption = Html::escape($node->getAttribute('data-caption')); $node->removeAttribute('data-caption'); // Sanitize caption: decode HTML encoding, limit allowed HTML tags; only // allow inline tags that are allowed by default, plus <br>. $caption = Html::decodeEntities($caption); $caption = FilteredMarkup::create(Xss::filter($caption, array('a', 'em', 'strong', 'cite', 'code', 'br'))); // The caption must be non-empty. if (Unicode::strlen($caption) === 0) { continue; } // Given the updated node and caption: re-render it with a caption, but // bubble up the value of the class attribute of the captioned element, // this allows it to collaborate with e.g. the filter_align filter. $tag = $node->tagName; $classes = $node->getAttribute('class'); $node->removeAttribute('class'); $node = $node->parentNode->tagName === 'a' ? $node->parentNode : $node; $filter_caption = array('#theme' => 'filter_caption', '#node' => FilteredMarkup::create($node->C14N()), '#tag' => $tag, '#caption' => $caption, '#classes' => $classes); $altered_html = drupal_render($filter_caption); // Load the altered HTML into a new DOMDocument and retrieve the element. $updated_nodes = Html::load($altered_html)->getElementsByTagName('body')->item(0)->childNodes; foreach ($updated_nodes as $updated_node) { // Import the updated node from the new DOMDocument into the original // one, importing also the child nodes of the updated node. $updated_node = $dom->importNode($updated_node, TRUE); $node->parentNode->insertBefore($updated_node, $node); } // Finally, remove the original data-caption node. $node->parentNode->removeChild($node); } $result->setProcessedText(Html::serialize($dom))->addAttachments(array('library' => array('filter/caption'))); } return $result; }
/** * {@inheritdoc} */ public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { $this->configuration['wrappers'] = $form_state->getValue('region_wrapper'); foreach (['outer_wrapper', 'attributes', 'link_attribute', 'link_custom'] as $name) { $this->configuration[$name] = $this->configuration['wrappers'][$name]; unset($this->configuration['wrappers'][$name]); } // Apply Xss::filter to attributes. $this->configuration['attributes'] = Xss::filter($this->configuration['attributes']); // In case classes is missing entirely, use the defaults. $defaults = $this->defaultConfiguration(); $this->configuration['classes'] = $form_state->getValue('ds_classes', $defaults['classes']); // Do not save empty classes. foreach ($this->configuration['classes'] as $region_name => &$classes) { foreach ($classes as $class) { if (empty($class)) { unset($classes[$class]); } } } }
/** * Outputs the data that is used for the Coffee autocompletion in JSON. */ public function coffeeData() { $output = array(); // Get configured menus from configuration. $menus = \Drupal::config('coffee.configuration')->get('coffee_menus'); if ($menus !== NULL) { foreach ($menus as $v) { if ($v === '0') { continue; } // Build the menu tree. $menu_tree_parameters = new MenuTreeParameters(); $tree = \Drupal::menuTree()->load($v, $menu_tree_parameters); foreach ($tree as $key => $link) { $command = $v == 'user-menu' ? ':user' : NULL; $this->coffee_traverse_below($link, $output, $command); } } } module_load_include('inc', 'coffee', 'coffee.hooks'); $commands = array(); foreach (\Drupal::moduleHandler()->getImplementations('coffee_commands') as $module) { $commands = array_merge($commands, \Drupal::moduleHandler()->invoke($module, 'coffee_commands', array())); } if (!empty($commands)) { $output = array_merge($output, $commands); } foreach ($output as $k => $v) { if ($v['value'] == '<front>') { unset($output[$k]); continue; } // Filter out XSS. $output[$k]['label'] = Xss::filter($output[$k]['label']); } // Re-index the array. $output = array_values($output); return new JsonResponse($output); }
/** * {@inheritdoc} */ public function form(array $form, FormStateInterface $form_state) { $form = parent::form($form, $form_state); $task_item = $this->entity; $form['#title'] = $task_item->label(); $job_item = $task_item->getJobItem(); $job = $job_item->getJob(); $form['info'] = array('#type' => 'container', '#attributes' => array('class' => array('tmgmt-local-task-info', 'clearfix')), '#weight' => 0); $url = $job_item->getSourceUrl(); $form['info']['source'] = array('#type' => 'item', '#title' => t('Source'), '#markup' => $url ? Link::fromTextAndUrl($job_item->getSourceLabel(), $url)->toString() : $job_item->getSourceLabel(), '#prefix' => '<div class="tmgmt-ui-source tmgmt-ui-info-item">', '#suffix' => '</div>'); $form['info']['sourcetype'] = array('#type' => 'item', '#title' => t('Source type'), '#markup' => $job_item->getSourceType(), '#prefix' => '<div class="tmgmt-ui-source-type tmgmt-ui-info-item">', '#suffix' => '</div>'); $form['info']['source_language'] = array('#type' => 'item', '#title' => t('Source language'), '#markup' => $job_item->getJob()->getSourceLanguage()->getName(), '#prefix' => '<div class="tmgmt-ui-source-language tmgmt-ui-info-item">', '#suffix' => '</div>'); $form['info']['target_language'] = array('#type' => 'item', '#title' => t('Target language'), '#markup' => $job_item->getJob()->getTargetLanguage()->getName(), '#prefix' => '<div class="tmgmt-ui-target-language tmgmt-ui-info-item">', '#suffix' => '</div>'); $form['info']['changed'] = array('#type' => 'item', '#title' => t('Last change'), '#value' => $task_item->getChangedTime(), '#markup' => \Drupal::service('date.formatter')->format($task_item->getChangedTime()), '#prefix' => '<div class="tmgmt-ui-changed tmgmt-ui-info-item">', '#suffix' => '</div>'); $statuses = LocalTaskItem::getStatuses(); $form['info']['status'] = array('#type' => 'item', '#title' => t('Status'), '#markup' => $statuses[$task_item->getStatus()], '#prefix' => '<div class="tmgmt-ui-task-item-status tmgmt-ui-info-item">', '#suffix' => '</div>', '#value' => $task_item->getStatus()); $task = $task_item->getTask(); $url = $task->toUrl(); $form['info']['task'] = array('#type' => 'item', '#title' => t('Task'), '#markup' => Link::fromTextAndUrl($task->label(), $url)->toString(), '#prefix' => '<div class="tmgmt-ui-task tmgmt-ui-info-item">', '#suffix' => '</div>'); if ($job->getSetting('job_comment')) { $form['job_comment'] = array('#type' => 'item', '#title' => t('Job comment'), '#markup' => Xss::filter($job->getSetting('job_comment'))); } $form['translation'] = array('#type' => 'container'); // Build the translation form. $data = $task_item->getData(); // Need to keep the first hierarchy. So flatten must take place inside // of the foreach loop. $zebra = 'even'; foreach (Element::children($data) as $key) { $flattened = \Drupal::service('tmgmt.data')->flatten($data[$key], $key); $form['translation'][$key] = $this->formElement($flattened, $task_item, $zebra); } $form['footer'] = tmgmt_color_local_review_legend(); $form['#attached']['library'][] = 'tmgmt/admin'; return $form; }
/** * Tests the generation of all system date tokens. */ public function testSystemDateTokenReplacement() { // Set time to one hour before request. $date = REQUEST_TIME - 3600; // Generate and test tokens. $tests = array(); $date_formatter = \Drupal::service('date.formatter'); $tests['[date:short]'] = $date_formatter->format($date, 'short', '', NULL, $this->interfaceLanguage->getId()); $tests['[date:medium]'] = $date_formatter->format($date, 'medium', '', NULL, $this->interfaceLanguage->getId()); $tests['[date:long]'] = $date_formatter->format($date, 'long', '', NULL, $this->interfaceLanguage->getId()); $tests['[date:custom:m/j/Y]'] = $date_formatter->format($date, 'custom', 'm/j/Y', NULL, $this->interfaceLanguage->getId()); $tests['[date:since]'] = $date_formatter->formatTimeDiffSince($date, array('langcode' => $this->interfaceLanguage->getId())); $tests['[date:raw]'] = Xss::filter($date); // Test to make sure that we generated something for each token. $this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.'); foreach ($tests as $input => $expected) { $output = $this->tokenService->replace($input, array('date' => $date), array('langcode' => $this->interfaceLanguage->getId())); $this->assertEqual($output, $expected, format_string('Date token %token replaced.', array('%token' => $input))); } }
/** * Creates some terms and a node, then tests the tokens generated from them. */ function testTaxonomyTokenReplacement() { $token_service = \Drupal::token(); $language_interface = \Drupal::languageManager()->getCurrentLanguage(); // Create two taxonomy terms. $term1 = $this->createTerm($this->vocabulary); $term2 = $this->createTerm($this->vocabulary); // Edit $term2, setting $term1 as parent. $edit = array(); $edit['name[0][value]'] = '<blink>Blinking Text</blink>'; $edit['parent[]'] = array($term1->id()); $this->drupalPostForm('taxonomy/term/' . $term2->id() . '/edit', $edit, t('Save')); // Create node with term2. $edit = array(); $node = $this->drupalCreateNode(array('type' => 'article')); $edit[$this->field_name . '[]'] = $term2->id(); $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save')); // Generate and test sanitized tokens for term1. $tests = array(); $tests['[term:tid]'] = $term1->id(); $tests['[term:name]'] = String::checkPlain($term1->getName()); $tests['[term:description]'] = $term1->description->processed; $tests['[term:url]'] = $term1->url('canonical', array('absolute' => TRUE)); $tests['[term:node-count]'] = 0; $tests['[term:parent:name]'] = '[term:parent:name]'; $tests['[term:vocabulary:name]'] = String::checkPlain($this->vocabulary->name); foreach ($tests as $input => $expected) { $output = $token_service->replace($input, array('term' => $term1), array('langcode' => $language_interface->getId())); $this->assertEqual($output, $expected, format_string('Sanitized taxonomy term token %token replaced.', array('%token' => $input))); } // Generate and test sanitized tokens for term2. $tests = array(); $tests['[term:tid]'] = $term2->id(); $tests['[term:name]'] = String::checkPlain($term2->getName()); $tests['[term:description]'] = $term2->description->processed; $tests['[term:url]'] = $term2->url('canonical', array('absolute' => TRUE)); $tests['[term:node-count]'] = 1; $tests['[term:parent:name]'] = String::checkPlain($term1->getName()); $tests['[term:parent:url]'] = $term1->url('canonical', array('absolute' => TRUE)); $tests['[term:parent:parent:name]'] = '[term:parent:parent:name]'; $tests['[term:vocabulary:name]'] = String::checkPlain($this->vocabulary->name); // Test to make sure that we generated something for each token. $this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.'); foreach ($tests as $input => $expected) { $output = $token_service->replace($input, array('term' => $term2), array('langcode' => $language_interface->getId())); $this->assertEqual($output, $expected, format_string('Sanitized taxonomy term token %token replaced.', array('%token' => $input))); } // Generate and test unsanitized tokens. $tests['[term:name]'] = $term2->getName(); $tests['[term:description]'] = $term2->getDescription(); $tests['[term:parent:name]'] = $term1->getName(); $tests['[term:vocabulary:name]'] = $this->vocabulary->name; foreach ($tests as $input => $expected) { $output = $token_service->replace($input, array('term' => $term2), array('langcode' => $language_interface->getId(), 'sanitize' => FALSE)); $this->assertEqual($output, $expected, format_string('Unsanitized taxonomy term token %token replaced.', array('%token' => $input))); } // Generate and test sanitized tokens. $tests = array(); $tests['[vocabulary:vid]'] = $this->vocabulary->id(); $tests['[vocabulary:name]'] = String::checkPlain($this->vocabulary->name); $tests['[vocabulary:description]'] = Xss::filter($this->vocabulary->description); $tests['[vocabulary:node-count]'] = 1; $tests['[vocabulary:term-count]'] = 2; // Test to make sure that we generated something for each token. $this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.'); foreach ($tests as $input => $expected) { $output = $token_service->replace($input, array('vocabulary' => $this->vocabulary), array('langcode' => $language_interface->getId())); $this->assertEqual($output, $expected, format_string('Sanitized taxonomy vocabulary token %token replaced.', array('%token' => $input))); } // Generate and test unsanitized tokens. $tests['[vocabulary:name]'] = $this->vocabulary->name; $tests['[vocabulary:description]'] = $this->vocabulary->description; foreach ($tests as $input => $expected) { $output = $token_service->replace($input, array('vocabulary' => $this->vocabulary), array('langcode' => $language_interface->getId(), 'sanitize' => FALSE)); $this->assertEqual($output, $expected, format_string('Unsanitized taxonomy vocabulary token %token replaced.', array('%token' => $input))); } }
/** * Formats a backtrace into a plain-text string. * * The calls show values for scalar arguments and type names for complex ones. * * @param array $backtrace * A standard PHP backtrace. * * @return string * A plain-text line-wrapped string ready to be put inside <pre>. */ public static function formatBacktrace(array $backtrace) { $return = ''; foreach ($backtrace as $trace) { $call = array('function' => '', 'args' => array()); if (isset($trace['class'])) { $call['function'] = $trace['class'] . $trace['type'] . $trace['function']; } elseif (isset($trace['function'])) { $call['function'] = $trace['function']; } else { $call['function'] = 'main'; } if (isset($trace['args'])) { foreach ($trace['args'] as $arg) { if (is_scalar($arg)) { $call['args'][] = is_string($arg) ? '\'' . Xss::filter($arg) . '\'' : $arg; } else { $call['args'][] = ucfirst(gettype($arg)); } } } $return .= $call['function'] . '(' . implode(', ', $call['args']) . ")\n"; } return $return; }
/** * Route title callback. * * @param \Drupal\aggregator\FeedInterface $aggregator_feed * The aggregator feed. * * @return string * The feed label. */ public function feedTitle(FeedInterface $aggregator_feed) { return Xss::filter($aggregator_feed->label()); }
/** * {@inheritdoc} */ public function sanitizeValue($value, $type = NULL) { switch ($type) { case 'xss': $value = Xss::filter($value); break; case 'xss_admin': $value = Xss::filterAdmin($value); break; case 'url': $value = Html::escape(UrlHelper::stripDangerousProtocols($value)); break; default: $value = Html::escape($value); break; } return ViewsRenderPipelineMarkup::create($value); }
/** * Tests basic aggregator_item view. */ public function testAggregatorItemView() { /** @var \Drupal\Core\Render\RendererInterface $renderer */ $renderer = \Drupal::service('renderer'); $feed = $this->feedStorage->create(array('title' => $this->randomMachineName(), 'url' => 'https://www.drupal.org/', 'refresh' => 900, 'checked' => 123543535, 'description' => $this->randomMachineName())); $feed->save(); $items = array(); $expected = array(); for ($i = 0; $i < 10; $i++) { $values = array(); $values['fid'] = $feed->id(); $values['timestamp'] = mt_rand(REQUEST_TIME - 10, REQUEST_TIME + 10); $values['title'] = $this->randomMachineName(); $values['description'] = $this->randomMachineName(); // Add a image to ensure that the sanitizing can be tested below. $values['author'] = $this->randomMachineName() . '<img src="http://example.com/example.png" \\>"'; $values['link'] = 'https://www.drupal.org/node/' . mt_rand(1000, 10000); $values['guid'] = $this->randomString(); $aggregator_item = $this->itemStorage->create($values); $aggregator_item->save(); $items[$aggregator_item->id()] = $aggregator_item; $values['iid'] = $aggregator_item->id(); $expected[] = $values; } $view = Views::getView('test_aggregator_items'); $this->executeView($view); $column_map = array('iid' => 'iid', 'title' => 'title', 'aggregator_item_timestamp' => 'timestamp', 'description' => 'description', 'aggregator_item_author' => 'author'); $this->assertIdenticalResultset($view, $expected, $column_map); // Ensure that the rendering of the linked title works as expected. foreach ($view->result as $row) { $iid = $view->field['iid']->getValue($row); $expected_link = \Drupal::l($items[$iid]->getTitle(), Url::fromUri($items[$iid]->getLink(), ['absolute' => TRUE])); $output = $renderer->executeInRenderContext(new RenderContext(), function () use($view, $row) { return $view->field['title']->advancedRender($row); }); $this->assertEqual($output, $expected_link->getGeneratedLink(), 'Ensure the right link is generated'); $expected_author = Xss::filter($items[$iid]->getAuthor(), _aggregator_allowed_tags()); $output = $renderer->executeInRenderContext(new RenderContext(), function () use($view, $row) { return $view->field['author']->advancedRender($row); }); $this->assertEqual($output, $expected_author, 'Ensure the author got filtered'); $expected_description = Xss::filter($items[$iid]->getDescription(), _aggregator_allowed_tags()); $output = $renderer->executeInRenderContext(new RenderContext(), function () use($view, $row) { return $view->field['description']->advancedRender($row); }); $this->assertEqual($output, $expected_description, 'Ensure the author got filtered'); } }
/** * {@inheritdoc} */ public function sanitizeValue($value, $type = NULL) { switch ($type) { case 'xss': $value = Xss::filter($value); break; case 'xss_admin': $value = Xss::filterAdmin($value); break; case 'url': $value = SafeMarkup::checkPlain(UrlHelper::stripDangerousProtocols($value)); break; default: $value = SafeMarkup::checkPlain($value); break; } return $value; }
/** * Formats a date, using a date type or a custom date format string. * * @param int $timestamp * A UNIX timestamp to format. * @param string $type * (optional) The format to use, one of: * - One of the built-in formats: 'short', 'medium', * 'long', 'html_datetime', 'html_date', 'html_time', * 'html_yearless_date', 'html_week', 'html_month', 'html_year'. * - The name of a date type defined by a date format config entity. * - The machine name of an administrator-defined date format. * - 'custom', to use $format. * Defaults to 'medium'. * @param string $format * (optional) If $type is 'custom', a PHP date format string suitable for * input to date(). Use a backslash to escape ordinary text, so it does not * get interpreted as date format characters. * @param string|null $timezone * (optional) Time zone identifier, as described at * http://php.net/manual/timezones.php Defaults to the time zone used to * display the page. * @param string|null $langcode * (optional) Language code to translate to. NULL (default) means to use * the user interface language for the page. * * @return string * A translated date string in the requested format. */ public function format($timestamp, $type = 'medium', $format = '', $timezone = NULL, $langcode = NULL) { if (!isset($timezone)) { $timezone = date_default_timezone_get(); } // Store DateTimeZone objects in an array rather than repeatedly // constructing identical objects over the life of a request. if (!isset($this->timezones[$timezone])) { $this->timezones[$timezone] = timezone_open($timezone); } if (empty($langcode)) { $langcode = $this->languageManager->getCurrentLanguage()->getId(); } // Create a DrupalDateTime object from the timestamp and timezone. $create_settings = array('langcode' => $langcode, 'country' => $this->country()); $date = DrupalDateTime::createFromTimestamp($timestamp, $this->timezones[$timezone], $create_settings); // If we have a non-custom date format use the provided date format pattern. if ($date_format = $this->dateFormat($type, $langcode)) { $format = $date_format->getPattern(); } // Fall back to medium if a format was not found. if (empty($format)) { $format = $this->dateFormat('fallback', $langcode)->getPattern(); } // Call $date->format(). $settings = array('langcode' => $langcode); return Xss::filter($date->format($format, $settings)); }
/** * Tests execution order of theme suggestion alter hooks. * * hook_theme_suggestions_alter() should fire before * hook_theme_suggestions_HOOK_alter() within an extension (module or theme). */ function testExecutionOrder() { // Enable our test theme and module. \Drupal::config('system.theme')->set('default', 'test_theme')->save(); \Drupal::moduleHandler()->install(array('theme_suggestions_test')); $this->resetAll(); // Send two requests so that we get all the messages we've set via // drupal_set_message(). $this->drupalGet('theme-test/suggestion-alter'); // Ensure that the order is first by extension, then for a given extension, // the hook-specific one after the generic one. $expected = array('theme_suggestions_test_theme_suggestions_alter() executed.', 'theme_suggestions_test_theme_suggestions_theme_test_suggestions_alter() executed.', 'theme_test_theme_suggestions_alter() executed.', 'theme_test_theme_suggestions_theme_test_suggestions_alter() executed.', 'test_theme_theme_suggestions_alter() executed.', 'test_theme_theme_suggestions_theme_test_suggestions_alter() executed.'); $content = preg_replace('/\\s+/', ' ', Xss::filter($this->content, array())); $this->assert(strpos($content, implode(' ', $expected)) !== FALSE, 'Suggestion alter hooks executed in the expected order.'); }
function at_core_submit_fonts($values, $generated_files_path) { // Websafe fonts. $websafe_fonts = $values['websafe_options']; // Elements to apply fonts to. $font_elements = font_elements(); // Fallback family $fallback_font_family = 'sans-serif'; if (isset($values['settings_font_fallback'])) { $fallback_font_family = str_replace('_', '-', $values['settings_font_fallback']); } // Initialize some variables. $fonts = array(); $base_size = '16'; // 16px default // Inject config settings for web-fonts. $values['settings_font_use_google_fonts'] = FALSE; $values['settings_font_use_typekit'] = FALSE; $font_styles = array(); foreach ($font_elements as $font_key => $font_values) { // Get the selectors for each element. $fonts[$font_key]['selectors'] = $font_values['selector']; // Reset the selectors variable if we have custom selectors. if ($font_key == 'custom_selectors' && !empty($values['settings_font_custom_selectors']) && !empty($values['settings_custom_selectors'])) { $fonts[$font_key]['selectors'] = $values['settings_custom_selectors']; // ? $values['settings_custom_selectors'] : 'ruby ruby' } // Size/Line height. if (!empty($values['settings_font_size_' . $font_key])) { //$base_size = $values['settings_font_size_base'] ? $values['settings_font_size_base'] : $base_size; $px_size = $values['settings_font_size_' . $font_key]; $rem_size = $values['settings_font_size_' . $font_key] / $base_size; // line-height multipliers are a bit magical, but "pretty good" defaults. $line_height_multiplier = $values['settings_font_line_height_multiplier_default']; if ($px_size >= $values['settings_font_line_height_multiplier_large_size']) { $line_height_multiplier = $values['settings_font_line_height_multiplier_large']; } $fonts[$font_key]['size'] = ' font-size: ' . ceil($px_size) . 'px; font-size: ' . round($rem_size, 3) . 'rem;'; $fonts[$font_key]['line_height'] = ' line-height: ' . ceil($px_size * $line_height_multiplier) . 'px; line-height: ' . round($rem_size * $line_height_multiplier, 3) . 'rem;'; // if (isset($values['settings_font_size_base'])) { // $rem_size = $base_size / 16; // $fonts['base']['size'] = ' font-size: ' . ceil($px_size) . 'px; font-size: ' . round($rem_size, 3) . 'rem;'; // $fonts['base']['line_height'] = ' line-height: ' . ceil($px_size * $line_height_multiplier) . 'px; line-height: ' . round($rem_size * $line_height_multiplier, 3) . 'rem;'; // } } // Set font family for each key. if (isset($values['settings_font_' . $font_key])) { // Websafe. if ($values['settings_font_' . $font_key] == 'websafe') { if (isset($values['settings_font_websafe_' . $font_key])) { if (!empty($websafe_fonts[$values['settings_font_websafe_' . $font_key]])) { $websafe_font = $websafe_fonts[$values['settings_font_websafe_' . $font_key]]; $fonts[$font_key]['family'] = 'font-family: ' . trim($websafe_font) . ';'; } else { $fonts[$font_key]['family'] = 'font-family: inherit;'; } } else { $fonts[$font_key]['family'] = 'font-family: inherit;'; } } // Google. if ($values['settings_font_' . $font_key] == 'google') { if (isset($values['settings_font_google_' . $font_key])) { $fonts[$font_key]['family'] = 'font-family: ' . $values['settings_font_google_' . $font_key] . ', ' . trim($fallback_font_family) . ';'; $values['settings_font_use_google_fonts'] = TRUE; } else { $fonts[$font_key]['family'] = 'font-family: inherit;'; } } // Typekit. if ($values['settings_font_' . $font_key] == 'typekit') { if (!empty($values['settings_font_typekit_' . $font_key])) { $fonts[$font_key]['family'] = 'font-family: ' . $values['settings_font_typekit_' . $font_key] . ', ' . trim($fallback_font_family) . ';'; $values['settings_font_use_typekit'] = TRUE; } else { $fonts[$font_key]['family'] = 'font-family: inherit;'; } } } // Font smoothing. if (isset($values['settings_font_smoothing_' . $font_key]) && $values['settings_font_smoothing_' . $font_key] == 1) { $fonts[$font_key]['smoothing'] = ' -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;'; } } // Output data to file. if (!empty($fonts)) { foreach ($fonts as $font_key => $font_values) { if (isset($font_values['family']) || isset($font_values['size'])) { $font_style = $font_values['selectors'] . ' { '; if (isset($font_values['family'])) { $font_style .= str_replace(';;', ';', $font_values['family']); } if (isset($font_values['size'])) { $font_style .= $font_values['size']; } if (isset($font_values['line_height'])) { $font_style .= $font_values['line_height']; } if (isset($font_values['smoothing'])) { $font_style .= $font_values['smoothing']; } $font_style .= ' }'; $font_styles[] = $font_style; } } $output = implode("\n", $font_styles); } $output = $output ? Xss::filter($output) : '/** No fonts styles set **/'; $file_name = 'fonts.css'; $filepath = "{$generated_files_path}/{$file_name}"; file_unmanaged_save_data($output, $filepath, FILE_EXISTS_REPLACE); // Return modified values to convert to config. return $values; }
/** * Displays a listing of database log messages. * * Messages are truncated at 56 chars. * Full-length messages can be viewed on the message details page. * * @return array * A render array as expected by drupal_render(). * * @see dblog_clear_log_form() * @see dblog_event() */ public function overview() { $filter = $this->buildFilterQuery(); $rows = array(); $classes = static::getLogLevelClassMap(); $this->moduleHandler->loadInclude('dblog', 'admin.inc'); $build['dblog_filter_form'] = $this->formBuilder->getForm('Drupal\\dblog\\Form\\DblogFilterForm'); $build['dblog_clear_log_form'] = $this->formBuilder->getForm('Drupal\\dblog\\Form\\DblogClearLogForm'); $header = array('', array('data' => $this->t('Type'), 'field' => 'w.type', 'class' => array(RESPONSIVE_PRIORITY_MEDIUM)), array('data' => $this->t('Date'), 'field' => 'w.wid', 'sort' => 'desc', 'class' => array(RESPONSIVE_PRIORITY_LOW)), $this->t('Message'), array('data' => $this->t('User'), 'field' => 'u.name', 'class' => array(RESPONSIVE_PRIORITY_MEDIUM)), array('data' => $this->t('Operations'), 'class' => array(RESPONSIVE_PRIORITY_LOW))); $query = $this->database->select('watchdog', 'w')->extend('\\Drupal\\Core\\Database\\Query\\PagerSelectExtender')->extend('\\Drupal\\Core\\Database\\Query\\TableSortExtender'); $query->fields('w', array('wid', 'uid', 'severity', 'type', 'timestamp', 'message', 'variables', 'link')); if (!empty($filter['where'])) { $query->where($filter['where'], $filter['args']); } $result = $query->limit(50)->orderByHeader($header)->execute(); foreach ($result as $dblog) { $message = $this->formatMessage($dblog); if ($message && isset($dblog->wid)) { // Truncate link_text to 56 chars of message. $log_text = Unicode::truncate(Xss::filter($message, array()), 56, TRUE, TRUE); $message = $this->l($log_text, 'dblog.event', array('event_id' => $dblog->wid), array('html' => TRUE)); } $username = array('#theme' => 'username', '#account' => user_load($dblog->uid)); $rows[] = array('data' => array(array('class' => array('icon')), $this->t($dblog->type), $this->dateFormatter->format($dblog->timestamp, 'short'), $message, array('data' => $username), Xss::filter($dblog->link)), 'class' => array(drupal_html_class('dblog-' . $dblog->type), $classes[$dblog->severity])); } $build['dblog_table'] = array('#type' => 'table', '#header' => $header, '#rows' => $rows, '#attributes' => array('id' => 'admin-dblog', 'class' => array('admin-dblog')), '#empty' => $this->t('No log messages available.'), '#attached' => array('library' => array('dblog/drupal.dblog'))); $build['dblog_pager'] = array('#theme' => 'pager'); return $build; }
/** * Helper function called from preg_replace_callback() above. * * Uses static vars to temporarily store footnotes found. * This is not threadsafe, but PHP isn't. * * @param array $matches * Elements from array: * - 0: complete matched string. * - 1: tag name. * - 2: tag attributes. * - 3: tag content. * @param string $op * Operation. * * @return string * Return the string processed by geshi library. */ protected function replaceCallback($matches, $op = '') { static $opt_collapse = 0; static $n = 0; static $store_matches = array(); static $used_values = array(); $str = ''; if ($op == 'prepare') { // In the 'prepare' case, the first argument contains the options to use. // The name 'matches' is incorrect, we just use the variable anyway. $opt_collapse = $matches; return 0; } if ($op == 'output footer') { if (count($store_matches) > 0) { // Only if there are stored fn matches, pass the array of fns to be // themed as a list Drupal 7 requires we use "render element" which // just introduces a wrapper around the old array. // @FIXME // theme() has been renamed to _theme() and should NEVER be called // directly. Calling _theme() directly can alter the expected output and // potentially introduce security issues // (see https://www.drupal.org/node/2195739). You should use renderable // arrays instead. @see https://www.drupal.org/node/2195739 $markup = array('#theme' => 'footnote_list', '#footnotes' => $store_matches); $str = \Drupal::service('renderer')->render($markup, FALSE); } // Reset the static variables so they can be used again next time. $n = 0; $store_matches = array(); $used_values = array(); return $str; } // Default op: act as called by preg_replace_callback() // Random string used to ensure footnote id's are unique, even // when contents of multiple nodes reside on same page. // (fixes http://drupal.org/node/194558). $randstr = $this->randstr(); $value = ''; // Did the pattern match anything in the <fn> tag? if ($matches[1]) { // See if value attribute can parsed, either well-formed in quotes eg // <fn value="3">. if (preg_match('|value=["\'](.*?)["\']|', $matches[1], $value_match)) { $value = $value_match[1]; // Or without quotes eg <fn value=8>. } elseif (preg_match('|value=(\\S*)|', $matches[1], $value_match)) { $value = $value_match[1]; } } if ($value) { // A value label was found. If it is numeric, record it in $n so further // notes can increment from there. // After adding support for multiple references to same footnote in the // body (http://drupal.org/node/636808) also must check that $n is // monotonously increasing. if (is_numeric($value) && $n < $value) { $n = $value; } } elseif ($opt_collapse and $value_existing = $this->findFootnote($matches[2], $store_matches)) { // An identical footnote already exists. Set value to the previously // existing value. $value = $value_existing; } else { // No value label, either a plain <fn> or unparsable attributes. Increment // the footnote counter, set label equal to it. $n++; $value = $n; } // Remove illegal characters from $value so it can be used as an HTML id // attribute. $value_id = preg_replace('|[^\\w\\-]|', '', $value); // Create a sanitized version of $text that is suitable for using as HTML // attribute value. (In particular, as the title attribute to the footnote // link). $allowed_tags = array(); $text_clean = Xss::filter($matches['2'], $allowed_tags); // HTML attribute cannot contain quotes. $text_clean = str_replace('"', """, $text_clean); // Remove newlines. Browsers don't support them anyway and they'll confuse // line break converter in filter.module. $text_clean = str_replace("\n", " ", $text_clean); $text_clean = str_replace("\r", "", $text_clean); // Create a footnote item as an array. $fn = array('value' => $value, 'text' => $matches[2], 'text_clean' => $text_clean, 'fn_id' => 'footnote' . $value_id . '_' . $randstr, 'ref_id' => 'footnoteref' . $value_id . '_' . $randstr); // We now allow to repeat the footnote value label, in which case the link // to the previously existing footnote is returned. Content of the current // footnote is ignored. See http://drupal.org/node/636808 . if (!in_array($value, $used_values)) { // This is the normal case, add the footnote to $store_matches. // Store the footnote item. array_push($store_matches, $fn); array_push($used_values, $value); } else { // A footnote with the same label already exists. // Use the text and id from the first footnote with this value. // Any text in this footnote is discarded. $i = array_search($value, $used_values); $fn['text'] = $store_matches[$i]['text']; $fn['text_clean'] = $store_matches[$i]['text_clean']; $fn['fn_id'] = $store_matches[$i]['fn_id']; // Push the new ref_id into the first occurence of this footnote label // The stored footnote thus holds a list of ref_id's rather than just one // id. $ref_array = is_array($store_matches[$i]['ref_id']) ? $store_matches[$i]['ref_id'] : array($store_matches[$i]['ref_id']); array_push($ref_array, $fn['ref_id']); $store_matches[$i]['ref_id'] = $ref_array; } // Return the item themed into a footnote link. // Drupal 7 requires we use "render element" which just introduces a wrapper // around the old array. $fn = array('#theme' => 'footnote_link', 'fn' => $fn); $result = \Drupal::service('renderer')->render($fn, FALSE); return $result; }
/** * Generates an overview table of older revisions of a node. * * @param \Drupal\node\NodeInterface $node * A node object. * * @return array * An array as expected by drupal_render(). */ public function revisionOverview(NodeInterface $node) { $account = $this->currentUser(); $node_storage = $this->entityManager()->getStorage('node'); $type = $node->getType(); $build = array(); $build['#title'] = $this->t('Revisions for %title', array('%title' => $node->label())); $header = array($this->t('Revision'), $this->t('Operations')); $revert_permission = ($account->hasPermission("revert {$type} revisions") || $account->hasPermission('revert all revisions') || $account->hasPermission('administer nodes')) && $node->access('update'); $delete_permission = ($account->hasPermission("delete {$type} revisions") || $account->hasPermission('delete all revisions') || $account->hasPermission('administer nodes')) && $node->access('delete'); $rows = array(); $vids = $node_storage->revisionIds($node); foreach (array_reverse($vids) as $vid) { if ($revision = $node_storage->loadRevision($vid)) { $row = array(); $revision_author = $revision->uid->entity; if ($vid == $node->getRevisionId()) { $username = array('#theme' => 'username', '#account' => $revision_author); $row[] = array('data' => $this->t('!date by !username', array('!date' => $node->link($this->dateFormatter->format($revision->revision_timestamp->value, 'short')), '!username' => drupal_render($username))) . ($revision->revision_log->value != '' ? '<p class="revision-log">' . Xss::filter($revision->revision_log->value) . '</p>' : ''), 'class' => array('revision-current')); $row[] = array('data' => SafeMarkup::placeholder($this->t('current revision')), 'class' => array('revision-current')); } else { $username = array('#theme' => 'username', '#account' => $revision_author); $row[] = $this->t('!date by !username', array('!date' => $this->l($this->dateFormatter->format($revision->revision_timestamp->value, 'short'), new Url('entity.node.revision', array('node' => $node->id(), 'node_revision' => $vid))), '!username' => drupal_render($username))) . ($revision->revision_log->value != '' ? '<p class="revision-log">' . Xss::filter($revision->revision_log->value) . '</p>' : ''); if ($revert_permission) { $links['revert'] = array('title' => $this->t('Revert'), 'url' => Url::fromRoute('node.revision_revert_confirm', ['node' => $node->id(), 'node_revision' => $vid])); } if ($delete_permission) { $links['delete'] = array('title' => $this->t('Delete'), 'url' => Url::fromRoute('node.revision_delete_confirm', ['node' => $node->id(), 'node_revision' => $vid])); } $row[] = array('data' => array('#type' => 'operations', '#links' => $links)); } $rows[] = $row; } } $build['node_revisions_table'] = array('#theme' => 'table', '#rows' => $rows, '#header' => $header, '#attached' => array('library' => array('node/drupal.node.admin'))); return $build; }