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('<input type="text">', 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); }