/**
  * Construct a sanitiser from a given HTMLEditorConfig
  *
  * Note that we build data structures from the current state of HTMLEditorConfig - later changes to
  * the passed instance won't cause this instance to update it's whitelist
  *
  * @param HTMLEditorConfig $config
  */
 public function __construct(HTMLEditorConfig $config)
 {
     $valid = $config->getOption('valid_elements');
     if ($valid) {
         $this->addValidElements($valid);
     }
     $valid = $config->getOption('extended_valid_elements');
     if ($valid) {
         $this->addValidElements($valid);
     }
 }
 public function testSanitisation()
 {
     $tests = array(array('p,strong', '<p>Leave Alone</p><div>Strip parent<strong>But keep children</strong> in order</div>', '<p>Leave Alone</p>Strip parent<strong>But keep children</strong> in order', 'Non-whitelisted elements are stripped, but children are kept'), array('p,strong', '<div>A <strong>B <div>Nested elements are still filtered</div> C</strong> D</div>', 'A <strong>B Nested elements are still filtered C</strong> D', 'Non-whitelisted elements are stripped even when children of non-whitelisted elements'), array('p', '<p>Keep</p><script>Strip <strong>including children</strong></script>', '<p>Keep</p>', 'Non-whitelisted script elements are totally stripped, including any children'), array('p[id]', '<p id="keep" bad="strip">Test</p>', '<p id="keep">Test</p>', 'Non-whitelisted attributes are stripped'), array('p[default1=default1|default2=default2|force1:force1|force2:force2]', '<p default1="specific1" force1="specific1">Test</p>', '<p default1="specific1" force1="force1" default2="default2" force2="force2">Test</p>', 'Default attributes are set when not present in input, forced attributes are always set'));
     $config = HTMLEditorConfig::get('htmleditorsanitisertest');
     foreach ($tests as $test) {
         list($validElements, $input, $output, $desc) = $test;
         $config->setOptions(array('valid_elements' => $validElements));
         $sanitiser = new HtmlEditorSanitiser($config);
         $htmlValue = Injector::inst()->create('HTMLValue', $input);
         $sanitiser->sanitise($htmlValue);
         $this->assertEquals($output, $htmlValue->getContent(), $desc);
     }
 }
 /**
  * @uses LeftAndMainExtension->init()
  * @uses LeftAndMainExtension->accessedCMS()
  * @uses CMSMenu
  */
 protected function init()
 {
     parent::init();
     SSViewer::config()->update('rewrite_hash_links', false);
     ContentNegotiator::config()->update('enabled', false);
     // set language
     $member = Member::currentUser();
     if (!empty($member->Locale)) {
         i18n::set_locale($member->Locale);
     }
     if (!empty($member->DateFormat)) {
         i18n::config()->date_format = $member->DateFormat;
     }
     if (!empty($member->TimeFormat)) {
         i18n::config()->time_format = $member->TimeFormat;
     }
     // can't be done in cms/_config.php as locale is not set yet
     CMSMenu::add_link('Help', _t('LeftAndMain.HELP', 'Help', 'Menu title'), $this->config()->help_link, -2, array('target' => '_blank'));
     // Allow customisation of the access check by a extension
     // Also all the canView() check to execute Controller::redirect()
     if (!$this->canView() && !$this->getResponse()->isFinished()) {
         // When access /admin/, we should try a redirect to another part of the admin rather than be locked out
         $menu = $this->MainMenu();
         foreach ($menu as $candidate) {
             if ($candidate->Link && $candidate->Link != $this->Link() && $candidate->MenuItem->controller && singleton($candidate->MenuItem->controller)->canView()) {
                 $this->redirect($candidate->Link);
                 return;
             }
         }
         if (Member::currentUser()) {
             Session::set("BackURL", null);
         }
         // if no alternate menu items have matched, return a permission error
         $messageSet = array('default' => _t('LeftAndMain.PERMDEFAULT', "You must be logged in to access the administration area; please enter your credentials below."), 'alreadyLoggedIn' => _t('LeftAndMain.PERMALREADY', "I'm sorry, but you can't access that part of the CMS.  If you want to log in as someone else, do" . " so below."), 'logInAgain' => _t('LeftAndMain.PERMAGAIN', "You have been logged out of the CMS.  If you would like to log in again, enter a username and" . " password below."));
         Security::permissionFailure($this, $messageSet);
         return;
     }
     // Don't continue if there's already been a redirection request.
     if ($this->redirectedTo()) {
         return;
     }
     // Audit logging hook
     if (empty($_REQUEST['executeForm']) && !$this->getRequest()->isAjax()) {
         $this->extend('accessedCMS');
     }
     // Set the members html editor config
     if (Member::currentUser()) {
         HTMLEditorConfig::set_active_identifier(Member::currentUser()->getHtmlEditorConfigForCMS());
     }
     // Set default values in the config if missing.  These things can't be defined in the config
     // file because insufficient information exists when that is being processed
     $htmlEditorConfig = HTMLEditorConfig::get_active();
     $htmlEditorConfig->setOption('language', i18n::get_tinymce_lang());
     Requirements::customScript("\n\t\t\twindow.ss = window.ss || {};\n\t\t\twindow.ss.config = " . $this->getCombinedClientConfig() . ";\n\t\t");
     Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/client/dist/js/vendor.js');
     Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/client/dist/js/bundle.js');
     Requirements::css(ltrim(FRAMEWORK_ADMIN_DIR . '/client/dist/styles/bundle.css', '/'));
     Requirements::add_i18n_javascript(ltrim(FRAMEWORK_DIR . '/client/lang', '/'), false, true);
     Requirements::add_i18n_javascript(FRAMEWORK_ADMIN_DIR . '/client/lang', false, true);
     if ($this->config()->session_keepalive_ping) {
         Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/client/dist/js/LeftAndMain.Ping.js');
     }
     if (Director::isDev()) {
         // TODO Confuses jQuery.ondemand through document.write()
         Requirements::javascript(ADMIN_THIRDPARTY_DIR . '/jquery-entwine/src/jquery.entwine.inspector.js');
         Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/client/dist/js/leaktools.js');
     }
     // Custom requirements
     $extraJs = $this->stat('extra_requirements_javascript');
     if ($extraJs) {
         foreach ($extraJs as $file => $config) {
             if (is_numeric($file)) {
                 $file = $config;
             }
             Requirements::javascript($file);
         }
     }
     $extraCss = $this->stat('extra_requirements_css');
     if ($extraCss) {
         foreach ($extraCss as $file => $config) {
             if (is_numeric($file)) {
                 $file = $config;
                 $config = array();
             }
             Requirements::css($file, isset($config['media']) ? $config['media'] : null);
         }
     }
     $extraThemedCss = $this->stat('extra_requirements_themedCss');
     if ($extraThemedCss) {
         foreach ($extraThemedCss as $file => $config) {
             if (is_numeric($file)) {
                 $file = $config;
                 $config = array();
             }
             Requirements::themedCSS($file, isset($config['media']) ? $config['media'] : null);
         }
     }
     $dummy = null;
     $this->extend('init', $dummy);
     // Assign default cms theme and replace user-specified themes
     SSViewer::set_themes($this->config()->admin_themes);
     //set the reading mode for the admin to stage
     Versioned::set_stage(Versioned::DRAFT);
 }
 public function testRequireJSIncludesAllConfigs()
 {
     $a = HTMLEditorConfig::get('configA');
     $c = HTMLEditorConfig::get('configB');
     $aAttributes = $a->getAttributes();
     $cAttributes = $c->getAttributes();
     $this->assertNotEmpty($aAttributes['data-config']);
     $this->assertNotEmpty($cAttributes['data-config']);
 }
 /**
  * Get the HtmlEditorConfig for this user to be used in the CMS.
  * This is set by the group. If multiple configurations are set,
  * the one with the highest priority wins.
  *
  * @return string
  */
 public function getHtmlEditorConfigForCMS()
 {
     $currentName = '';
     $currentPriority = 0;
     foreach ($this->Groups() as $group) {
         $configName = $group->HtmlEditorConfig;
         if ($configName) {
             $config = HTMLEditorConfig::get($group->HtmlEditorConfig);
             if ($config && $config->getOption('priority') > $currentPriority) {
                 $currentName = $configName;
                 $currentPriority = $config->getOption('priority');
             }
         }
     }
     // If can't find a suitable editor, just default to cms
     return $currentName ? $currentName : 'cms';
 }
 /**
  * @param DataObject|DataObjectInterface $record
  * @throws Exception
  */
 public function saveInto(DataObjectInterface $record)
 {
     if ($record->hasField($this->name) && $record->escapeTypeForField($this->name) != 'xml') {
         throw new Exception('HTMLEditorField->saveInto(): This field should save into a HTMLText or HTMLVarchar field.');
     }
     // Sanitise if requested
     $htmlValue = Injector::inst()->create('HTMLValue', $this->Value());
     if ($this->config()->sanitise_server_side) {
         $santiser = HTMLEditorSanitiser::create(HTMLEditorConfig::get_active());
         $santiser->sanitise($htmlValue);
     }
     // optionally manipulate the HTML after a TinyMCE edit and prior to a save
     $this->extend('processHTML', $htmlValue);
     // Store into record
     $record->{$this->name} = $htmlValue->getContent();
 }
 /**
  * Caution: Only call on instances, not through a singleton.
  * The "root group" fields will be created through {@link SecurityAdmin->EditForm()}.
  *
  * @return FieldList
  */
 public function getCMSFields()
 {
     $fields = new FieldList(new TabSet("Root", new Tab('Members', _t('SecurityAdmin.MEMBERS', 'Members'), new TextField("Title", $this->fieldLabel('Title')), $parentidfield = DropdownField::create('ParentID', $this->fieldLabel('Parent'), Group::get()->exclude('ID', $this->ID)->map('ID', 'Breadcrumbs'))->setEmptyString(' '), new TextareaField('Description', $this->fieldLabel('Description'))), $permissionsTab = new Tab('Permissions', _t('SecurityAdmin.PERMISSIONS', 'Permissions'), $permissionsField = new PermissionCheckboxSetField('Permissions', false, 'SilverStripe\\Security\\Permission', 'GroupID', $this))));
     $parentidfield->setDescription(_t('Group.GroupReminder', 'If you choose a parent group, this group will take all it\'s roles'));
     // Filter permissions
     // TODO SecurityAdmin coupling, not easy to get to the form fields through GridFieldDetailForm
     $permissionsField->setHiddenPermissions((array) Config::inst()->get('SilverStripe\\Admin\\SecurityAdmin', 'hidden_permissions'));
     if ($this->ID) {
         $group = $this;
         $config = GridFieldConfig_RelationEditor::create();
         $config->addComponent(new GridFieldButtonRow('after'));
         $config->addComponents(new GridFieldExportButton('buttons-after-left'));
         $config->addComponents(new GridFieldPrintButton('buttons-after-left'));
         /** @var GridFieldAddExistingAutocompleter $autocompleter */
         $autocompleter = $config->getComponentByType('SilverStripe\\Forms\\GridField\\GridFieldAddExistingAutocompleter');
         /** @skipUpgrade */
         $autocompleter->setResultsFormat('$Title ($Email)')->setSearchFields(array('FirstName', 'Surname', 'Email'));
         /** @var GridFieldDetailForm $detailForm */
         $detailForm = $config->getComponentByType('SilverStripe\\Forms\\GridField\\GridFieldDetailForm');
         $detailForm->setValidator(Member_Validator::create())->setItemEditFormCallback(function ($form, $component) use($group) {
             /** @var Form $form */
             $record = $form->getRecord();
             $groupsField = $form->Fields()->dataFieldByName('DirectGroups');
             if ($groupsField) {
                 // If new records are created in a group context,
                 // set this group by default.
                 if ($record && !$record->ID) {
                     $groupsField->setValue($group->ID);
                 } elseif ($record && $record->ID) {
                     // TODO Mark disabled once chosen.js supports it
                     // $groupsField->setDisabledItems(array($group->ID));
                     $form->Fields()->replaceField('DirectGroups', $groupsField->performReadonlyTransformation());
                 }
             }
         });
         $memberList = GridField::create('Members', false, $this->DirectMembers(), $config)->addExtraClass('members_grid');
         // @todo Implement permission checking on GridField
         //$memberList->setPermissions(array('edit', 'delete', 'export', 'add', 'inlineadd'));
         $fields->addFieldToTab('Root.Members', $memberList);
     }
     // Only add a dropdown for HTML editor configurations if more than one is available.
     // Otherwise Member->getHtmlEditorConfigForCMS() will default to the 'cms' configuration.
     $editorConfigMap = HTMLEditorConfig::get_available_configs_map();
     if (count($editorConfigMap) > 1) {
         $fields->addFieldToTab('Root.Permissions', new DropdownField('HtmlEditorConfig', 'HTML Editor Configuration', $editorConfigMap), 'Permissions');
     }
     if (!Permission::check('EDIT_PERMISSIONS')) {
         $fields->removeFieldFromTab('Root', 'Permissions');
     }
     // Only show the "Roles" tab if permissions are granted to edit them,
     // and at least one role exists
     if (Permission::check('APPLY_ROLES') && DataObject::get('SilverStripe\\Security\\PermissionRole')) {
         $fields->findOrMakeTab('Root.Roles', _t('SecurityAdmin.ROLES', 'Roles'));
         $fields->addFieldToTab('Root.Roles', new LiteralField("", "<p>" . _t('SecurityAdmin.ROLESDESCRIPTION', "Roles are predefined sets of permissions, and can be assigned to groups.<br />" . "They are inherited from parent groups if required.") . '<br />' . sprintf('<a href="%s" class="add-role">%s</a>', SecurityAdmin::singleton()->Link('show/root#Root_Roles'), _t('Group.RolesAddEditLink', 'Manage roles')) . "</p>"));
         // Add roles (and disable all checkboxes for inherited roles)
         $allRoles = PermissionRole::get();
         if (!Permission::check('ADMIN')) {
             $allRoles = $allRoles->filter("OnlyAdminCanApply", 0);
         }
         if ($this->ID) {
             $groupRoles = $this->Roles();
             $inheritedRoles = new ArrayList();
             $ancestors = $this->getAncestors();
             foreach ($ancestors as $ancestor) {
                 $ancestorRoles = $ancestor->Roles();
                 if ($ancestorRoles) {
                     $inheritedRoles->merge($ancestorRoles);
                 }
             }
             $groupRoleIDs = $groupRoles->column('ID') + $inheritedRoles->column('ID');
             $inheritedRoleIDs = $inheritedRoles->column('ID');
         } else {
             $groupRoleIDs = array();
             $inheritedRoleIDs = array();
         }
         $rolesField = ListboxField::create('Roles', false, $allRoles->map()->toArray())->setDefaultItems($groupRoleIDs)->setAttribute('data-placeholder', _t('Group.AddRole', 'Add a role for this group'))->setDisabledItems($inheritedRoleIDs);
         if (!$allRoles->count()) {
             $rolesField->setAttribute('data-placeholder', _t('Group.NoRoles', 'No roles found'));
         }
         $fields->addFieldToTab('Root.Roles', $rolesField);
     }
     $fields->push($idField = new HiddenField("ID"));
     $this->extend('updateCMSFields', $fields);
     return $fields;
 }