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;
 }
 /**
  * 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);
 }
 /**
  * Tests {@link Convert::raw2xml()}
  */
 public function testRaw2Xml()
 {
     $val1 = '<input type="text">';
     $this->assertEquals('&lt;input type=&quot;text&quot;&gt;', Convert::raw2xml($val1), 'Special characters are escaped');
     $val2 = 'This is some normal text.';
     $this->assertEquals('This is some normal text.', Convert::raw2xml($val2), 'Normal text is not escaped');
     $val3 = "This is test\nNow on a new line.";
     $this->assertEquals("This is test\nNow on a new line.", Convert::raw2xml($val3), 'Newlines are retained. They should not be replaced with <br /> as it is not XML valid');
 }
 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 . "\" />";
 }
 /**
  * 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>";
 }
 /**
  * @param HTTPRequest $request
  */
 public function runTask($request)
 {
     $name = $request->param('TaskName');
     $tasks = $this->getTasks();
     $title = function ($content) {
         printf(Director::is_cli() ? "%s\n\n" : '<h1>%s</h1>', $content);
     };
     $message = function ($content) {
         printf(Director::is_cli() ? "%s\n" : '<p>%s</p>', $content);
     };
     foreach ($tasks as $task) {
         if ($task['segment'] == $name) {
             $inst = Injector::inst()->create($task['class']);
             $title(sprintf('Running Task %s', $inst->getTitle()));
             if (!$inst->isEnabled()) {
                 $message('The task is disabled');
                 return;
             }
             $inst->run($request);
             return;
         }
     }
     $message(sprintf('The build task "%s" could not be found', Convert::raw2xml($name)));
 }
    /**
     * Get a site tree HTML listing which displays the nodes under the given criteria.
     *
     * @param string $className The class of the root object
     * @param string $rootID The ID of the root object.  If this is null then a complete tree will be
     *  shown
     * @param string $childrenMethod The method to call to get the children of the tree. For example,
     *  Children, AllChildrenIncludingDeleted, or AllHistoricalChildren
     * @param string $numChildrenMethod
     * @param callable $filterFunction
     * @param int $nodeCountThreshold
     * @return string Nested unordered list with links to each page
     */
    public function getSiteTreeFor($className, $rootID = null, $childrenMethod = null, $numChildrenMethod = null, $filterFunction = null, $nodeCountThreshold = 30)
    {
        // Filter criteria
        $filter = $this->getSearchFilter();
        // Default childrenMethod and numChildrenMethod
        if (!$childrenMethod) {
            $childrenMethod = $filter && $filter->getChildrenMethod() ? $filter->getChildrenMethod() : 'AllChildrenIncludingDeleted';
        }
        if (!$numChildrenMethod) {
            $numChildrenMethod = 'numChildren';
            if ($filter && $filter->getNumChildrenMethod()) {
                $numChildrenMethod = $filter->getNumChildrenMethod();
            }
        }
        if (!$filterFunction && $filter) {
            $filterFunction = function ($node) use($filter) {
                return $filter->isPageIncluded($node);
            };
        }
        // Get the tree root
        $record = $rootID ? $this->getRecord($rootID) : null;
        $obj = $record ? $record : singleton($className);
        // Get the current page
        // NOTE: This *must* be fetched before markPartialTree() is called, as this
        // causes the Hierarchy::$marked cache to be flushed (@see CMSMain::getRecord)
        // which means that deleted pages stored in the marked tree would be removed
        $currentPage = $this->currentPage();
        // Mark the nodes of the tree to return
        if ($filterFunction) {
            $obj->setMarkingFilterFunction($filterFunction);
        }
        $obj->markPartialTree($nodeCountThreshold, $this, $childrenMethod, $numChildrenMethod);
        // Ensure current page is exposed
        if ($currentPage) {
            $obj->markToExpose($currentPage);
        }
        // NOTE: SiteTree/CMSMain coupling :-(
        if (class_exists('SilverStripe\\CMS\\Model\\SiteTree')) {
            SiteTree::prepopulate_permission_cache('CanEditType', $obj->markedNodeIDs(), 'SilverStripe\\CMS\\Model\\SiteTree::can_edit_multiple');
        }
        // getChildrenAsUL is a flexible and complex way of traversing the tree
        $controller = $this;
        $recordController = $this->stat('tree_class') == 'SilverStripe\\CMS\\Model\\SiteTree' ? CMSPageEditController::singleton() : $this;
        $titleFn = function (&$child, $numChildrenMethod) use(&$controller, &$recordController, $filter) {
            $link = Controller::join_links($recordController->Link("show"), $child->ID);
            $node = LeftAndMain_TreeNode::create($child, $link, $controller->isCurrentPage($child), $numChildrenMethod, $filter);
            return $node->forTemplate();
        };
        // Limit the amount of nodes shown for performance reasons.
        // Skip the check if we're filtering the tree, since its not clear how many children will
        // match the filter criteria until they're queried (and matched up with previously marked nodes).
        $nodeThresholdLeaf = Config::inst()->get(Hierarchy::class, 'node_threshold_leaf');
        if ($nodeThresholdLeaf && !$filterFunction) {
            $nodeCountCallback = function ($parent, $numChildren) use(&$controller, $className, $nodeThresholdLeaf) {
                if ($className !== 'SilverStripe\\CMS\\Model\\SiteTree' || !$parent->ID || $numChildren >= $nodeThresholdLeaf) {
                    return null;
                }
                return sprintf('<ul><li class="readonly"><span class="item">' . '%s (<a href="%s" class="cms-panel-link" data-pjax-target="Content">%s</a>)' . '</span></li></ul>', _t('LeftAndMain.TooManyPages', 'Too many pages'), Controller::join_links($controller->LinkWithSearch($controller->Link()), '
							?view=list&ParentID=' . $parent->ID), _t('LeftAndMain.ShowAsList', 'show as list', 'Show large amount of pages in list instead of tree view'));
            };
        } else {
            $nodeCountCallback = null;
        }
        // If the amount of pages exceeds the node thresholds set, use the callback
        $html = null;
        if ($obj->ParentID && $nodeCountCallback) {
            $html = $nodeCountCallback($obj, $obj->{$numChildrenMethod}());
        }
        // Otherwise return the actual tree (which might still filter leaf thresholds on children)
        if (!$html) {
            $html = $obj->getChildrenAsUL("", $titleFn, CMSPagesController::singleton(), true, $childrenMethod, $numChildrenMethod, $nodeCountThreshold, $nodeCountCallback);
        }
        // Wrap the root if needs be.
        if (!$rootID) {
            $rootLink = $this->Link('show') . '/root';
            // This lets us override the tree title with an extension
            if ($this->hasMethod('getCMSTreeTitle') && ($customTreeTitle = $this->getCMSTreeTitle())) {
                $treeTitle = $customTreeTitle;
            } elseif (class_exists('SilverStripe\\SiteConfig\\SiteConfig')) {
                $siteConfig = SiteConfig::current_site_config();
                $treeTitle = Convert::raw2xml($siteConfig->Title);
            } else {
                $treeTitle = '...';
            }
            $html = "<ul><li id=\"record-0\" data-id=\"0\" class=\"Root nodelete\"><strong>{$treeTitle}</strong>" . $html . "</li></ul>";
        }
        return $html;
    }
 /**
  * XML encode this value
  *
  * @return string
  */
 public function XML()
 {
     return Convert::raw2xml($this->RAW());
 }
 public function getEditForm($id = null, $fields = null)
 {
     // TODO Duplicate record fetching (see parent implementation)
     if (!$id) {
         $id = $this->currentPageID();
     }
     $form = parent::getEditForm($id);
     // TODO Duplicate record fetching (see parent implementation)
     $record = $this->getRecord($id);
     if ($record && !$record->canView()) {
         return Security::permissionFailure($this);
     }
     $memberList = GridField::create('Members', false, Member::get(), $memberListConfig = GridFieldConfig_RecordEditor::create()->addComponent(new GridFieldButtonRow('after'))->addComponent(new GridFieldExportButton('buttons-after-left')))->addExtraClass("members_grid");
     if ($record && method_exists($record, 'getValidator')) {
         $validator = $record->getValidator();
     } else {
         $validator = Member::singleton()->getValidator();
     }
     /** @var GridFieldDetailForm $detailForm */
     $detailForm = $memberListConfig->getComponentByType('SilverStripe\\Forms\\GridField\\GridFieldDetailForm');
     $detailForm->setValidator($validator);
     $groupList = GridField::create('Groups', false, Group::get(), GridFieldConfig_RecordEditor::create());
     /** @var GridFieldDataColumns $columns */
     $columns = $groupList->getConfig()->getComponentByType('SilverStripe\\Forms\\GridField\\GridFieldDataColumns');
     $columns->setDisplayFields(array('Breadcrumbs' => Group::singleton()->fieldLabel('Title')));
     $columns->setFieldFormatting(array('Breadcrumbs' => function ($val, $item) {
         /** @var Group $item */
         return Convert::raw2xml($item->getBreadcrumbs(' > '));
     }));
     $fields = new FieldList($root = new TabSet('Root', $usersTab = new Tab('Users', _t('SecurityAdmin.Users', 'Users'), new LiteralField('MembersCautionText', sprintf('<div class="alert alert-warning" role="alert">%s</div>', _t('SecurityAdmin.MemberListCaution', 'Caution: Removing members from this list will remove them from all groups and the database'))), $memberList), $groupsTab = new Tab('Groups', singleton('SilverStripe\\Security\\Group')->i18n_plural_name(), $groupList)), new HiddenField('ID', false, 0));
     // Add import capabilities. Limit to admin since the import logic can affect assigned permissions
     if (Permission::check('ADMIN')) {
         $fields->addFieldsToTab('Root.Users', array(new HeaderField('ImportUsersHeader', _t('SecurityAdmin.IMPORTUSERS', 'Import users'), 3), new LiteralField('MemberImportFormIframe', sprintf('<iframe src="%s" id="MemberImportFormIframe" width="100%%" height="330px" frameBorder="0">' . '</iframe>', $this->Link('memberimport')))));
         $fields->addFieldsToTab('Root.Groups', array(new HeaderField('ImportGroupsHeader', _t('SecurityAdmin.IMPORTGROUPS', 'Import groups'), 3), new LiteralField('GroupImportFormIframe', sprintf('<iframe src="%s" id="GroupImportFormIframe" width="100%%" height="330px" frameBorder="0">' . '</iframe>', $this->Link('groupimport')))));
     }
     // Tab nav in CMS is rendered through separate template
     $root->setTemplate('SilverStripe\\Forms\\CMSTabSet');
     // Add roles editing interface
     $rolesTab = null;
     if (Permission::check('APPLY_ROLES')) {
         $rolesField = GridField::create('Roles', false, PermissionRole::get(), GridFieldConfig_RecordEditor::create());
         $rolesTab = $fields->findOrMakeTab('Root.Roles', _t('SecurityAdmin.TABROLES', 'Roles'));
         $rolesTab->push($rolesField);
     }
     $actionParam = $this->getRequest()->param('Action');
     if ($actionParam == 'groups') {
         $groupsTab->addExtraClass('ui-state-active');
     } elseif ($actionParam == 'users') {
         $usersTab->addExtraClass('ui-state-active');
     } elseif ($actionParam == 'roles' && $rolesTab) {
         $rolesTab->addExtraClass('ui-state-active');
     }
     $actions = new FieldList();
     $form = Form::create($this, 'EditForm', $fields, $actions)->setHTMLID('Form_EditForm');
     $form->addExtraClass('cms-edit-form fill-height');
     $form->setTemplate($this->getTemplatesWithSuffix('_EditForm'));
     // Tab nav in CMS is rendered through separate template
     if ($form->Fields()->hasTabSet()) {
         $form->Fields()->findOrMakeTab('Root')->setTemplate('SilverStripe\\Forms\\CMSTabSet');
     }
     $form->addExtraClass('ss-tabset cms-tabset ' . $this->BaseCSSClasses());
     $form->setAttribute('data-pjax-fragment', 'CurrentForm');
     $this->extend('updateEditForm', $form);
     return $form;
 }
 /**
  * Rewrite all the URLs in the given content, evaluating the given string as PHP code.
  *
  * Put $URL where you want the URL to appear, however, you can't embed $URL in strings, for example:
  * <ul>
  * <li><code>'"../../" . $URL'</code></li>
  * <li><code>'myRewriter($URL)'</code></li>
  * <li><code>'(substr($URL, 0, 1)=="/") ? "../" . substr($URL, 1) : $URL'</code></li>
  * </ul>
  *
  * As of 3.2 $code should be a callable which takes a single parameter and returns the rewritten,
  * for example:
  * <code>
  * function($url) {
  *		return Director::absoluteURL($url, true);
  * }
  * </code>
  *
  * @param string $content The HTML to search for links to rewrite.
  * @param callable $code Either a string that can evaluate to an expression to rewrite links
  * (depreciated), or a callable that takes a single parameter and returns the rewritten URL.
  *
  * @return string The content with all links rewritten as per the logic specified in $code.
  */
 public static function urlRewriter($content, $code)
 {
     if (!is_callable($code)) {
         throw new InvalidArgumentException('HTTP::urlRewriter expects a callable as the second parameter');
     }
     // Replace attributes
     $attribs = array("src", "background", "a" => "href", "link" => "href", "base" => "href");
     $regExps = [];
     foreach ($attribs as $tag => $attrib) {
         if (!is_numeric($tag)) {
             $tagPrefix = "{$tag} ";
         } else {
             $tagPrefix = "";
         }
         $regExps[] = "/(<{$tagPrefix}[^>]*{$attrib} *= *\")([^\"]*)(\")/i";
         $regExps[] = "/(<{$tagPrefix}[^>]*{$attrib} *= *')([^']*)(')/i";
         $regExps[] = "/(<{$tagPrefix}[^>]*{$attrib} *= *)([^\"' ]*)( )/i";
     }
     // Replace css styles
     // @todo - http://www.css3.info/preview/multiple-backgrounds/
     $styles = array('background-image', 'background', 'list-style-image', 'list-style', 'content');
     foreach ($styles as $style) {
         $regExps[] = "/({$style}:[^;]*url *\\(\")([^\"]+)(\"\\))/i";
         $regExps[] = "/({$style}:[^;]*url *\\(')([^']+)('\\))/i";
         $regExps[] = "/({$style}:[^;]*url *\\()([^\"\\)')]+)(\\))/i";
     }
     // Callback for regexp replacement
     $callback = function ($matches) use($code) {
         // Decode HTML attribute
         $URL = Convert::xml2raw($matches[2]);
         $rewritten = $code($URL);
         return $matches[1] . Convert::raw2xml($rewritten) . $matches[3];
     };
     // Execute each expression
     foreach ($regExps as $regExp) {
         $content = preg_replace_callback($regExp, $callback, $content);
     }
     return $content;
 }
 /**
  * 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;
 }
 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);
     }
 }
 /**
  * Show the "password sent" page, after a user has requested
  * to reset their password.
  *
  * @param HTTPRequest $request The HTTPRequest for this action.
  * @return string Returns the "password sent" page as HTML code.
  */
 public function passwordsent($request)
 {
     $controller = $this->getResponseController(_t('Security.LOSTPASSWORDHEADER', 'Lost Password'));
     // if the controller calls Director::redirect(), this will break early
     if (($response = $controller->getResponse()) && $response->isFinished()) {
         return $response;
     }
     $email = Convert::raw2xml(rawurldecode($request->param('ID')) . '.' . $request->getExtension());
     $customisedController = $controller->customise(array('Title' => _t('Security.PASSWORDSENTHEADER', "Password reset link sent to '{email}'", array('email' => $email)), 'Content' => "<p>" . _t('Security.PASSWORDSENTTEXT', "Thank you! A reset link has been sent to '{email}', provided an account exists for this email" . " address.", array('email' => $email)) . "</p>", 'Email' => $email));
     //Controller::$currentController = $controller;
     return $customisedController->renderWith($this->getTemplatesFor('passwordsent'));
 }
 /**
  * Delete this record from the live site
  *
  * @param array $data
  * @param Form $form
  * @return HTTPResponse
  */
 public function doUnpublish($data, $form)
 {
     /** @var Versioned|DataObject $record */
     $record = $this->getRecord();
     if (!$record->canUnpublish()) {
         return $this->httpError(403);
     }
     // Record name before it's deleted
     $title = $record->Title;
     try {
         $record->doUnpublish();
     } catch (ValidationException $e) {
         return $this->generateValidationResponse($form, $e);
     }
     $message = sprintf(_t('VersionedGridFieldItemRequest.Unpublished', 'Unpublished %s %s'), $record->i18n_singular_name(), Convert::raw2xml($title));
     $this->setFormMessage($form, $message);
     // Redirect back to edit
     return $this->redirectAfterSave(false);
 }
 /**
  * Perform context searching to give some context to searches, optionally
  * highlighting the search term.
  *
  * @param int $characters Number of characters in the summary
  * @param string $keywords Supplied string ("keywords"). Will fall back to 'Search' querystring arg.
  * @param bool $highlight Add a highlight <span> element around search query?
  * @param string $prefix Prefix text
  * @param string $suffix Suffix text
  * @return string HTML string with context
  */
 public function ContextSummary($characters = 500, $keywords = null, $highlight = true, $prefix = "... ", $suffix = "...")
 {
     if (!$keywords) {
         // Use the default "Search" request variable (from SearchForm)
         $keywords = isset($_REQUEST['Search']) ? $_REQUEST['Search'] : '';
     }
     // Get raw text value, but XML encode it (as we'll be merging with HTML tags soon)
     $text = nl2br(Convert::raw2xml($this->Plain()));
     $keywords = Convert::raw2xml($keywords);
     // Find the search string
     $position = (int) stripos($text, $keywords);
     // We want to search string to be in the middle of our block to give it some context
     $position = max(0, $position - $characters / 2);
     if ($position > 0) {
         // We don't want to start mid-word
         $position = max((int) strrpos(substr($text, 0, $position), ' '), (int) strrpos(substr($text, 0, $position), "\n"));
     }
     $summary = substr($text, $position, $characters);
     $stringPieces = explode(' ', $keywords);
     if ($highlight) {
         // Add a span around all key words from the search term as well
         if ($stringPieces) {
             foreach ($stringPieces as $stringPiece) {
                 if (strlen($stringPiece) > 2) {
                     // Maintain case of original string
                     $summary = preg_replace('/' . preg_quote($stringPiece, '/') . '/i', '<span class="highlight">$0</span>', $summary);
                 }
             }
         }
     }
     $summary = trim($summary);
     // Add leading / trailing '...' if trimmed on either end
     if ($position > 0) {
         $summary = $prefix . $summary;
     }
     if (strlen($text) > $characters + $position) {
         $summary = $summary . $suffix;
     }
     return $summary;
 }
 /**
  * Get the whole tree of a part of the tree via an AJAX request.
  *
  * @param HTTPRequest $request
  * @return string
  * @throws Exception
  */
 public function tree(HTTPRequest $request)
 {
     // Array sourceObject is an explicit list of values - construct a "flat tree"
     if (is_array($this->sourceObject)) {
         $output = "<ul class=\"tree\">\n";
         foreach ($this->sourceObject as $k => $v) {
             $output .= '<li id="selector-' . $this->name . '-' . $k . '"><a>' . $v . '</a>';
         }
         $output .= "</ul>";
         return $output;
     }
     // Regular source specification
     $isSubTree = false;
     $this->search = $request->requestVar('search');
     $id = is_numeric($request->latestParam('ID')) ? (int) $request->latestParam('ID') : (int) $request->requestVar('ID');
     /** @var DataObject|Hierarchy $obj */
     $obj = null;
     if ($id && !$request->requestVar('forceFullTree')) {
         $obj = DataObject::get_by_id($this->sourceObject, $id);
         $isSubTree = true;
         if (!$obj) {
             throw new Exception("TreeDropdownField->tree(): the object #{$id} of type {$this->sourceObject} could not be found");
         }
     } else {
         if ($this->baseID) {
             $obj = DataObject::get_by_id($this->sourceObject, $this->baseID);
         }
         if (!$this->baseID || !$obj) {
             $obj = DataObject::singleton($this->sourceObject);
         }
     }
     // pre-process the tree - search needs to operate globally, not locally as marking filter does
     if ($this->search) {
         $this->populateIDs();
     }
     if ($this->filterCallback || $this->search) {
         $obj->setMarkingFilterFunction(array($this, "filterMarking"));
     }
     $obj->markPartialTree($nodeCountThreshold = 30, $context = null, $this->childrenMethod, $this->numChildrenMethod);
     // allow to pass values to be selected within the ajax request
     if (isset($_REQUEST['forceValue']) || $this->value) {
         $forceValue = isset($_REQUEST['forceValue']) ? $_REQUEST['forceValue'] : $this->value;
         $values = preg_split('/,\\s*/', $forceValue);
         if ($values) {
             foreach ($values as $value) {
                 if (!$value || $value == 'unchanged') {
                     continue;
                 }
                 $obj->markToExpose($this->objectForKey($value));
             }
         }
     }
     $self = $this;
     $titleFn = function (&$child) use(&$self) {
         /** @var DataObject|Hierarchy $child */
         $keyField = $self->keyField;
         $labelField = $self->labelField;
         return sprintf('<li id="selector-%s-%s" data-id="%s" class="class-%s %s %s"><a rel="%d">%s</a>', Convert::raw2xml($self->getName()), Convert::raw2xml($child->{$keyField}), Convert::raw2xml($child->{$keyField}), Convert::raw2xml($child->class), Convert::raw2xml($child->markingClasses($self->numChildrenMethod)), $self->nodeIsDisabled($child) ? 'disabled' : '', (int) $child->ID, $child->obj($labelField)->forTemplate());
     };
     // Limit the amount of nodes shown for performance reasons.
     // Skip the check if we're filtering the tree, since its not clear how many children will
     // match the filter criteria until they're queried (and matched up with previously marked nodes).
     $nodeThresholdLeaf = Config::inst()->get('SilverStripe\\ORM\\Hierarchy\\Hierarchy', 'node_threshold_leaf');
     if ($nodeThresholdLeaf && !$this->filterCallback && !$this->search) {
         $className = $this->sourceObject;
         $nodeCountCallback = function ($parent, $numChildren) use($className, $nodeThresholdLeaf) {
             if ($className === 'SilverStripe\\CMS\\Model\\SiteTree' && $parent->ID && $numChildren > $nodeThresholdLeaf) {
                 return sprintf('<ul><li><span class="item">%s</span></li></ul>', _t('LeftAndMain.TooManyPages', 'Too many pages'));
             }
             return null;
         };
     } else {
         $nodeCountCallback = null;
     }
     if ($isSubTree) {
         $html = $obj->getChildrenAsUL("", $titleFn, null, true, $this->childrenMethod, $this->numChildrenMethod, true, null, $nodeCountCallback);
         return substr(trim($html), 4, -5);
     } else {
         $html = $obj->getChildrenAsUL('class="tree"', $titleFn, null, true, $this->childrenMethod, $this->numChildrenMethod, true, null, $nodeCountCallback);
         return $html;
     }
 }
 /**
  * Export core.
  *
  * @param GridField $gridField
  * @return ArrayData
  */
 public function generatePrintData(GridField $gridField)
 {
     $printColumns = $this->getPrintColumnsForGridField($gridField);
     $header = null;
     if ($this->printHasHeader) {
         $header = new ArrayList();
         foreach ($printColumns as $field => $label) {
             $header->push(new ArrayData(array("CellString" => $label)));
         }
     }
     $items = $gridField->getManipulatedList();
     $itemRows = new ArrayList();
     /** @var DataObject $item */
     foreach ($items->limit(null) as $item) {
         $itemRow = new ArrayList();
         foreach ($printColumns as $field => $label) {
             $value = $gridField->getDataFieldValue($item, $field);
             if ($item->escapeTypeForField($field) != 'xml') {
                 $value = Convert::raw2xml($value);
             }
             $itemRow->push(new ArrayData(array("CellString" => $value)));
         }
         $itemRows->push(new ArrayData(array("ItemRow" => $itemRow)));
         if ($item->hasMethod('destroy')) {
             $item->destroy();
         }
     }
     $ret = new ArrayData(array("Title" => $this->getTitle($gridField), "Header" => $header, "ItemRows" => $itemRows, "Datetime" => DBDatetime::now(), "Member" => Member::currentUser()));
     return $ret;
 }
 public function testRepeatedLoginAttemptsLockingPeopleOut()
 {
     $local = i18n::get_locale();
     i18n::set_locale('en_US');
     Member::config()->lock_out_after_incorrect_logins = 5;
     Member::config()->lock_out_delay_mins = 15;
     // Login with a wrong password for more than the defined threshold
     for ($i = 1; $i <= Member::config()->lock_out_after_incorrect_logins + 1; $i++) {
         $this->doTestLoginForm('*****@*****.**', 'incorrectpassword');
         $member = DataObject::get_by_id("SilverStripe\\Security\\Member", $this->idFromFixture('SilverStripe\\Security\\Member', 'test'));
         if ($i < Member::config()->lock_out_after_incorrect_logins) {
             $this->assertNull($member->LockedOutUntil, 'User does not have a lockout time set if under threshold for failed attempts');
             $this->assertContains($this->loginErrorMessage(), Convert::raw2xml(_t('Member.ERRORWRONGCRED')));
         } else {
             // Fuzzy matching for time to avoid side effects from slow running tests
             $this->assertGreaterThan(time() + 14 * 60, strtotime($member->LockedOutUntil), 'User has a lockout time set after too many failed attempts');
         }
         $msg = _t('Member.ERRORLOCKEDOUT2', 'Your account has been temporarily disabled because of too many failed attempts at ' . 'logging in. Please try again in {count} minutes.', null, array('count' => Member::config()->lock_out_delay_mins));
         if ($i > Member::config()->lock_out_after_incorrect_logins) {
             $this->assertContains($msg, $this->loginErrorMessage());
         }
     }
     $this->doTestLoginForm('*****@*****.**', '1nitialPassword');
     $this->assertNull($this->session()->inst_get('loggedInAs'), 'The user can\'t log in after being locked out, even with the right password');
     // (We fake this by re-setting LockedOutUntil)
     $member = DataObject::get_by_id("SilverStripe\\Security\\Member", $this->idFromFixture('SilverStripe\\Security\\Member', 'test'));
     $member->LockedOutUntil = date('Y-m-d H:i:s', time() - 30);
     $member->write();
     $this->doTestLoginForm('*****@*****.**', '1nitialPassword');
     $this->assertEquals($this->session()->inst_get('loggedInAs'), $member->ID, 'After lockout expires, the user can login again');
     // Log the user out
     $this->session()->inst_set('loggedInAs', null);
     // Login again with wrong password, but less attempts than threshold
     for ($i = 1; $i < Member::config()->lock_out_after_incorrect_logins; $i++) {
         $this->doTestLoginForm('*****@*****.**', 'incorrectpassword');
     }
     $this->assertNull($this->session()->inst_get('loggedInAs'));
     $this->assertContains($this->loginErrorMessage(), Convert::raw2xml(_t('Member.ERRORWRONGCRED')), 'The user can retry with a wrong password after the lockout expires');
     $this->doTestLoginForm('*****@*****.**', '1nitialPassword');
     $this->assertEquals($this->session()->inst_get('loggedInAs'), $member->ID, 'The user can login successfully after lockout expires, if staying below the threshold');
     i18n::set_locale($local);
 }
 /**
  * Change the password
  *
  * @param array $data The user submitted data
  * @return HTTPResponse
  */
 public function doChangePassword(array $data)
 {
     if ($member = Member::currentUser()) {
         // The user was logged in, check the current password
         if (empty($data['OldPassword']) || !$member->checkPassword($data['OldPassword'])->valid()) {
             $this->clearMessage();
             $this->sessionMessage(_t('Member.ERRORPASSWORDNOTMATCH', "Your current password does not match, please try again"), "bad");
             // redirect back to the form, instead of using redirectBack() which could send the user elsewhere.
             return $this->controller->redirect($this->controller->Link('changepassword'));
         }
     }
     if (!$member) {
         if (Session::get('AutoLoginHash')) {
             $member = Member::member_from_autologinhash(Session::get('AutoLoginHash'));
         }
         // The user is not logged in and no valid auto login hash is available
         if (!$member) {
             Session::clear('AutoLoginHash');
             return $this->controller->redirect($this->controller->Link('login'));
         }
     }
     // Check the new password
     if (empty($data['NewPassword1'])) {
         $this->clearMessage();
         $this->sessionMessage(_t('Member.EMPTYNEWPASSWORD', "The new password can't be empty, please try again"), "bad");
         // redirect back to the form, instead of using redirectBack() which could send the user elsewhere.
         return $this->controller->redirect($this->controller->Link('changepassword'));
     } else {
         if ($data['NewPassword1'] == $data['NewPassword2']) {
             $isValid = $member->changePassword($data['NewPassword1']);
             if ($isValid->valid()) {
                 // Clear locked out status
                 $member->LockedOutUntil = null;
                 $member->FailedLoginCount = null;
                 $member->write();
                 if ($member->canLogIn()->valid()) {
                     $member->logIn();
                 }
                 // TODO Add confirmation message to login redirect
                 Session::clear('AutoLoginHash');
                 if (!empty($_REQUEST['BackURL']) && Director::is_site_url($_REQUEST['BackURL'])) {
                     $url = Director::absoluteURL($_REQUEST['BackURL']);
                     return $this->controller->redirect($url);
                 } else {
                     // Redirect to default location - the login form saying "You are logged in as..."
                     $redirectURL = HTTP::setGetVar('BackURL', Director::absoluteBaseURL(), $this->controller->Link('login'));
                     return $this->controller->redirect($redirectURL);
                 }
             } else {
                 $this->clearMessage();
                 $this->sessionMessage(_t('Member.INVALIDNEWPASSWORD', "We couldn't accept that password: {password}", array('password' => nl2br("\n" . Convert::raw2xml($isValid->starredList())))), "bad", false);
                 // redirect back to the form, instead of using redirectBack() which could send the user elsewhere.
                 return $this->controller->redirect($this->controller->Link('changepassword'));
             }
         } else {
             $this->clearMessage();
             $this->sessionMessage(_t('Member.ERRORNEWPASSWORD', "You have entered your new password differently, try again"), "bad");
             // redirect back to the form, instead of using redirectBack() which could send the user elsewhere.
             return $this->controller->redirect($this->controller->Link('changepassword'));
         }
     }
 }
 /**
  * Login in the user and figure out where to redirect the browser.
  *
  * The $data has this format
  * array(
  *   'AuthenticationMethod' => 'MemberAuthenticator',
  *   'Email' => '*****@*****.**',
  *   'Password' => '1nitialPassword',
  *   'BackURL' => 'test/link',
  *   [Optional: 'Remember' => 1 ]
  * )
  *
  * @param array $data
  * @return HTTPResponse
  */
 protected function logInUserAndRedirect($data)
 {
     Session::clear('SessionForms.MemberLoginForm.Email');
     Session::clear('SessionForms.MemberLoginForm.Remember');
     if (Member::currentUser()->isPasswordExpired()) {
         if (isset($_REQUEST['BackURL']) && ($backURL = $_REQUEST['BackURL'])) {
             Session::set('BackURL', $backURL);
         }
         /** @skipUpgrade */
         $cp = ChangePasswordForm::create($this->controller, 'ChangePasswordForm');
         $cp->sessionMessage(_t('Member.PASSWORDEXPIRED', 'Your password has expired. Please choose a new one.'), 'good');
         return $this->controller->redirect('Security/changepassword');
     }
     // Absolute redirection URLs may cause spoofing
     if (!empty($_REQUEST['BackURL'])) {
         $url = $_REQUEST['BackURL'];
         if (Director::is_site_url($url)) {
             $url = Director::absoluteURL($url);
         } else {
             // Spoofing attack, redirect to homepage instead of spoofing url
             $url = Director::absoluteBaseURL();
         }
         return $this->controller->redirect($url);
     }
     // If a default login dest has been set, redirect to that.
     if ($url = Security::config()->default_login_dest) {
         $url = Controller::join_links(Director::absoluteBaseURL(), $url);
         return $this->controller->redirect($url);
     }
     // Redirect the user to the page where they came from
     $member = Member::currentUser();
     if ($member) {
         $firstname = Convert::raw2xml($member->FirstName);
         if (!empty($data['Remember'])) {
             Session::set('SessionForms.MemberLoginForm.Remember', '1');
             $member->logIn(true);
         } else {
             $member->logIn();
         }
         Session::set('Security.Message.message', _t('Member.WELCOMEBACK', "Welcome Back, {firstname}", array('firstname' => $firstname)));
         Session::set("Security.Message.type", "good");
     }
     return Controller::curr()->redirectBack();
 }
 /**
  * @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);
 }
 /**
  * Show a debugging message
  *
  * @param string $message
  * @param bool $showHeader
  */
 public static function message($message, $showHeader = true)
 {
     if (!Director::isLive()) {
         $caller = Debug::caller();
         $file = basename($caller['file']);
         if (Director::is_cli()) {
             if ($showHeader) {
                 echo "Debug (line {$caller['line']} of {$file}):\n ";
             }
             echo $message . "\n";
         } else {
             echo "<p class=\"message warning\">\n";
             if ($showHeader) {
                 echo "<b>Debug (line {$caller['line']} of {$file}):</b>\n ";
             }
             echo Convert::raw2xml($message) . "</p>\n";
         }
     }
 }
 /**
  * Return an HTML table containing the full result-set
  *
  * @return string
  */
 public function table()
 {
     $first = true;
     $result = "<table>\n";
     foreach ($this as $record) {
         if ($first) {
             $result .= "<tr>";
             foreach ($record as $k => $v) {
                 $result .= "<th>" . Convert::raw2xml($k) . "</th> ";
             }
             $result .= "</tr> \n";
         }
         $result .= "<tr>";
         foreach ($record as $k => $v) {
             $result .= "<td>" . Convert::raw2xml($v) . "</td> ";
         }
         $result .= "</tr> \n";
         $first = false;
     }
     $result .= "</table>\n";
     if ($first) {
         return "No records found";
     }
     return $result;
 }
 public function scaffoldFormField($title = null, $params = null)
 {
     $field = DateField::create($this->name, $title);
     // Show formatting hints for better usability
     $field->setDescription(sprintf(_t('FormField.Example', 'e.g. %s', 'Example format'), Convert::raw2xml(Zend_Date::now()->toString($field->getConfig('dateformat')))));
     $field->setAttribute('placeholder', $field->getConfig('dateformat'));
     return $field;
 }
 /**
  * We overwrite the field attribute to add our hidden fields, as this
  * formfield can contain multiple values.
  *
  * @param array $properties
  * @return DBHTMLText
  */
 public function Field($properties = array())
 {
     $value = '';
     $titleArray = array();
     $idArray = array();
     $items = $this->getItems();
     $emptyTitle = _t('DropdownField.CHOOSE', '(Choose)', 'start value of a dropdown');
     if ($items && count($items)) {
         foreach ($items as $item) {
             $idArray[] = $item->ID;
             $titleArray[] = $item instanceof ViewableData ? $item->obj($this->labelField)->forTemplate() : Convert::raw2xml($item->{$this->labelField});
         }
         $title = implode(", ", $titleArray);
         $value = implode(",", $idArray);
     } else {
         $title = $emptyTitle;
     }
     $dataUrlTree = '';
     if ($this->form) {
         $dataUrlTree = $this->Link('tree');
         if (!empty($idArray)) {
             $dataUrlTree = Controller::join_links($dataUrlTree, '?forceValue=' . implode(',', $idArray));
         }
     }
     $properties = array_merge($properties, array('Title' => $title, 'EmptyTitle' => $emptyTitle, 'Link' => $dataUrlTree, 'Value' => $value));
     return FormField::Field($properties);
 }
 public function forTemplate()
 {
     return Convert::raw2xml($this->value);
 }