ENHANCEMENT Replaced client side URL filtering in CMS with ajax callbacks to new SiteTreeURLSegmentField, in order to align with extended server side logic (and avoid pre-filtering values too heavily before passing them to the server). Removed suggestions from client side user confirmation.

This commit is contained in:
Ingo Schommer 2011-11-14 12:29:58 +01:00
parent 81aeb8d378
commit b4eda409c2
3 changed files with 77 additions and 56 deletions

View File

@ -0,0 +1,42 @@
<?php
/**
* @package cms
* @subpackage forms
*/
/**
* Used to edit the SiteTree->URLSegment property, and suggest input based on the serverside rules
* defined through {@link SiteTree->generateURLSegment()} and {@link URLSegmentFilter}.
*
* Note: The actual conversion for saving the value takes place in the model layer.
*/
class SiteTreeURLSegmentField extends TextField {
static $allowed_actions = array(
'suggest'
);
function suggest($request) {
if(!$request->getVar('value')) return $this->httpError(405);
$page = $this->getPage();
// Same logic as SiteTree->onBeforeWrite
$page->URLSegment = $page->generateURLSegment($request->getVar('value'));
$count = 2;
while(!$page->validURLSegment()) {
$page->URLSegment = preg_replace('/-[0-9]+$/', null, $page->URLSegment) . '-' . $count;
$count++;
}
Controller::curr()->getResponse()->addHeader('Content-Type', 'application/json');
return Convert::raw2json(array('value' => $page->URLSegment));
}
/**
* @return SiteTree
*/
function getPage() {
$idField = $this->getForm()->dataFieldByName('ID');
return ($idField && $idField->Value()) ? DataObject::get_by_id('SiteTree', $idField->Value()) : singleton('SiteTree');
}
}

View File

