Skip to content

Commit

Permalink
ENHANCEMENT Replaced client side URL filtering in CMS with ajax callb…
Browse files Browse the repository at this point in the history
…acks 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.
  • Loading branch information
chillu committed Nov 29, 2011
1 parent 0b5d205 commit e649082
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 56 deletions.
42 changes: 42 additions & 0 deletions code/forms/SiteTreeURLSegmentField.php
Original file line number Original file line Diff line number Diff line change
@@ -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');
}
}
6 changes: 3 additions & 3 deletions code/model/SiteTree.php
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -1385,7 +1385,7 @@ protected function onBeforeWrite() {
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";
Expand Down Expand Up @@ -1578,7 +1578,7 @@ public function validURLSegment() {
* @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)
Expand Down Expand Up @@ -1828,7 +1828,7 @@ function getCMSFields() {
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
), ),
Expand Down
85 changes: 32 additions & 53 deletions javascript/CMSMain.EditForm.js
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) {
// TODO Do we want to enforce lowercasing in URLs?
return val.substr(0, this.getMaxLength()).replace(this.getFilterRegex(), '').toLowerCase();
},

/**
* Function: validate
*
* Returns:
* Boolean
*/ */
validate: function() { suggest: function(val, callback) {
return ( $.get(
this.val().length > this.getMaxLength() this.parents('form:first').attr('action') +
|| this.val().match(this.getFilterRegex()) '/field/URLSegment/suggest/?value=' + encodeURIComponent(this.val()),
function(data) {
callback.apply(this, arguments);
}
); );
} }
}); });
Expand Down Expand Up @@ -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');
} }
} }
}); });
Expand Down

0 comments on commit e649082

Please sign in to comment.