/** * Tests {@link Convert::raw2att()} */ public function testRaw2Att() { $val1 = '<input type="text">'; $this->assertEquals('<input type="text">', Convert::raw2att($val1), 'Special characters are escaped'); $val2 = 'This is some normal text.'; $this->assertEquals('This is some normal text.', Convert::raw2att($val2), 'Normal text is not escaped'); }
/** * @return string */ public function getContent() { $doc = clone $this->getDocument(); $xp = new DOMXPath($doc); // If there's no body, the content is empty string if (!$doc->getElementsByTagName('body')->length) { return ''; } // saveHTML Percentage-encodes any URI-based attributes. We don't want this, since it interferes with // shortcodes. So first, save all the attribute values for later restoration. $attrs = array(); $i = 0; foreach ($xp->query('//body//@*') as $attr) { $key = "__HTMLVALUE_" . $i++; $attrs[$key] = $attr->value; $attr->value = $key; } // Then, call saveHTML & extract out the content from the body tag $res = preg_replace(array('/^(.*?)<body>/is', '/<\\/body>(.*?)$/isD'), '', $doc->saveHTML()); // Then replace the saved attributes with their original versions $res = preg_replace_callback('/__HTMLVALUE_(\\d+)/', function ($matches) use($attrs) { return Convert::raw2att($attrs[$matches[0]]); }, $res); // Prevent being encoded as literal utf-8 characters // Possible alternative solution: http://stackoverflow.com/questions/2142120/php-encoding-with-domdocument $from = mb_convert_encoding(' ', 'utf-8', 'html-entities'); $res = str_replace($from, ' ', $res); return $res; }
/** * Get raw HTML for image markup * * @param File $file * @return string */ protected function getIconMarkup($file) { if (!$file) { return null; } $previewLink = Convert::raw2att($file->PreviewLink()); return "<img src=\"{$previewLink}\" class=\"editor__thumbnail\" />"; }
/** * overloaded to display the correctly formated value for this datatype * * @param array $properties * @return string */ public function Field($properties = array()) { if ($this->value) { $val = Convert::raw2xml($this->value); $val = _t('CurrencyField.CURRENCYSYMBOL', '$') . number_format(preg_replace('/[^0-9.]/', "", $val), 2); $valforInput = Convert::raw2att($val); } else { $valforInput = ''; } return "<input class=\"text\" type=\"text\" disabled=\"disabled\"" . " name=\"" . $this->name . "\" value=\"" . $valforInput . "\" />"; }
/** * Overloaded to display the correctly formated value for this datatype * * @param array $properties * @return string */ public function Field($properties = array()) { if ($this->value) { $val = Convert::raw2xml($this->value); $val = _t('CurrencyField.CURRENCYSYMBOL', '$') . number_format(preg_replace('/[^0-9.]/', "", $val), 2); $valforInput = Convert::raw2att($val); } else { $val = '<i>' . _t('CurrencyField.CURRENCYSYMBOL', '$') . '0.00</i>'; $valforInput = ''; } return "<span class=\"readonly " . $this->extraClass() . "\" id=\"" . $this->ID() . "\">{$val}</span>" . "<input type=\"hidden\" name=\"" . $this->name . "\" value=\"" . $valforInput . "\" />"; }
protected function getSpecsMarkup($record) { if (!$record || !$record->isInDB()) { return null; } /** * Can remove .label and .label-info when Bootstrap has been updated to BS4 Beta * .label is being replaced with .tag */ $versionTag = sprintf('<span class="label label-info tag tag-info">v.%s</span>', $record->Version); $agoTag = sprintf('%s <time class="relative-time" title="%s">%s</time>', $record->WasPublished ? _t('SilverStripe\\AssetAdmin\\Forms\\FileHistoryFormFactory.PUBLISHED', 'Published') : _t('SilverStripe\\AssetAdmin\\Forms\\FileHistoryFormFactory.SAVED', 'Saved'), Convert::raw2att($record->LastEdited), Convert::raw2xml($record->dbObject('LastEdited')->Ago())); return sprintf('<div class="editor__specs">%s %s, %s %s</div>', $versionTag, $agoTag, $record->getSize(), $this->getStatusFlagMarkup($record)); }
/** * Redirects the user to the external login page * * @return HTTPResponse */ protected function redirectToExternalLogin() { $loginURL = Security::create()->Link('login'); $loginURLATT = Convert::raw2att($loginURL); $loginURLJS = Convert::raw2js($loginURL); $message = _t('CMSSecurity.INVALIDUSER', '<p>Invalid user. <a target="_top" href="{link}">Please re-authenticate here</a> to continue.</p>', 'Message displayed to user if their session cannot be restored', array('link' => $loginURLATT)); $response = $this->getResponse(); $response->setStatusCode(200); $response->setBody(<<<PHP <!DOCTYPE html> <html><body> {$message} <script type="application/javascript"> setTimeout(function(){top.location.href = "{$loginURLJS}";}, 0); </script> </body></html> PHP ); $this->setResponse($response); return $response; }
/** * The process() method handles the "meat" of the template processing. * * It takes care of caching the output (via {@link Cache}), as well as * replacing the special "$Content" and "$Layout" placeholders with their * respective subtemplates. * * The method injects extra HTML in the header via {@link Requirements::includeInHTML()}. * * Note: You can call this method indirectly by {@link ViewableData->renderWith()}. * * @param ViewableData $item * @param array|null $arguments Arguments to an included template * @param ViewableData $inheritedScope The current scope of a parent template including a sub-template * @return DBHTMLText Parsed template output. */ public function process($item, $arguments = null, $inheritedScope = null) { SSViewer::$topLevel[] = $item; $template = $this->chosen; $cacheFile = TEMP_FOLDER . "/.cache" . str_replace(array('\\', '/', ':'), '.', Director::makeRelative(realpath($template))); $lastEdited = filemtime($template); if (!file_exists($cacheFile) || filemtime($cacheFile) < $lastEdited) { $content = file_get_contents($template); $content = $this->parseTemplateContent($content, $template); $fh = fopen($cacheFile, 'w'); fwrite($fh, $content); fclose($fh); } $underlay = array('I18NNamespace' => basename($template)); // Makes the rendered sub-templates available on the parent item, // through $Content and $Layout placeholders. foreach (array('Content', 'Layout') as $subtemplate) { $sub = null; if (isset($this->subTemplates[$subtemplate])) { $sub = $this->subTemplates[$subtemplate]; } elseif (!is_array($this->templates)) { $sub = ['type' => $subtemplate, $this->templates]; } elseif (!array_key_exists('type', $this->templates) || !$this->templates['type']) { $sub = array_merge($this->templates, ['type' => $subtemplate]); } if ($sub) { $subtemplateViewer = clone $this; // Disable requirements - this will be handled by the parent template $subtemplateViewer->includeRequirements(false); // Select the right template $subtemplateViewer->setTemplate($sub); if ($subtemplateViewer->exists()) { $underlay[$subtemplate] = $subtemplateViewer->process($item, $arguments); } } } $output = $this->includeGeneratedTemplate($cacheFile, $item, $arguments, $underlay, $inheritedScope); if ($this->includeRequirements) { $output = Requirements::includeInHTML($output); } array_pop(SSViewer::$topLevel); // If we have our crazy base tag, then fix # links referencing the current page. $rewrite = SSViewer::config()->get('rewrite_hash_links'); if ($this->rewriteHashlinks && $rewrite) { if (strpos($output, '<base') !== false) { if ($rewrite === 'php') { $thisURLRelativeToBase = "<?php echo \\SilverStripe\\Core\\Convert::raw2att(preg_replace(\"/^(\\\\/)+/\", \"/\", \$_SERVER['REQUEST_URI'])); ?>"; } else { $thisURLRelativeToBase = Convert::raw2att(preg_replace("/^(\\/)+/", "/", $_SERVER['REQUEST_URI'])); } $output = preg_replace('/(<a[^>]+href *= *)"#/i', '\\1"' . $thisURLRelativeToBase . '#', $output); } } return DBField::create_field('HTMLFragment', $output); }
/** * Return the attributes of the form tag - used by the templates. * * @param array $attrs Custom attributes to process. Falls back to {@link getAttributes()}. * If at least one argument is passed as a string, all arguments act as excludes by name. * * @return string HTML attributes, ready for insertion into an HTML tag */ public function getAttributesHTML($attrs = null) { $exclude = is_string($attrs) ? func_get_args() : null; // Figure out if we can cache this form // - forms with validation shouldn't be cached, cos their error messages won't be shown // - forms with security tokens shouldn't be cached because security tokens expire $needsCacheDisabled = false; if ($this->getSecurityToken()->isEnabled()) { $needsCacheDisabled = true; } if ($this->FormMethod() != 'GET') { $needsCacheDisabled = true; } if (!$this->validator instanceof RequiredFields || count($this->validator->getRequired())) { $needsCacheDisabled = true; } // If we need to disable cache, do it if ($needsCacheDisabled) { HTTP::set_cache_age(0); } $attrs = $this->getAttributes(); // Remove empty $attrs = array_filter((array) $attrs, create_function('$v', 'return ($v || $v === 0);')); // Remove excluded if ($exclude) { $attrs = array_diff_key($attrs, array_flip($exclude)); } // Prepare HTML-friendly 'method' attribute (lower-case) if (isset($attrs['method'])) { $attrs['method'] = strtolower($attrs['method']); } // Create markup $parts = array(); foreach ($attrs as $name => $value) { $parts[] = $value === true ? "{$name}=\"{$name}\"" : "{$name}=\"" . Convert::raw2att($value) . "\""; } return implode(' ', $parts); }
/** * Returns a version of a title suitable for insertion into an HTML attribute. * * @return string */ public function attrValue() { return Convert::raw2att($this->value); }
/** * HTML Content for external link * * @return string */ public function getExternalLink() { $title = $this->file ? $this->file->getTitle() : $this->getName(); return sprintf('<a href="%1$s" title="%2$s" target="_blank" rel="external" class="file-url">%1$s</a>', Convert::raw2att($this->url), Convert::raw2att($title)); }
/** * Get part of the current classes ancestry to be used as a CSS class. * * This method returns an escaped string of CSS classes representing the current classes ancestry until it hits a * stop point - e.g. "Page DataObject ViewableData". * * @param string $stopAtClass the class to stop at (default: ViewableData) * @return string * @uses ClassInfo */ public function CSSClasses($stopAtClass = 'SilverStripe\\View\\ViewableData') { $classes = array(); $classAncestry = array_reverse(ClassInfo::ancestry($this->class)); $stopClasses = ClassInfo::ancestry($stopAtClass); foreach ($classAncestry as $class) { if (in_array($class, $stopClasses)) { break; } $classes[] = $class; } // optionally add template identifier if (isset($this->template) && !in_array($this->template, $classes)) { $classes[] = $this->template; } // Strip out namespaces $classes = preg_replace('#.*\\\\#', '', $classes); return Convert::raw2att(implode(' ', $classes)); }
/** * Regenerates "[image id=n]" shortcode with new src attribute prior to being edited within the CMS. * * @param array $args Arguments passed to the parser * @param string $content Raw shortcode * @param ShortcodeParser $parser Parser * @param string $shortcode Name of shortcode used to register this handler * @param array $extra Extra arguments * @return string Result of the handled shortcode */ public static function regenerate_shortcode($args, $content, $parser, $shortcode, $extra = array()) { // Check if there is a suitable record $record = static::find_shortcode_record($args); if ($record) { $args['src'] = $record->getURL(); } // Rebuild shortcode $parts = array(); foreach ($args as $name => $value) { $htmlValue = Convert::raw2att($value ?: $name); $parts[] = sprintf('%s="%s"', $name, $htmlValue); } return sprintf("[%s %s]", $shortcode, implode(' ', $parts)); }
/** * Gets the value appropriate for a HTML attribute string * * @return string */ public function ATT() { return Convert::raw2att($this->RAW()); }
/** * @param array $attrs * @return DBHTMLText */ public function getAttributesHTML($attrs = null) { $excludeKeys = is_string($attrs) ? func_get_args() : null; if (!$attrs || is_string($attrs)) { $attrs = $this->attributes; } // Remove empty or excluded values foreach ($attrs as $key => $value) { if ($excludeKeys && in_array($key, $excludeKeys) || !$value && $value !== 0 && $value !== '0') { unset($attrs[$key]); continue; } } // Create markkup $parts = array(); foreach ($attrs as $name => $value) { $parts[] = $value === true ? "{$name}=\"{$name}\"" : "{$name}=\"" . Convert::raw2att($value) . "\""; } return DBField::create_field('HTMLFragment', implode(' ', $parts)); }
/** * Generate a CSV import form for a single {@link DataObject} subclass. * * @return Form|false */ public function ImportForm() { $modelSNG = singleton($this->modelClass); $modelName = $modelSNG->i18n_singular_name(); // check if a import form should be generated if (!$this->showImportForm || is_array($this->showImportForm) && !in_array($this->modelClass, $this->showImportForm)) { return false; } $importers = $this->getModelImporters(); if (!$importers || !isset($importers[$this->modelClass])) { return false; } if (!$modelSNG->canCreate(Member::currentUser())) { return false; } $fields = new FieldList(new HiddenField('ClassName', _t('ModelAdmin.CLASSTYPE'), $this->modelClass), new FileField('_CsvFile', false)); // get HTML specification for each import (column names etc.) $importerClass = $importers[$this->modelClass]; /** @var BulkLoader $importer */ $importer = new $importerClass($this->modelClass); $spec = $importer->getImportSpec(); $specFields = new ArrayList(); foreach ($spec['fields'] as $name => $desc) { $specFields->push(new ArrayData(array('Name' => $name, 'Description' => $desc))); } $specRelations = new ArrayList(); foreach ($spec['relations'] as $name => $desc) { $specRelations->push(new ArrayData(array('Name' => $name, 'Description' => $desc))); } $specHTML = $this->customise(array('ClassName' => $this->sanitiseClassName($this->modelClass), 'ModelName' => Convert::raw2att($modelName), 'Fields' => $specFields, 'Relations' => $specRelations))->renderWith($this->getTemplatesWithSuffix('_ImportSpec')); $fields->push(new LiteralField("SpecFor{$modelName}", $specHTML)); $fields->push(new CheckboxField('EmptyBeforeImport', _t('ModelAdmin.EMPTYBEFOREIMPORT', 'Replace data'), false)); $actions = new FieldList(new FormAction('import', _t('ModelAdmin.IMPORT', 'Import from CSV'))); $form = new Form($this, "ImportForm", $fields, $actions); $form->setFormAction(Controller::join_links($this->Link($this->sanitiseClassName($this->modelClass)), 'ImportForm')); $this->extend('updateImportForm', $form); return $form; }
/** * Get HTML for img containing the icon for this file * * @return DBHTMLText */ public function IconTag() { return DBField::create_field('HTMLFragment', '<img src="' . Convert::raw2att($this->getIcon()) . '" />'); }
/** * Adds extra fields to this form * * @param FieldList $fields * @param Controller $controller * @param string $name * @param array $context */ public function updateFormFields(FieldList &$fields, Controller $controller, $name, $context = []) { // Add preview link if (empty($context['Record'])) { return; } $record = $context['Record']; if ($record->hasExtension(Versioned::class)) { $link = $controller->Link('preview'); $fields->unshift(new LiteralField("PreviewLink", sprintf('<a href="%s" rel="external" target="_blank">Preview</a>', Convert::raw2att($link)))); } }
/** * @return string */ public function getTreeTitle() { return sprintf("<span class=\"jstree-foldericon\"></span><span class=\"item\">%s</span>", Convert::raw2att(preg_replace('~\\R~u', ' ', $this->Title))); }
public function testRewriteHashlinks() { SSViewer::config()->update('rewrite_hash_links', true); $_SERVER['HTTP_HOST'] = 'www.mysite.com'; $_SERVER['REQUEST_URI'] = '//file.com?foo"onclick="alert(\'xss\')""'; // Emulate SSViewer::process() // Note that leading double slashes have been rewritten to prevent these being mis-interepreted // as protocol-less absolute urls $base = Convert::raw2att('/file.com?foo"onclick="alert(\'xss\')""'); $tmplFile = TEMP_FOLDER . '/SSViewerTest_testRewriteHashlinks_' . sha1(rand()) . '.ss'; // Note: SSViewer_FromString doesn't rewrite hash links. file_put_contents($tmplFile, '<!DOCTYPE html> <html> <head><% base_tag %></head> <body> <a class="external-inline" href="http://google.com#anchor">ExternalInlineLink</a> $ExternalInsertedLink <a class="inline" href="#anchor">InlineLink</a> $InsertedLink <svg><use xlink:href="#sprite"></use></svg> <body> </html>'); $tmpl = new SSViewer($tmplFile); $obj = new ViewableData(); $obj->InsertedLink = DBField::create_field('HTMLFragment', '<a class="inserted" href="#anchor">InsertedLink</a>'); $obj->ExternalInsertedLink = DBField::create_field('HTMLFragment', '<a class="external-inserted" href="http://google.com#anchor">ExternalInsertedLink</a>'); $result = $tmpl->process($obj); $this->assertContains('<a class="inserted" href="' . $base . '#anchor">InsertedLink</a>', $result); $this->assertContains('<a class="external-inserted" href="http://google.com#anchor">ExternalInsertedLink</a>', $result); $this->assertContains('<a class="inline" href="' . $base . '#anchor">InlineLink</a>', $result); $this->assertContains('<a class="external-inline" href="http://google.com#anchor">ExternalInlineLink</a>', $result); $this->assertContains('<svg><use xlink:href="#sprite"></use></svg>', $result, 'SSTemplateParser should only rewrite anchor hrefs'); unlink($tmplFile); }
/** * Redirect the user to the change password form. * * @return HTTPResponse */ protected function redirectToChangePassword() { // Since this form is loaded via an iframe, this redirect must be performed via javascript $changePasswordForm = new ChangePasswordForm($this->controller, 'SilverStripe\\Security\\ChangePasswordForm'); $changePasswordForm->sessionMessage(_t('Member.PASSWORDEXPIRED', 'Your password has expired. Please choose a new one.'), 'good'); // Get redirect url $changePasswordURL = $this->getExternalLink('changepassword'); if ($backURL = $this->controller->getRequest()->requestVar('BackURL')) { Session::set('BackURL', $backURL); $changePasswordURL = Controller::join_links($changePasswordURL, '?BackURL=' . urlencode($backURL)); } $changePasswordURLATT = Convert::raw2att($changePasswordURL); $changePasswordURLJS = Convert::raw2js($changePasswordURL); $message = _t('CMSMemberLoginForm.PASSWORDEXPIRED', '<p>Your password has expired. <a target="_top" href="{link}">Please choose a new one.</a></p>', 'Message displayed to user if their session cannot be restored', array('link' => $changePasswordURLATT)); // Redirect to change password page $this->controller->getResponse()->setStatusCode(200); $this->controller->getResponse()->setBody(<<<PHP <!DOCTYPE html> <html><body> {$message} <script type="application/javascript"> setTimeout(function(){top.location.href = "{$changePasswordURLJS}";}, 0); </script> </body></html> PHP ); return $this->controller->getResponse(); }