@ -1385,7 +1385,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
if((!$this->URLSegment || $this->URLSegment == 'new-page') && $this->Title) { if((!$this->URLSegment || $this->URLSegment == 'new-page') && $this->Title) {
$this->URLSegment = $this->generateURLSegment($this->Title); $this->URLSegment = $this->generateURLSegment($this->Title);
} else if($this->isChanged('URLSegment')) { } else if($this->isChanged('URLSegment')) {
$filter = Object::create('URLPathFilter'); $filter = Object::create('URLSegmentFilter');
$this->URLSegment = $filter->filter($this->URLSegment); $this->URLSegment = $filter->filter($this->URLSegment);
// If after sanitising there is no URLSegment, give it a reasonable default // If after sanitising there is no URLSegment, give it a reasonable default
if(!$this->URLSegment) $this->URLSegment = "page-$this->ID"; if(!$this->URLSegment) $this->URLSegment = "page-$this->ID";
@ -1578,7 +1578,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
* @return string Generated url segment * @return string Generated url segment
*/ */
function generateURLSegment($title){ function generateURLSegment($title){
$filter = Object::create('URLPathFilter'); $filter = Object::create('URLSegmentFilter');
$t = $filter->filter($title); $t = $filter->filter($title);
// Fallback to generic page name if path is empty (= no valid, convertable characters) // Fallback to generic page name if path is empty (= no valid, convertable characters)
@ -1828,7 +1828,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
new HtmlEditorField("Content", _t('SiteTree.HTMLEDITORTITLE', "Content", PR_MEDIUM, 'HTML editor title')) new HtmlEditorField("Content", _t('SiteTree.HTMLEDITORTITLE', "Content", PR_MEDIUM, 'HTML editor title'))
), ),
$tabMeta = new Tab('Metadata', $tabMeta = new Tab('Metadata',
new TextField("URLSegment", $this->fieldLabel('URLSegment') . $urlHelper), new SiteTreeURLSegmentField("URLSegment", $this->fieldLabel('URLSegment') . $urlHelper),
new LiteralField('LinkChangeNote', self::nested_urls() && count($this->Children()) ? new LiteralField('LinkChangeNote', self::nested_urls() && count($this->Children()) ?
'<p>' . $this->fieldLabel('LinkChangeNote'). '</p>' : null '<p>' . $this->fieldLabel('LinkChangeNote'). '</p>' : null
), ),

View File

@ -21,23 +21,6 @@
* Input validation on the URLSegment field * Input validation on the URLSegment field
*/ */
$('.cms-edit-form input[name=URLSegment]').entwine({ $('.cms-edit-form input[name=URLSegment]').entwine({
/**
* Property: FilterRegex
* Regex
*/
FilterRegex: /[^A-Za-z0-9-]+/,
/**
* Property: ValidationMessage
* String
*/
ValidationMessage: ss.i18n._t('CMSMAIN.URLSEGMENTVALIDATION'),
/**
* Property: MaxLength
* Int
*/
MaxLength: 50,
/** /**
* Constructor: onmatch * Constructor: onmatch
@ -47,42 +30,41 @@
// intercept change event, do our own writing // intercept change event, do our own writing
this.bind('change', function(e) { this.bind('change', function(e) {
if(!self.validate()) { if(!self.val()) return;
jQuery.noticeAdd(self.getValidationMessage());
} self.attr('disabled', 'disabled').parents('.field:first').addClass('loading');
self.val(self.suggestValue(e.target.value)); var oldVal = self.val();
return false; self.suggest(oldVal, function(data) {
self.removeAttr('disabled').parents('.field:first').removeClass('loading');
var newVal = decodeURIComponent(data.value);
self.val(newVal);
if(oldVal != newVal) {
jQuery.noticeAdd(ss.i18n._t('The URL has been changed'));
}
});
}); });
this._super(); this._super();
}, },
/** /**
* Function: suggestValue * Function: suggest
* *
* Return a value matching the criteria. * Return a value matching the criteria.
* *
* Parameters: * Parameters:
* (String) val * (String) val
* * (Function) callback
* Returns:
* String
*/ */
suggestValue: function(val) { suggest: function(val, callback) {
// TODO Do we want to enforce lowercasing in URLs? $.get(
return val.substr(0, this.getMaxLength()).replace(this.getFilterRegex(), '').toLowerCase(); this.parents('form:first').attr('action') +
}, '/field/URLSegment/suggest/?value=' + encodeURIComponent(this.val()),
function(data) {
/** callback.apply(this, arguments);
* Function: validate }
*
* Returns:
* Boolean
*/
validate: function() {
return (
this.val().length > this.getMaxLength()
|| this.val().match(this.getFilterRegex())
); );
} }
}); });
@ -114,24 +96,21 @@
*/ */
updateURLSegment: function(field) { updateURLSegment: function(field) {
if(!field || !field.length) return; if(!field || !field.length) return;
// TODO The new URL value is determined asynchronously,
// which means we need to come up with an alternative system
// to ask user permission to change it.
// TODO language/logic coupling // TODO language/logic coupling
var isNew = this.val().indexOf("new") == 0; var isNew = this.val().indexOf("new") == 0;
var suggestion = field.entwine('ss').suggestValue(this.val()); var confirmMessage = ss.i18n._t(
var confirmMessage = ss.i18n.sprintf( 'UPDATEURL.CONFIRMSIMPLE',
ss.i18n._t( 'Do you want to update the URL from your new page title?'
'UPDATEURL.CONFIRM',
'Would you like me to change the URL to:\n\n'
+ '%s/\n\nClick Ok to change the URL, '
+ 'click Cancel to leave it as:\n\n%s'
),
suggestion,
field.val()
); );
// don't ask for replacement if record is considered 'new' as defined by its title // don't ask for replacement if record is considered 'new' as defined by its title
if(isNew || (suggestion != field.val() && confirm(confirmMessage))) { if(isNew || confirm(confirmMessage)) {
field.val(suggestion); field.val(this.val()).trigger('change');
} }
} }
}); });