diff --git a/core/Requirements.php b/core/Requirements.php index 6313ee7f0..829968d4a 100644 --- a/core/Requirements.php +++ b/core/Requirements.php @@ -259,55 +259,71 @@ class Requirements { * @param string $content HTML content that has already been parsed from the $templateFilePath through {@link SSViewer}. * @return string HTML content thats augumented with the requirements before the closing
tag. */ - static function includeInHTML($templateFilePath, $content) { + static function includeInHTML($templateFile, $content) { if(isset($_GET['debug_profile'])) Profiler::mark("Requirements::includeInHTML"); - if(strpos($content, ' $dummy) { + if(substr($file,0,7) == 'http://' || Director::fileExists($file)) { + if(Director::fileExists($file)) $mtimesuffix = "?m=" . filemtime(Director::baseFolder() . '/' . $file); + else $mtimesuffix = ''; + $jsRequirements .= "\n"; + } + } + + if(self::$customScript) { + foreach(array_diff_key(self::$customScript,self::$blocked) as $script) { + $jsRequirements .= "\n"; + } + } + + foreach(array_diff_key(self::$css,self::$blocked) as $file => $params) { + if(Director::fileExists($file)) { + $media = (isset($params['media']) && !empty($params['media'])) ? " media=\"{$params['media']}\"" : ""; + if(Director::fileExists($file)) $mtimesuffix = "?m=" . filemtime(Director::baseFolder() . '/' .$file); + else $mtimesuffix = ''; + $requirements .= "\n"; + } + } + foreach(array_diff_key(self::$customCSS,self::$blocked) as $css) { + $requirements .= "\n"; + } + + foreach(array_diff_key(self::$customHeadTags,self::$blocked) as $customHeadTag) { + $requirements .= "$customHeadTag\n"; + } + + if(isset($_GET['debug_profile'])) Profiler::unmark("Requirements::includeInHTML"); + + + // We put script tags into the body, for performance. + // If your template already has script tags in the body, then we put our script tags at the top of the body. + // Otherwise, we put it at the bottom. + $p1 = strripos($content, '\n"; - } - } - - if(self::$customScript) { - foreach(array_diff_key(self::$customScript,self::$blocked) as $script) { - $requirements .= "\n"; - } - } - - $jsRequirements = $requirements; - - foreach(array_diff_key(self::$css,self::$blocked) as $file => $params) { - if(Director::fileExists($file)) { - $media = (isset($params['media']) && !empty($params['media'])) ? " media=\"{$params['media']}\"" : ""; - $requirements .= "\n"; - } - } - foreach(array_diff_key(self::$customCSS,self::$blocked) as $css) { - $requirements .= "\n"; - } - - foreach(array_diff_key(self::$customHeadTags,self::$blocked) as $customHeadTag) { - $requirements .= "$customHeadTag\n"; - } - - if(isset($_GET['debug_profile'])) Profiler::unmark("Requirements::includeInHTML"); - - return eregi_replace("(]*>)", $requirements . "\\1", $content); } /** diff --git a/core/model/DataObject.php b/core/model/DataObject.php index af9d4035a..59cecabd9 100644 --- a/core/model/DataObject.php +++ b/core/model/DataObject.php @@ -285,6 +285,32 @@ class DataObject extends ViewableData implements DataObjectInterface { $name = $this->plural_name(); return _t($this->class.'.PLURALNAME', $name); } + + /** + * Standard implementation of a title/label for a specific + * record. Tries to find properties 'Title' or 'Name', + * and falls back to the 'ID'. Useful to provide + * user-friendly identification of a record, e.g. in errormessages + * or UI-selections. + * + * Overload this method to have a more specialized implementation, + * e.g. for an Address record this could be: + *
+ * public function getTitle() {
+ * return "{$this->StreetNumber} {$this->StreetName} {$this->City}";
+ * }
+ *
+ *
+ * @usedby {@link DataObjectSet->toDropDownMap()}
+ *
+ * @return string
+ */
+ public function getTitle() {
+ if($this->hasField('Title')) return $this->getField('Title');
+ if($this->hasField('Name')) return $this->getField('Name');
+
+ return "#{$this->ID}";
+ }
/**
* Returns the associated database record - in this case, the object itself.
@@ -2492,6 +2518,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
public static $casting = array(
"LastEdited" => "Datetime",
"Created" => "Datetime",
+ "Title" => 'Text',
);
/**
diff --git a/core/model/DataObjectDecorator.php b/core/model/DataObjectDecorator.php
index 2c5aa5633..8eaba137e 100755
--- a/core/model/DataObjectDecorator.php
+++ b/core/model/DataObjectDecorator.php
@@ -62,6 +62,14 @@ abstract class DataObjectDecorator extends Extension {
*/
function augmentDatabase() {
}
+
+ /**
+ * Augment a write-record request.
+ *
+ * @param SQLQuery $manipulation Query to augment.
+ */
+ function augmentWrite(&$manipulation) {
+ }
/**
diff --git a/core/model/SQLQuery.php b/core/model/SQLQuery.php
index e849f3e01..1f0d54b28 100755
--- a/core/model/SQLQuery.php
+++ b/core/model/SQLQuery.php
@@ -69,7 +69,7 @@ class SQLQuery extends Object {
* The logical connective used to join WHERE clauses. Defaults to AND.
* @var string
*/
- private $connective = 'AND';
+ public $connective = 'AND';
/**
* Construct a new SQLQuery.
diff --git a/core/model/SiteTree.php b/core/model/SiteTree.php
index 2539292b3..179b5c357 100644
--- a/core/model/SiteTree.php
+++ b/core/model/SiteTree.php
@@ -182,6 +182,7 @@ class SiteTree extends DataObject {
static $searchable_fields = array(
'Title',
+ 'Content',
);
diff --git a/forms/CheckboxSetField.php b/forms/CheckboxSetField.php
index 2bc0559a6..67e93c0c4 100755
--- a/forms/CheckboxSetField.php
+++ b/forms/CheckboxSetField.php
@@ -32,7 +32,7 @@ class CheckboxSetField extends OptionsetField {
if(!$values && $record && $record->hasMethod($this->name)) {
$funcName = $this->name;
$join = $record->$funcName();
- foreach($join as $joinItem) $values[] = $joinItem->ID;
+ if($join) foreach($join as $joinItem) $values[] = $joinItem->ID;
}
}
$source = $this->source;
diff --git a/forms/ComplexTableField.php b/forms/ComplexTableField.php
index 805869f29..a62de175d 100755
--- a/forms/ComplexTableField.php
+++ b/forms/ComplexTableField.php
@@ -252,7 +252,7 @@ JS;
* @return Boolean
*/
function IsAddMode() {
- return ($this->methodName == "add");
+ return ($this->methodName == "add" || $this->request->param('Action') == 'AddForm');
}
function sourceID() {
@@ -457,17 +457,20 @@ JS;
$childData = new $className();
$form->saveInto($childData);
$childData->write();
+
+ $closeLink = sprintf(
+ '(%s)',
+ _t('ComplexTableField.CLOSEPOPUP', 'Close Popup')
+ );
+ $message = sprintf(
+ _t('ComplexTableField.SUCCESSADD', 'Added %s %s %s'),
+ $childData->singular_name(),
+ '' . $childData->Title . '',
+ $closeLink
+ );
+ $form->sessionMessage($message, 'good');
- // if ajax-call in an iframe, update window
- if(Director::is_ajax()) {
- // Newly saved objects need their ID reflected in the reloaded form to avoid double saving
- $childRequestHandler = new ComplexTableField_ItemRequest($this, $childData->ID);
- $form = $childRequestHandler->DetailForm();
- FormResponse::update_dom_id($form->FormName(), $form->formHtmlContent(), true, 'update');
- return FormResponse::respond();
- } else {
- Director::redirectBack();
- }
+ Director::redirectBack();
}
}
@@ -599,18 +602,20 @@ class ComplexTableField_ItemRequest extends RequestHandlingData {
function saveComplexTableField($data, $form, $request) {
$form->saveInto($this->dataObj());
$this->dataObj()->write();
+
+ $closeLink = sprintf(
+ '(%s)',
+ _t('ComplexTableField.CLOSEPOPUP', 'Close Popup')
+ );
+ $message = sprintf(
+ _t('ComplexTableField.SUCCESSEDIT', 'Saved %s %s %s'),
+ $this->dataObj()->singular_name(),
+ '"' . $this->dataObj()->Title . '"',
+ $closeLink
+ );
+ $form->sessionMessage($message, 'good');
- // if ajax-call in an iframe, update window
- if(Director::is_ajax()) {
- // Newly saved objects need their ID reflected in the reloaded form to avoid double saving
- $form = $this->DetailForm();
- //$form->loadDataFrom($this->dataObject);
- FormResponse::update_dom_id($form->FormName(), $form->formHtmlContent(), true, 'update');
- return FormResponse::respond();
-
- } else {
- Director::redirectBack();
- }
+ Director::redirectBack();
}
function PopupCurrentItem() {
diff --git a/forms/TableListField.php b/forms/TableListField.php
index 3c45730f6..c3fa1c2c8 100755
--- a/forms/TableListField.php
+++ b/forms/TableListField.php
@@ -681,7 +681,7 @@ JS
if(!isset($_REQUEST['ctf'][$this->Name()]['start']) || !is_numeric($_REQUEST['ctf'][$this->Name()]['start']) || $_REQUEST['ctf'][$this->Name()]['start'] == 0) {
return null;
}
- $link = $this->Link() . "/ajax_refresh?ctf[{$this->Name()}][start]={$start}";
+ $link = $this->Link() . "/?ctf[{$this->Name()}][start]={$start}";
if($this->extraLinkParams) $link .= "&" . http_build_query($this->extraLinkParams);
return $link;
}
@@ -695,7 +695,7 @@ JS
$start = ($_REQUEST['ctf'][$this->Name()]['start'] - $this->pageSize < 0) ? 0 : $_REQUEST['ctf'][$this->Name()]['start'] - $this->pageSize;
- $link = $this->Link() . "/ajax_refresh?ctf[{$this->Name()}][start]={$start}";
+ $link = $this->Link() . "/?ctf[{$this->Name()}][start]={$start}";
if($this->extraLinkParams) $link .= "&" . http_build_query($this->extraLinkParams);
return $link;
}
@@ -706,7 +706,7 @@ JS
if($currentStart >= $start-1) {
return null;
}
- $link = $this->Link() . "/ajax_refresh?ctf[{$this->Name()}][start]={$start}";
+ $link = $this->Link() . "/?ctf[{$this->Name()}][start]={$start}";
if($this->extraLinkParams) $link .= "&" . http_build_query($this->extraLinkParams);
return $link;
}
@@ -719,7 +719,7 @@ JS
return null;
}
- $link = $this->Link() . "/ajax_refresh?ctf[{$this->Name()}][start]={$start}";
+ $link = $this->Link() . "/?ctf[{$this->Name()}][start]={$start}";
if($this->extraLinkParams) $link .= "&" . http_build_query($this->extraLinkParams);
return $link;
}
@@ -928,6 +928,9 @@ JS
}
+ /**
+ * @deprecated Please use the standard URL through Link() which gives you the FieldHolder
+ */
function ajax_refresh() {
// compute sourceItems here instead of Items() to ensure that
// pagination and filters are respected on template accessors
diff --git a/javascript/ComplexTableField.js b/javascript/ComplexTableField.js
index 0793788af..0ff8e4d17 100755
--- a/javascript/ComplexTableField.js
+++ b/javascript/ComplexTableField.js
@@ -8,12 +8,9 @@ ComplexTableField.prototype = {
popupWidth: 560,
popupHeight: 390,
- deleteConfirmMessage: "Are you sure you want to delete this record?",
-
initialize: function() {
var rules = {};
rules['#'+this.id+' table.data a.popuplink'] = {onclick: this.openPopup.bind(this)};
- rules['#'+this.id+' table.data a.deletelink'] = {onclick: this.deleteRecord.bind(this)};
rules['#'+this.id+' table.data tbody td'] = {onclick: this.openPopup.bind(this)};
Behaviour.register(rules);
@@ -22,49 +19,6 @@ ComplexTableField.prototype = {
if(window != top) $$('#'+this.id+' table.data a.addlink').each(function(el) {Element.hide(el);});
},
- /**
- * Deletes the given dataobject record via an ajax request
- * to complextablefield->Delete()
- * @param {Object} e
- */
- deleteRecord: function(e) {
- var img = Event.element(e);
- var link = Event.findElement(e,"a");
- var row = Event.findElement(e,"tr");
-
- // TODO ajaxErrorHandler and loading-image are dependent on cms, but formfield is in sapphire
- var confirmed = (this.deleteConfirmMessage != undefined) ? confirm(this.deleteConfirmMessage) : true;
- if(confirmed)
- {
- img.setAttribute("src",'cms/images/network-save.gif'); // TODO doesn't work
- new Ajax.Request(
- link.getAttribute("href"),
- {
- method: 'post',
- postBody: 'forceajax=1' + ($('SecurityID') ? '&SecurityID=' + $('SecurityID').value : ''),
- onComplete: function(){
- Effect.Fade(
- row,
- {
- afterFinish: function(obj) {
- // remove row from DOM
- obj.element.parentNode.removeChild(obj.element);
- // recalculate summary if needed (assumes that TableListField.js is present)
- // TODO Proper inheritance
- if(this._summarise) this._summarise();
- // custom callback
- if(this.callback_deleteRecord) this.callback_deleteRecord(e);
- }.bind(this)
- }
- );
- }.bind(this),
- onFailure: this.ajaxErrorHandler
- }
- );
- }
- Event.stop(e);
- },
-
/**
* @param href, table Optional dom object (use for external triggering without an event)
*/
@@ -117,10 +71,6 @@ ComplexTableField.prototype = {
}
}
- GB_OpenerObj = this;
- // use same url to refresh the table after saving the popup, but use a generic rendering method
- GB_RefreshLink = this.getAttribute('href');
-
if(this.GB_Caption) {
var title = this.GB_Caption;
} else {
@@ -129,7 +79,17 @@ ComplexTableField.prototype = {
var title = (type && type[1]) ? type[1].ucfirst() : "";
}
- GB_show(title, popupLink, this.popupHeight, this.popupWidth);
+ // reset internal greybox callbacks, they are not properly unregistered
+ // and fire multiple times on each subsequent popup close action otherwise
+ if(GB_ONLY_ONE) GB_ONLY_ONE.callback_fn = [];
+
+ GB_show(
+ title,
+ popupLink,
+ this.popupHeight,
+ this.popupWidth,
+ this.refresh.bind(this)
+ );
if(e) {
Event.stop(e);
diff --git a/javascript/ComplexTableField_popup.js b/javascript/ComplexTableField_popup.js
index f8684344d..fa104f9b0 100755
--- a/javascript/ComplexTableField_popup.js
+++ b/javascript/ComplexTableField_popup.js
@@ -1,119 +1,10 @@
ComplexTableFieldPopupForm = Class.create();
ComplexTableFieldPopupForm.prototype = {
- errorMessage: "Error talking to server",
-
initialize: function() {
var rules = {};
- rules["#" + this.id + " .Actions input.action"] = {
- 'onclick' : this.submitForm.bind(this)
- };
Behaviour.register(rules);
- },
-
- loadNewPage : function(content) {
- this.innerHTML = content;
- },
-
- submitForm : function(e) {
- // if custom validation implementation (extend class to implement)
- if(this.validate) {
- if(!this.validate()) {
- Event.stop(e);
- return false;
- }
- }
-
- // only do ajaxy stuff for content loaded in an iframe
- if(window != top && parent.parent.GB_hide) {
- var theForm = Event.findElement(e,"form");
- if(parent.parent.statusMessage != undefined) parent.parent.statusMessage('saving');
- var submitButton = document.getElementsBySelector("input.action",theForm)[0];
- if(typeof submitButton != 'undefined') {
- submitButton.disabled = true;
- Element.addClassName(submitButton,'loading');
- }
- new parent.parent.Ajax.Request(
- theForm.getAttribute("action"),
- {
- parameters: Form.serialize(theForm)+"&ajax=1",
- onComplete: this.updateTableAfterSave.bind(this),
- onFailure: this.ajaxErrorHandler.bind(this)
- }
- );
- Event.stop(e);
- return false;
- } else {
- return true;
- }
- },
-
- updateTableAfterSave : function(response) {
- try {
- eval(response.responseText);
- } catch(er) { alert(er.message); }
-
- var theForm = document.getElementsByTagName("form")[0];
-
- // don't update when validation is present and failed
- if(!this.validate || (this.validate && !hasHadFormError())) {
- new parent.parent.Ajax.Request(
- parent.parent.GB_RefreshLink,
- {
- onComplete: this.updateAndHide.bind(parent.parent),
- onFailure : this.ajaxErrorHandler
- }
- );
- } else {
- var submitButton = document.getElementsBySelector("input.action",theForm)[0];
- if(typeof submitButton != 'undefined') {
- submitButton.disabled = false;
- Element.removeClassName(submitButton,'loading');
- }
- }
- },
-
- ajaxErrorHandler: function(response) {
- var submitButton = document.getElementsBySelector("input.action",theForm)[0];
- if(typeof submitButton != 'undefined') {
- submitButton.disabled = false;
- Element.removeClassName(submitButton,'loading');
- }
-
- // TODO does not work due to sandbox-iframe restrictions?
- if(typeof(parent.parent.ajaxErrorHandler) == 'function') {
- parent.parent.ajaxErrorHandler();
- } else {
- alert(this.errorMessage);
- }
- },
-
- updateAndHide: function(response) {
- var theForm =document.getElementsByTagName("form")[0];
-
- var submitButton = document.getElementsBySelector("input.action",theForm)[0];
- if(typeof submitButton != 'undefined') {
- submitButton.disabled = false;
- Element.removeClassName(submitButton,'loading');
- }
-
- onload_init_tabstrip();
-
- // TODO Fix DOM-relation after pagination inside popup
- if(this.GB_OpenerObj) {
- // apparently firefox doesn't remember its DOM after innerHTML, so we help out here...
- var cachedObj = this.GB_OpenerObj;
- var cachedParentObj = this.GB_OpenerObj.parentNode;
- Element.replace(this.GB_OpenerObj, response.responseText);
- this.Behaviour.apply(cachedParentObj);
- cachedObj = null;
- this.GB_OpenerObj = null;
- }
-
- // causes IE6 to go nuts
- this.GB_hide();
-
}
}
ComplexTableFieldPopupForm.applyTo('#ComplexTableField_Popup_DetailForm');
diff --git a/javascript/TableListField.js b/javascript/TableListField.js
index e351d749b..cbf42b6b2 100755
--- a/javascript/TableListField.js
+++ b/javascript/TableListField.js
@@ -13,7 +13,7 @@ TableListField.prototype = {
};
rules['#'+this.id+' th a'] = {
- onclick: this.paginate.bind(this)
+ onclick: this.refresh.bind(this)
};
rules['#'+this.id+' th'] = {
@@ -31,7 +31,7 @@ TableListField.prototype = {
}
};
- rules['#'+this.id+' div.PageControls a'] = {onclick: this.paginate.bind(this)};
+ rules['#'+this.id+' div.PageControls a'] = {onclick: this.refresh.bind(this)};
rules['#'+this.id+' table.data tr td.markingcheckbox'] = {
onclick : function(e) {
@@ -71,7 +71,9 @@ TableListField.prototype = {
},
/**
- * TODO Evaluate server-status before visually deleting (might have caused an error)
+ * Deletes the given dataobject record via an ajax request
+ * to complextablefield->Delete()
+ * @param {Object} e
*/
deleteRecord: function(e) {
var img = Event.element(e);
@@ -82,16 +84,29 @@ TableListField.prototype = {
var confirmed = (this.deleteConfirmMessage != undefined) ? confirm(this.deleteConfirmMessage) : true;
if(confirmed)
{
- img.setAttribute("src",'cms/images/network-save.gif'); // TODO doesn't work in Firefox1.5+
+ img.setAttribute("src",'cms/images/network-save.gif'); // TODO doesn't work
new Ajax.Request(
link.getAttribute("href"),
{
method: 'post',
postBody: 'forceajax=1' + ($('SecurityID') ? '&SecurityID=' + $('SecurityID').value : ''),
onComplete: function(){
- Effect.Fade(row);
+ Effect.Fade(
+ row,
+ {
+ afterFinish: function(obj) {
+ // remove row from DOM
+ obj.element.parentNode.removeChild(obj.element);
+ // recalculate summary if needed (assumes that TableListField.js is present)
+ // TODO Proper inheritance
+ if(this._summarise) this._summarise();
+ // custom callback
+ if(this.callback_deleteRecord) this.callback_deleteRecord(e);
+ }.bind(this)
+ }
+ );
}.bind(this),
- onFailure: this.ajaxErrorHandler.bind(this)
+ onFailure: this.ajaxErrorHandler
}
);
}
@@ -104,22 +119,25 @@ TableListField.prototype = {
this._summarise();
},
- paginate: function(e) {
- var el = Event.element(e);
-
- if(el.nodeName != "a") {
- var el = Event.findElement(e,"a");
+ refresh: function(e) {
+ if(e) {
+ var el = Event.element(e);
+ if(el.nodeName != "a") el = Event.findElement(e,"a");
+ } else {
+ var el = $(this.id);
}
- new Ajax.Request(
- el.href,
+ new Ajax.Updater(
+ $(this.id),
+ el.getAttribute('href'),
{
postBody: 'update=1',
- onComplete: Ajax.Evaluator,
- onFailure: this.ajaxErrorHandler.bind(this)
+ onComplete: function() {
+ Behaviour.apply($(this.id))
+ }.bind(this)
}
);
- Event.stop(e);
+ if(e) Event.stop(e);
return false;
},
diff --git a/search/SearchContext.php b/search/SearchContext.php
index 288dbafc9..53ea51e41 100644
--- a/search/SearchContext.php
+++ b/search/SearchContext.php
@@ -46,6 +46,12 @@ class SearchContext extends Object {
*/
protected $filters;
+ /**
+ * The logical connective used to join WHERE clauses. Defaults to AND.
+ * @var string
+ */
+ public $connective = 'AND';
+
/**
* A key value pair of values that should be searched for.
* The keys should match the field names specified in {@link self::$fields}.
@@ -123,6 +129,8 @@ class SearchContext extends Object {
}
}
}
+
+ $query->connective = $this->connective;
return $query;
}
diff --git a/security/Permission.php b/security/Permission.php
index 2c16ae54f..1693e659f 100755
--- a/security/Permission.php
+++ b/security/Permission.php
@@ -507,6 +507,17 @@ class Permission extends DataObject {
return self::$declared_permissions_list;
}
+ /**
+ * Look up the human-readable title for the permission as defined by Permission::declare_permissions
+ *
+ * @param $perm Permission code
+ * @return Label for the given permission, or the permission itself if the label doesn't exist
+ */
+ public static function get_label_for_permission($perm) {
+ $list = self::get_declared_permissions_list();
+ if(array_key_exists($perm, $list)) return $list[$perm];
+ return $perm;
+ }
/**
* Recursively traverse the nested list of declared permissions and create