public function testEnablePluginsByArrayWithPaths()
 {
     Config::inst()->update('SilverStripe\\Control\\Director', 'alternate_base_url', 'http://mysite.com/subdir');
     $c = new TinyMCEConfig();
     $c->setTheme('modern');
     $c->setOption('language', 'es');
     $c->disablePlugins('table', 'emoticons', 'paste', 'code', 'link', 'importcss');
     $c->enablePlugins(array('plugin1' => 'mypath/plugin1.js', 'plugin2' => '/anotherbase/mypath/plugin2.js', 'plugin3' => 'https://www.google.com/plugin.js', 'plugin4' => null, 'plugin5' => null));
     $attributes = $c->getAttributes();
     $config = Convert::json2array($attributes['data-config']);
     $plugins = $config['external_plugins'];
     $this->assertNotEmpty($plugins);
     // Plugin specified via relative url
     $this->assertContains('plugin1', array_keys($plugins));
     $this->assertEquals('http://mysite.com/subdir/mypath/plugin1.js', $plugins['plugin1']);
     // Plugin specified via root-relative url
     $this->assertContains('plugin2', array_keys($plugins));
     $this->assertEquals('http://mysite.com/anotherbase/mypath/plugin2.js', $plugins['plugin2']);
     // Plugin specified with absolute url
     $this->assertContains('plugin3', array_keys($plugins));
     $this->assertEquals('https://www.google.com/plugin.js', $plugins['plugin3']);
     // Plugin specified with standard location
     $this->assertContains('plugin4', array_keys($plugins));
     $this->assertEquals('http://mysite.com/subdir/' . ADMIN_THIRDPARTY_DIR . '/tinymce/plugins/plugin4/plugin.min.js', $plugins['plugin4']);
     // Check that internal plugins are extractable separately
     $this->assertEquals(['plugin4', 'plugin5'], $c->getInternalPlugins());
     // Test plugins included via gzip compresser
     HTMLEditorField::config()->update('use_gzip', true);
     $this->assertEquals(ADMIN_THIRDPARTY_DIR . '/tinymce/tiny_mce_gzip.php?js=1&plugins=plugin4,plugin5&themes=modern&languages=es&diskcache=true&src=true', $c->getScriptURL());
     // If gzip is disabled only the core plugin is loaded
     HTMLEditorField::config()->remove('use_gzip');
     $this->assertEquals(ADMIN_THIRDPARTY_DIR . '/tinymce/tinymce.min.js', $c->getScriptURL());
 }
 /**
  * Returns a readonly span containing the correct value.
  *
  * @param array $properties
  *
  * @return string
  */
 public function Field($properties = array())
 {
     $source = ArrayLib::flatten($this->getSource());
     $values = $this->getValueArray();
     // Get selected values
     $mapped = array();
     foreach ($values as $value) {
         if (isset($source[$value])) {
             $mapped[] = $source[$value];
         }
     }
     // Don't check if string arguments are matching against the source,
     // as they might be generated HTML diff views instead of the actual values
     if ($this->value && is_string($this->value) && empty($mapped)) {
         $mapped = array(trim($this->value));
         $values = array();
     }
     if ($mapped) {
         $attrValue = implode(', ', array_values($mapped));
         $attrValue = Convert::raw2xml($attrValue);
         $inputValue = implode(', ', array_values($values));
     } else {
         $attrValue = '<i>(' . _t('FormField.NONE', 'none') . ')</i>';
         $inputValue = '';
     }
     $properties = array_merge($properties, array('AttrValue' => DBField::create_field('HTMLFragment', $attrValue), 'InputValue' => $inputValue));
     return parent::Field($properties);
 }
 public function preRequest(HTTPRequest $request, Session $session, DataModel $model)
 {
     // Bootstrap session so that Session::get() accesses the right instance
     $dummyController = new Controller();
     $dummyController->setSession($session);
     $dummyController->setRequest($request);
     $dummyController->pushCurrent();
     // Block non-authenticated users from setting the stage mode
     if (!Versioned::can_choose_site_stage($request)) {
         $permissionMessage = sprintf(_t("ContentController.DRAFT_SITE_ACCESS_RESTRICTION", 'You must log in with your CMS password in order to view the draft or archived content. ' . '<a href="%s">Click here to go back to the published site.</a>'), Convert::raw2xml(Controller::join_links(Director::baseURL(), $request->getURL(), "?stage=Live")));
         // Force output since RequestFilter::preRequest doesn't support response overriding
         $response = Security::permissionFailure($dummyController, $permissionMessage);
         $session->inst_save();
         $dummyController->popCurrent();
         // Prevent output in testing
         if (class_exists('SilverStripe\\Dev\\SapphireTest', false) && SapphireTest::is_running_test()) {
             throw new HTTPResponse_Exception($response);
         }
         $response->output();
         die;
     }
     Versioned::choose_site_stage();
     $dummyController->popCurrent();
     return true;
 }
 /**
  * Generate the field ID value
  *
  * @param FormField $field
  * @return string
  */
 public function generateFieldID($field)
 {
     if ($form = $field->getForm()) {
         return sprintf("%s_%s", $this->generateFormID($form), Convert::raw2htmlid($field->getName()));
     }
     return Convert::raw2htmlid($field->getName());
 }
 public function Field($properties = array())
 {
     $config = array('timeformat' => $this->getConfig('timeformat'));
     $config = array_filter($config);
     $this->addExtraClass(Convert::raw2json($config));
     return parent::Field($properties);
 }
 /**
  * @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 &nbsp; being encoded as literal utf-8 characters
     // Possible alternative solution: http://stackoverflow.com/questions/2142120/php-encoding-with-domdocument
     $from = mb_convert_encoding('&nbsp;', 'utf-8', 'html-entities');
     $res = str_replace($from, '&nbsp;', $res);
     return $res;
 }
 public function processRecord($record, $columnMap, &$results, $preview = false)
 {
     $objID = parent::processRecord($record, $columnMap, $results, $preview);
     $_cache_groupByCode = array();
     // Add to predefined groups
     /** @var Member $member */
     $member = DataObject::get_by_id($this->objectClass, $objID);
     foreach ($this->groups as $group) {
         // TODO This isnt the most memory effective way to add members to a group
         $member->Groups()->add($group);
     }
     // Add to groups defined in CSV
     if (isset($record['Groups']) && $record['Groups']) {
         $groupCodes = explode(',', $record['Groups']);
         foreach ($groupCodes as $groupCode) {
             $groupCode = Convert::raw2url($groupCode);
             if (!isset($_cache_groupByCode[$groupCode])) {
                 $group = Group::get()->filter('Code', $groupCode)->first();
                 if (!$group) {
                     $group = new Group();
                     $group->Code = $groupCode;
                     $group->Title = $groupCode;
                     $group->write();
                 }
                 $member->Groups()->add($group);
                 $_cache_groupByCode[$groupCode] = $group;
             }
         }
     }
     $member->destroy();
     unset($member);
     return $objID;
 }
 /**
  * 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\" />";
 }
 public function Field($properties = array())
 {
     if ($this->valueObj) {
         $val = Convert::raw2xml($this->valueObj->toString($this->getConfig('timeformat')));
     } else {
         // TODO Localization
         $val = '<i>(not set)</i>';
     }
     return "<span class=\"readonly\" id=\"" . $this->ID() . "\">{$val}</span>";
 }
 /**
  * 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 . "\" />";
 }
 /**
  * Create a new PolymorphicHasManyList relation list.
  *
  * @param string $dataClass The class of the DataObjects that this will list.
  * @param string $foreignField The name of the composite foreign relation field. Used
  * to generate the ID and Class foreign keys.
  * @param string $foreignClass Name of the class filter this relation is filtered against
  */
 function __construct($dataClass, $foreignField, $foreignClass)
 {
     // Set both id foreign key (as in HasManyList) and the class foreign key
     parent::__construct($dataClass, "{$foreignField}ID");
     $this->classForeignKey = "{$foreignField}Class";
     // Ensure underlying DataQuery globally references the class filter
     $this->dataQuery->setQueryParam('Foreign.Class', $foreignClass);
     // For queries with multiple foreign IDs (such as that generated by
     // DataList::relation) the filter must be generalised to filter by subclasses
     $classNames = Convert::raw2sql(ClassInfo::subclassesFor($foreignClass));
     $this->dataQuery->where(sprintf("\"{$this->classForeignKey}\" IN ('%s')", implode("', '", $classNames)));
 }
 /**
  * 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));
 }
 /**
  * Create the "custom" selection field option
  *
  * @param bool $isChecked True if this is checked
  * @param bool $odd Is odd striped
  * @return ArrayData
  */
 protected function getCustomFieldOption($isChecked, $odd)
 {
     // Add "custom" input field
     $option = $this->getFieldOption(self::CUSTOM_OPTION, _t('MemberDatetimeOptionsetField.Custom', 'Custom'), $odd);
     $option->setField('isChecked', $isChecked);
     $option->setField('CustomName', $this->getName() . '[Custom]');
     $option->setField('CustomValue', $this->Value());
     if ($this->Value()) {
         $preview = Convert::raw2xml($this->previewFormat($this->Value()));
         $option->setField('CustomPreview', $preview);
         $option->setField('CustomPreviewLabel', _t('MemberDatetimeOptionsetField.Preview', 'Preview'));
     }
     return $option;
 }
 public function Field($properties = array())
 {
     if ($this->valueObj) {
         if ($this->valueObj->isToday()) {
             $val = Convert::raw2xml($this->valueObj->toString($this->getConfig('dateformat')) . ' (' . _t('DateField.TODAY', 'today') . ')');
         } else {
             $df = new DBDate($this->name);
             $df->setValue($this->dataValue());
             $val = Convert::raw2xml($this->valueObj->toString($this->getConfig('dateformat')) . ', ' . $df->Ago());
         }
     } else {
         $val = '<i>(' . _t('DateField.NOTSET', 'not set') . ')</i>';
     }
     return "<span class=\"readonly\" id=\"" . $this->ID() . "\">{$val}</span>";
 }
 public function run(SS_List $records)
 {
     $status = array('modified' => array(), 'deleted' => array());
     foreach ($records as $record) {
         $id = $record->ID;
         // Perform the action
         if ($record->canDelete()) {
             $record->delete();
         }
         $status['deleted'][$id] = array();
         $record->destroy();
         unset($record);
     }
     return Convert::raw2json($status);
 }
 /**
  * Gets the list of options to render in this formfield
  *
  * @return ArrayList
  */
 public function getOptions()
 {
     $selectedValues = $this->getValueArray();
     $defaultItems = $this->getDefaultItems();
     // Generate list of options to display
     $odd = false;
     $formID = $this->ID();
     $options = new ArrayList();
     foreach ($this->getSource() as $itemValue => $title) {
         $itemID = Convert::raw2htmlid("{$formID}_{$itemValue}");
         $odd = !$odd;
         $extraClass = $odd ? 'odd' : 'even';
         $extraClass .= ' val' . preg_replace('/[^a-zA-Z0-9\\-\\_]/', '_', $itemValue);
         $itemChecked = in_array($itemValue, $selectedValues) || in_array($itemValue, $defaultItems);
         $itemDisabled = $this->isDisabled() || in_array($itemValue, $defaultItems);
         $options->push(new ArrayData(array('ID' => $itemID, 'Class' => $extraClass, 'Name' => "{$this->name}[{$itemValue}]", 'Value' => $itemValue, 'Title' => $title, 'isChecked' => $itemChecked, 'isDisabled' => $itemDisabled)));
     }
     $this->extend('updateGetOptions', $options);
     return $options;
 }
 /**
  * Helper method for responding to a back action request
  * @param string $successMessage The message to return as a notification.
  * Can have up to two %d's in it. The first will be replaced by the number of successful
  * changes, the second by the number of failures
  * @param array $status A status array like batchactions builds. Should be
  * key => value pairs, the key can be any string: "error" indicates errors, anything
  * else indicates a type of success. The value is an array. We don't care what's in it,
  * we just use count($value) to find the number of items that succeeded or failed
  * @return string
  */
 public function response($successMessage, $status)
 {
     $count = 0;
     $errors = 0;
     foreach ($status as $k => $v) {
         switch ($k) {
             case 'error':
                 $errors += count($v);
                 break;
             case 'success':
                 $count += count($v);
                 break;
         }
     }
     $response = Controller::curr()->getResponse();
     if ($response) {
         $response->setStatusCode(200, sprintf($successMessage, $count, $errors));
     }
     return Convert::raw2json($status);
 }
    /**
     * 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;
    }
 /**
  * @uses FormField::name_to_label()
  *
  * @param string $name Identifier of the tab, without characters like dots or spaces
  * @param string|FormField $titleOrField Natural language title of the tabset, or first tab.
  * If its left out, the class uses {@link FormField::name_to_label()} to produce a title
  * from the {@link $name} parameter.
  * @param FormField ...$fields All following parameters are inserted as children to this tab
  */
 public function __construct($name, $titleOrField = null, $fields = null)
 {
     if (!is_string($name)) {
         throw new InvalidArgumentException('Invalid string parameter for $name');
     }
     // Get following arguments
     $fields = func_get_args();
     array_shift($fields);
     // Detect title from second argument, if it is a string
     if ($titleOrField && is_string($titleOrField)) {
         $title = $titleOrField;
         array_shift($fields);
     } else {
         $title = static::name_to_label($name);
     }
     // Remaining arguments are child fields
     parent::__construct($fields);
     // Assign name and title (not assigned by parent constructor)
     $this->setName($name);
     $this->setTitle($title);
     $this->setID(Convert::raw2htmlid($name));
 }
 function testSearch()
 {
     $team1 = $this->objFromFixture('GridFieldTest_Team', 'team1');
     $team2 = $this->objFromFixture('GridFieldTest_Team', 'team2');
     $response = $this->get('GridFieldAddExistingAutocompleterTest_Controller');
     $this->assertFalse($response->isError());
     $parser = new CSSContentParser($response->getBody());
     $btns = $parser->getBySelector('.grid-field .action_gridfield_relationfind');
     $response = $this->post('GridFieldAddExistingAutocompleterTest_Controller/Form/field/testfield/search' . '/?gridfield_relationsearch=Team 2', array((string) $btns[0]['name'] => 1));
     $this->assertFalse($response->isError());
     $result = Convert::json2array($response->getBody());
     $this->assertEquals(1, count($result));
     $this->assertEquals(array(array('label' => 'Team 2', 'value' => 'Team 2', 'id' => $team2->ID)), $result);
     $response = $this->post('GridFieldAddExistingAutocompleterTest_Controller/Form/field/testfield/' . 'search/?gridfield_relationsearch=Heather', array((string) $btns[0]['name'] => 1));
     $this->assertFalse($response->isError());
     $result = Convert::json2array($response->getBody());
     $this->assertEquals(1, count($result), "The relational filter did not work");
     $response = $this->post('GridFieldAddExistingAutocompleterTest_Controller/Form/field/testfield/search' . '/?gridfield_relationsearch=Unknown', array((string) $btns[0]['name'] => 1));
     $this->assertFalse($response->isError());
     $result = Convert::json2array($response->getBody());
     $this->assertEmpty($result, 'The output is either an empty array or boolean FALSE');
 }
 /**
  * Returns a version of a title suitable for insertion into an HTML attribute.
  *
  * @return string
  */
 public function attrValue()
 {
     return Convert::raw2att($this->value);
 }
 public function testCacheFilename()
 {
     $image = $this->objFromFixture('SilverStripe\\Assets\\Image', 'imageWithoutTitle');
     $imageFirst = $image->Pad(200, 200, 'CCCCCC');
     $imageFilename = $imageFirst->getURL();
     // Encoding of the arguments is duplicated from cacheFilename
     $neededPart = 'Pad' . Convert::base64url_encode(array(200, 200, 'CCCCCC'));
     $this->assertContains($neededPart, $imageFilename, 'Filename for cached image is correctly generated');
 }
 /**
  * @param string $varname
  * @param string $varvalue
  * @param null|string $currentURL
  *
  * @return string
  */
 public static function RAW_setGetVar($varname, $varvalue, $currentURL = null)
 {
     $url = self::setGetVar($varname, $varvalue, $currentURL);
     return Convert::xml2raw($url);
 }
 /**
  * Test that UploadField:overwriteWarning cannot overwrite Upload:replaceFile
  */
 public function testConfigOverwriteWarningCannotRelaceFiles()
 {
     Upload::config()->replaceFile = false;
     UploadField::config()->defaultConfig = array_merge(UploadField::config()->defaultConfig, array('overwriteWarning' => true));
     $tmpFileName = 'testUploadBasic.txt';
     $response = $this->mockFileUpload('NoRelationField', $tmpFileName);
     $this->assertFalse($response->isError());
     $responseData = Convert::json2array($response->getBody());
     $uploadedFile = DataObject::get_by_id('SilverStripe\\Assets\\File', (int) $responseData[0]['id']);
     $this->assertTrue(is_object($uploadedFile), 'The file object is created');
     $this->assertFileExists(AssetStoreTest_SpyStore::getLocalPath($uploadedFile));
     $tmpFileName = 'testUploadBasic.txt';
     $response = $this->mockFileUpload('NoRelationField', $tmpFileName);
     $this->assertFalse($response->isError());
     $responseData = Convert::json2array($response->getBody());
     $uploadedFile2 = DataObject::get_by_id('SilverStripe\\Assets\\File', (int) $responseData[0]['id']);
     $this->assertTrue(is_object($uploadedFile2), 'The file object is created');
     $this->assertFileExists(AssetStoreTest_SpyStore::getLocalPath($uploadedFile2));
     $this->assertTrue($uploadedFile->Filename !== $uploadedFile2->Filename, 'Filename is not the same');
     $this->assertTrue($uploadedFile->ID !== $uploadedFile2->ID, 'File database record is not the same');
 }
 /**
  * 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);
 }
 public function testSummaryEndings()
 {
     $cases = array('...', ' -> more', '');
     $orig = '<p>Cut it off, cut it off</p>';
     $match = 'Cut it off, cut';
     foreach ($cases as $add) {
         $textObj = DBField::create_field('HTMLFragment', $orig);
         $result = $textObj->obj('Summary', [4, $add])->forTemplate();
         $this->assertEquals($match . Convert::raw2xml($add), $result);
     }
 }
 /**
  * Processing that occurs before a form is executed.
  *
  * This includes form validation, if it fails, we redirect back
  * to the form with appropriate error messages.
  * Always return true if the current form action is exempt from validation
  *
  * Triggered through {@link httpSubmission()}.
  *
  * Note that CSRF protection takes place in {@link httpSubmission()},
  * if it fails the form data will never reach this method.
  *
  * @return boolean
  */
 public function validate()
 {
     $action = $this->buttonClicked();
     if ($action && $this->actionIsValidationExempt($action)) {
         return true;
     }
     if ($this->validator) {
         $errors = $this->validator->validate();
         if ($errors) {
             // Load errors into session and post back
             $data = $this->getData();
             // Encode validation messages as XML before saving into session state
             // As per Form::addErrorMessage()
             $errors = array_map(function ($error) {
                 // Encode message as XML by default
                 if ($error['message'] instanceof DBField) {
                     $error['message'] = $error['message']->forTemplate();
                 } else {
                     $error['message'] = Convert::raw2xml($error['message']);
                 }
                 return $error;
             }, $errors);
             Session::set("FormInfo.{$this->FormName()}.errors", $errors);
             Session::set("FormInfo.{$this->FormName()}.data", $data);
             return false;
         }
     }
     return true;
 }
 /**
  * Simple conversion of HTML to plaintext.
  *
  * @param string $data Input data
  * @param bool $preserveLinks
  * @param int $wordWrap
  * @param array $config
  * @return string
  */
 public static function html2raw($data, $preserveLinks = false, $wordWrap = 0, $config = null)
 {
     $defaultConfig = array('PreserveLinks' => false, 'ReplaceBoldAsterisk' => true, 'CompressWhitespace' => true, 'ReplaceImagesWithAlt' => true);
     if (isset($config)) {
         $config = array_merge($defaultConfig, $config);
     } else {
         $config = $defaultConfig;
     }
     $data = preg_replace("/<style([^A-Za-z0-9>][^>]*)?>.*?<\\/style[^>]*>/is", "", $data);
     $data = preg_replace("/<script([^A-Za-z0-9>][^>]*)?>.*?<\\/script[^>]*>/is", "", $data);
     if ($config['ReplaceBoldAsterisk']) {
         $data = preg_replace('%<(strong|b)( [^>]*)?>|</(strong|b)>%i', '*', $data);
     }
     // Expand hyperlinks
     if (!$preserveLinks && !$config['PreserveLinks']) {
         $data = preg_replace_callback('/<a[^>]*href\\s*=\\s*"([^"]*)">(.*?)<\\/a>/i', function ($matches) {
             return Convert::html2raw($matches[2]) . "[{$matches['1']}]";
         }, $data);
         $data = preg_replace_callback('/<a[^>]*href\\s*=\\s*([^ ]*)>(.*?)<\\/a>/i', function ($matches) {
             return Convert::html2raw($matches[2]) . "[{$matches['1']}]";
         }, $data);
     }
     // Replace images with their alt tags
     if ($config['ReplaceImagesWithAlt']) {
         $data = preg_replace('/<img[^>]*alt *= *"([^"]*)"[^>]*>/i', ' \\1 ', $data);
         $data = preg_replace('/<img[^>]*alt *= *([^ ]*)[^>]*>/i', ' \\1 ', $data);
     }
     // Compress whitespace
     if ($config['CompressWhitespace']) {
         $data = preg_replace("/\\s+/", " ", $data);
     }
     // Parse newline tags
     $data = preg_replace("/\\s*<[Hh][1-6]([^A-Za-z0-9>][^>]*)?> */", "\n\n", $data);
     $data = preg_replace("/\\s*<[Pp]([^A-Za-z0-9>][^>]*)?> */", "\n\n", $data);
     $data = preg_replace("/\\s*<[Dd][Ii][Vv]([^A-Za-z0-9>][^>]*)?> */", "\n\n", $data);
     $data = preg_replace("/\n\n\n+/", "\n\n", $data);
     $data = preg_replace("/<[Bb][Rr]([^A-Za-z0-9>][^>]*)?> */", "\n", $data);
     $data = preg_replace("/<[Tt][Rr]([^A-Za-z0-9>][^>]*)?> */", "\n", $data);
     $data = preg_replace("/<\\/[Tt][Dd]([^A-Za-z0-9>][^>]*)?> */", "    ", $data);
     $data = preg_replace('/<\\/p>/i', "\n\n", $data);
     // Replace HTML entities
     $data = html_entity_decode($data, ENT_QUOTES, 'UTF-8');
     // Remove all tags (but optionally keep links)
     // strip_tags seemed to be restricting the length of the output
     // arbitrarily. This essentially does the same thing.
     if (!$preserveLinks && !$config['PreserveLinks']) {
         $data = preg_replace('/<\\/?[^>]*>/', '', $data);
     } else {
         $data = strip_tags($data, '<a>');
     }
     // Wrap
     if ($wordWrap) {
         $data = wordwrap(trim($data), $wordWrap);
     }
     return trim($data);
 }
 /**
  * @param string $from
  * @param string $to
  * @param bool $escape
  * @return string
  */
 public static function compareHTML($from, $to, $escape = false)
 {
     // First split up the content into words and tags
     $set1 = self::getHTMLChunks($from);
     $set2 = self::getHTMLChunks($to);
     // Diff that
     $diff = new Diff($set1, $set2);
     $tagStack[1] = $tagStack[2] = 0;
     $rechunked[1] = $rechunked[2] = array();
     // Go through everything, converting edited tags (and their content) into single chunks.  Otherwise
     // the generated HTML gets crusty
     foreach ($diff->edits as $edit) {
         $lookForTag = false;
         $stuffFor = [];
         switch ($edit->type) {
             case 'copy':
                 $lookForTag = false;
                 $stuffFor[1] = $edit->orig;
                 $stuffFor[2] = $edit->orig;
                 break;
             case 'change':
                 $lookForTag = true;
                 $stuffFor[1] = $edit->orig;
                 $stuffFor[2] = $edit->final;
                 break;
             case 'add':
                 $lookForTag = true;
                 $stuffFor[1] = null;
                 $stuffFor[2] = $edit->final;
                 break;
             case 'delete':
                 $lookForTag = true;
                 $stuffFor[1] = $edit->orig;
                 $stuffFor[2] = null;
                 break;
         }
         foreach ($stuffFor as $listName => $chunks) {
             if ($chunks) {
                 foreach ($chunks as $item) {
                     // $tagStack > 0 indicates that we should be tag-building
                     if ($tagStack[$listName]) {
                         $rechunked[$listName][sizeof($rechunked[$listName]) - 1] .= ' ' . $item;
                     } else {
                         $rechunked[$listName][] = $item;
                     }
                     if ($lookForTag && !$tagStack[$listName] && isset($item[0]) && $item[0] == "<" && substr($item, 0, 2) != "</") {
                         $tagStack[$listName] = 1;
                     } else {
                         if ($tagStack[$listName]) {
                             if (substr($item, 0, 2) == "</") {
                                 $tagStack[$listName]--;
                             } else {
                                 if (isset($item[0]) && $item[0] == "<") {
                                     $tagStack[$listName]++;
                                 }
                             }
                         }
                     }
                 }
             }
         }
     }
     // Diff the re-chunked data, turning it into maked up HTML
     $diff = new Diff($rechunked[1], $rechunked[2]);
     $content = '';
     foreach ($diff->edits as $edit) {
         $orig = $escape ? Convert::raw2xml($edit->orig) : $edit->orig;
         $final = $escape ? Convert::raw2xml($edit->final) : $edit->final;
         switch ($edit->type) {
             case 'copy':
                 $content .= " " . implode(" ", $orig) . " ";
                 break;
             case 'change':
                 $content .= " <ins>" . implode(" ", $final) . "</ins> ";
                 $content .= " <del>" . implode(" ", $orig) . "</del> ";
                 break;
             case 'add':
                 $content .= " <ins>" . implode(" ", $final) . "</ins> ";
                 break;
             case 'delete':
                 $content .= " <del>" . implode(" ", $orig) . "</del> ";
                 break;
         }
     }
     return self::cleanHTML($content);
 }