mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 12:05:37 +00:00
(merged from branches/roa. use "svn log -c <changeset> -g <module-svn-path>" for detailed commit message)
git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@60207 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
parent
6bd5da7e6e
commit
b5776e0438
@ -54,19 +54,13 @@ class RestfulServer extends Controller {
|
|||||||
*/
|
*/
|
||||||
function index() {
|
function index() {
|
||||||
ContentNegotiator::disable();
|
ContentNegotiator::disable();
|
||||||
|
|
||||||
$requestMethod = $_SERVER['REQUEST_METHOD'];
|
$requestMethod = $_SERVER['REQUEST_METHOD'];
|
||||||
|
|
||||||
if(!isset($this->urlParams['ClassName'])) return $this->notFound();
|
if(!isset($this->urlParams['ClassName'])) return $this->notFound();
|
||||||
$className = $this->urlParams['ClassName'];
|
$className = $this->urlParams['ClassName'];
|
||||||
$id = (isset($this->urlParams['ID'])) ? $this->urlParams['ID'] : null;
|
$id = (isset($this->urlParams['ID'])) ? $this->urlParams['ID'] : null;
|
||||||
$relation = (isset($this->urlParams['Relation'])) ? $this->urlParams['Relation'] : null;
|
$relation = (isset($this->urlParams['Relation'])) ? $this->urlParams['Relation'] : null;
|
||||||
|
|
||||||
// This is a little clumsy and should be improved with the new TokenisedURL that's coming
|
$extension = $this->request->getExtension();
|
||||||
if(strpos($relation,'.') !== false) list($relation, $extension) = explode('.', $relation, 2);
|
|
||||||
else if(strpos($id,'.') !== false) list($id, $extension) = explode('.', $id, 2);
|
|
||||||
else if(strpos($className,'.') !== false) list($className, $extension) = explode('.', $className, 2);
|
|
||||||
else $extension = null;
|
|
||||||
|
|
||||||
// Determine mime-type from extension
|
// Determine mime-type from extension
|
||||||
$contentMap = array(
|
$contentMap = array(
|
||||||
@ -142,6 +136,8 @@ class RestfulServer extends Controller {
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
$obj = DataObject::get($className, "");
|
$obj = DataObject::get($className, "");
|
||||||
|
// show empty serialized result when no records are present
|
||||||
|
if(!$obj) $obj = new DataObjectSet();
|
||||||
if(!singleton($className)->stat('api_access')) {
|
if(!singleton($className)->stat('api_access')) {
|
||||||
return $this->permissionFailure();
|
return $this->permissionFailure();
|
||||||
}
|
}
|
||||||
|
@ -442,6 +442,21 @@ class Controller extends RequestHandlingData {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Joins two link segments together, putting a slash between them if necessary.
|
||||||
|
* Use this for building the results of Link() methods.
|
||||||
|
*/
|
||||||
|
static function join_links() {
|
||||||
|
$args = func_get_args();
|
||||||
|
|
||||||
|
$result = array_shift($args);
|
||||||
|
foreach($args as $arg) {
|
||||||
|
if(substr($result,-1) != '/' && $arg[0] != '/') $result .= "/$arg";
|
||||||
|
else $result .= $arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
@ -32,6 +32,22 @@ class HTTPRequest extends Object implements ArrayAccess {
|
|||||||
|
|
||||||
protected $unshiftedButParsedParts = 0;
|
protected $unshiftedButParsedParts = 0;
|
||||||
|
|
||||||
|
function isGET() {
|
||||||
|
return $this->httpMethod == 'GET';
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPOST() {
|
||||||
|
return $this->httpMethod == 'POST';
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPUT() {
|
||||||
|
return $this->httpMethod == 'PUT';
|
||||||
|
}
|
||||||
|
|
||||||
|
function isDELETE() {
|
||||||
|
return $this->httpMethod == 'DELETE';
|
||||||
|
}
|
||||||
|
|
||||||
function getVars() {
|
function getVars() {
|
||||||
return $this->getVars;
|
return $this->getVars;
|
||||||
}
|
}
|
||||||
@ -53,6 +69,10 @@ class HTTPRequest extends Object implements ArrayAccess {
|
|||||||
if(isset($this->getVars[$name])) return $this->getVars[$name];
|
if(isset($this->getVars[$name])) return $this->getVars[$name];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getExtension() {
|
||||||
|
return $this->extension;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enables the existence of a key-value pair in the request to be checked using
|
* Enables the existence of a key-value pair in the request to be checked using
|
||||||
* array syntax, so isset($request['title']) will check for $_POST['title'] and $_GET['title]
|
* array syntax, so isset($request['title']) will check for $_POST['title'] and $_GET['title]
|
||||||
@ -195,6 +215,7 @@ class HTTPRequest extends Object implements ArrayAccess {
|
|||||||
if($v || !isset($this->allParams[$k])) $this->allParams[$k] = $v;
|
if($v || !isset($this->allParams[$k])) $this->allParams[$k] = $v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if($arguments === array()) $arguments['_matched'] = true;
|
||||||
return $arguments;
|
return $arguments;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,9 +243,8 @@ class HTTPRequest extends Object implements ArrayAccess {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the give pattern is an empty pattern - that is, one that only matches completely parsed
|
* Returns true if this is a URL that will match without shifting off any of the URL.
|
||||||
* URLs. It will also return true if this is a completely parsed URL and the pattern contains only variable
|
* This is used by the request handler to prevent infinite parsing loops.
|
||||||
* references.
|
|
||||||
*/
|
*/
|
||||||
function isEmptyPattern($pattern) {
|
function isEmptyPattern($pattern) {
|
||||||
if(preg_match('/^([A-Za-z]+) +(.*)$/', $pattern, $matches)) {
|
if(preg_match('/^([A-Za-z]+) +(.*)$/', $pattern, $matches)) {
|
||||||
@ -232,10 +252,6 @@ class HTTPRequest extends Object implements ArrayAccess {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(trim($pattern) == "") return true;
|
if(trim($pattern) == "") return true;
|
||||||
|
|
||||||
if(!$this->dirParts) {
|
|
||||||
return preg_replace('/\$[A-Za-z][A-Za-z0-9]*(\/|$)/','',$pattern) == "";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -253,6 +269,5 @@ class HTTPRequest extends Object implements ArrayAccess {
|
|||||||
*/
|
*/
|
||||||
function allParsed() {
|
function allParsed() {
|
||||||
return sizeof($this->dirParts) <= $this->unshiftedButParsedParts;
|
return sizeof($this->dirParts) <= $this->unshiftedButParsedParts;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -23,6 +23,8 @@
|
|||||||
* {@link RequestHandlingData::handleRequest()} is where this behaviour is implemented.
|
* {@link RequestHandlingData::handleRequest()} is where this behaviour is implemented.
|
||||||
*/
|
*/
|
||||||
class RequestHandlingData extends ViewableData {
|
class RequestHandlingData extends ViewableData {
|
||||||
|
protected $request = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default URL handling rules. This specifies that the next component of the URL corresponds to a method to
|
* The default URL handling rules. This specifies that the next component of the URL corresponds to a method to
|
||||||
* be called on this RequestHandlingData object.
|
* be called on this RequestHandlingData object.
|
||||||
@ -68,10 +70,12 @@ class RequestHandlingData extends ViewableData {
|
|||||||
* @uses HTTPRequest
|
* @uses HTTPRequest
|
||||||
*/
|
*/
|
||||||
function handleRequest($request) {
|
function handleRequest($request) {
|
||||||
|
$this->request = $request;
|
||||||
|
|
||||||
foreach($this->stat('url_handlers') as $rule => $action) {
|
foreach($this->stat('url_handlers') as $rule => $action) {
|
||||||
if(isset($_GET['debug_request'])) Debug::message("Testing '$rule' with '" . $request->remaining() . "' on $this->class");
|
if(isset($_REQUEST['debug_request'])) Debug::message("Testing '$rule' with '" . $request->remaining() . "' on $this->class");
|
||||||
if($params = $request->match($rule, true)) {
|
if($params = $request->match($rule, true)) {
|
||||||
if(isset($_GET['debug_request'])) Debug::message("Rule '$rule' matched on $this->class");
|
if(isset($_REQUEST['debug_request'])) Debug::message("Rule '$rule' matched on $this->class");
|
||||||
|
|
||||||
// Actions can reference URL parameters, eg, '$Action/$ID/$OtherID' => '$Action',
|
// Actions can reference URL parameters, eg, '$Action/$ID/$OtherID' => '$Action',
|
||||||
if($action[0] == '$') $action = $params[substr($action,1)];
|
if($action[0] == '$') $action = $params[substr($action,1)];
|
||||||
|
@ -616,9 +616,14 @@ class DataObject extends ViewableData implements DataObjectInterface {
|
|||||||
foreach($this->record as $fieldName => $fieldValue) {
|
foreach($this->record as $fieldName => $fieldValue) {
|
||||||
if(isset($this->changed[$fieldName]) && $this->changed[$fieldName] && $fieldType = $classSingleton->fieldExists($fieldName)) {
|
if(isset($this->changed[$fieldName]) && $this->changed[$fieldName] && $fieldType = $classSingleton->fieldExists($fieldName)) {
|
||||||
$fieldObj = $this->obj($fieldName);
|
$fieldObj = $this->obj($fieldName);
|
||||||
$fieldObj->setValue($this->record[$fieldName], $this->record);
|
|
||||||
if(!isset($manipulation[$class])) $manipulation[$class] = array();
|
if(!isset($manipulation[$class])) $manipulation[$class] = array();
|
||||||
if($fieldObj) $fieldObj->writeToManipulation($manipulation[$class]);
|
|
||||||
|
// if database column doesn't correlate to a DBField instance, set up a default Varchar DBField
|
||||||
|
// (used mainly for has_one/has_many)
|
||||||
|
if(!$fieldObj) $fieldObj = DBField::create('Varchar', $this->record[$fieldName], $fieldName);
|
||||||
|
|
||||||
|
$fieldObj->setValue($this->record[$fieldName], $this->record);
|
||||||
|
$fieldObj->writeToManipulation($manipulation[$class]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1051,7 +1056,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
|
|||||||
return $candidate;
|
return $candidate;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
eval("\$items = array_merge((array){$class}::\$db, (array)\$items);");
|
eval("\$items = array_merge((array)\$items, (array){$class}::\$db);");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1192,19 +1197,29 @@ class DataObject extends ViewableData implements DataObjectInterface {
|
|||||||
*/
|
*/
|
||||||
public function scaffoldFormFields() {
|
public function scaffoldFormFields() {
|
||||||
$fields = new FieldSet();
|
$fields = new FieldSet();
|
||||||
|
$fields->push(new HeaderField($this->singular_name()));
|
||||||
foreach($this->db() as $fieldName => $fieldType) {
|
foreach($this->db() as $fieldName => $fieldType) {
|
||||||
// @todo Pass localized title
|
// @todo Pass localized title
|
||||||
// commented out, to be less of a pain in the ass
|
// commented out, to be less of a pain in the ass
|
||||||
//$fields->addFieldToTab('Root.Main', $this->dbObject($fieldName)->scaffoldFormField());
|
//$fields->addFieldToTab('Root.Main', $this->dbObject($fieldName)->scaffoldFormField());
|
||||||
$fields->push($this->dbObject($fieldName)->scaffoldFormField());
|
$fields->push($this->dbObject($fieldName)->scaffoldFormField());
|
||||||
}
|
}
|
||||||
|
|
||||||
// @todo Add relation tabs
|
|
||||||
|
|
||||||
return $fields;
|
return $fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the scaffold-generated relation fields to the given field set
|
||||||
|
*/
|
||||||
|
protected function addScaffoldRelationFields($fieldSet) {
|
||||||
|
foreach($this->has_many() as $relationship => $component) {
|
||||||
|
$relationshipFields = array_keys($this->searchableFields());
|
||||||
|
$fieldSet->push(new ComplexTableField($this, $relationship, $component, $relationshipFields));
|
||||||
|
}
|
||||||
|
return $fieldSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Centerpiece of every data administration interface in Silverstripe,
|
* Centerpiece of every data administration interface in Silverstripe,
|
||||||
* which returns a {@link FieldSet} suitable for a {@link Form} object.
|
* which returns a {@link FieldSet} suitable for a {@link Form} object.
|
||||||
@ -1228,7 +1243,12 @@ class DataObject extends ViewableData implements DataObjectInterface {
|
|||||||
* @return FieldSet
|
* @return FieldSet
|
||||||
*/
|
*/
|
||||||
public function getCMSFields() {
|
public function getCMSFields() {
|
||||||
return $this->scaffoldFormFields();
|
$fields = $this->scaffoldFormFields();
|
||||||
|
// If we don't have an ID, then relation fields don't work
|
||||||
|
if($this->ID) {
|
||||||
|
$this->addScaffoldRelationFields($fields);
|
||||||
|
}
|
||||||
|
return $fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -53,9 +53,6 @@ class ComplexTableField extends TableListField {
|
|||||||
*/
|
*/
|
||||||
protected $permissions = array(
|
protected $permissions = array(
|
||||||
"add",
|
"add",
|
||||||
"edit",
|
|
||||||
"show",
|
|
||||||
"delete",
|
|
||||||
//"export",
|
//"export",
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -114,6 +111,7 @@ class ComplexTableField extends TableListField {
|
|||||||
|
|
||||||
static $url_handlers = array(
|
static $url_handlers = array(
|
||||||
'item/$ID' => 'handleItem',
|
'item/$ID' => 'handleItem',
|
||||||
|
'$Action!' => '$Action',
|
||||||
);
|
);
|
||||||
|
|
||||||
function handleItem($request) {
|
function handleItem($request) {
|
||||||
@ -387,6 +385,124 @@ JS;
|
|||||||
function setTemplatePopup($template) {
|
function setTemplatePopup($template) {
|
||||||
$this->templatePopup = $template;
|
$this->templatePopup = $template;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
function getFieldsFor($childData) {
|
||||||
|
// Add the relation value to related records
|
||||||
|
if(!$childData->ID && $this->getParentClass()) {
|
||||||
|
// make sure the relation-link is existing, even if we just add the sourceClass and didn't save it
|
||||||
|
$parentIDName = $this->getParentIdName( $this->getParentClass(), $this->sourceClass() );
|
||||||
|
$childData->$parentIDName = $childData->ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the fieldset is passed, use it
|
||||||
|
if(is_a($this->detailFormFields,"Fieldset")) {
|
||||||
|
$detailFields = $this->detailFormFields;
|
||||||
|
|
||||||
|
// Else use the formfields returned from the object via a string method call.
|
||||||
|
} else {
|
||||||
|
if(!is_string($this->detailFormFields)) $this->detailFormFields = "getCMSFields";
|
||||||
|
$functioncall = $this->detailFormFields;
|
||||||
|
if(!$childData->hasMethod($functioncall)) $functioncall = "getCMSFields";
|
||||||
|
|
||||||
|
$detailFields = $childData->$functioncall();
|
||||||
|
}
|
||||||
|
|
||||||
|
// the ID field confuses the Controller-logic in finding the right view for ReferencedField
|
||||||
|
$detailFields->removeByName('ID');
|
||||||
|
|
||||||
|
// only add childID if we're not adding a record
|
||||||
|
if($childData->ID) {
|
||||||
|
$detailFields->push(new HiddenField("ctf[childID]","",$childData->ID));
|
||||||
|
}
|
||||||
|
|
||||||
|
// add a namespaced ID instead thats "converted" by saveComplexTableField()
|
||||||
|
$detailFields->push(new HiddenField("ctf[ClassName]","",$this->sourceClass()));
|
||||||
|
|
||||||
|
if($this->getParentClass()) {
|
||||||
|
$parentIdName = $this->getParentIdName($this->getParentClass(), $this->sourceClass());
|
||||||
|
/*
|
||||||
|
if(!$parentIdName) {
|
||||||
|
user_error("ComplexTableField::DetailForm() Cannot automatically
|
||||||
|
determine 'has-one'-relationship to parent class " . $this->ctf->getParentClass() . ",
|
||||||
|
please use setParentClass() to set it manually",
|
||||||
|
E_USER_WARNING);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
if($parentIdName) {
|
||||||
|
// add relational fields
|
||||||
|
$detailFields->push(new HiddenField("ctf[parentClass]"," ",$this->getParentClass()));
|
||||||
|
|
||||||
|
if( $this->relationAutoSetting )
|
||||||
|
$detailFields->push(new HiddenField("$parentIdName"," ",$childData->ID));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $detailFields;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getValidatorFor($childData) {
|
||||||
|
// if no custom validator is set, and there's on present on the object (e.g. Member), use it
|
||||||
|
if(!isset($this->detailFormValidator) && $childData->hasMethod('getValidator')) {
|
||||||
|
$this->detailFormValidator = $childData->getValidator();
|
||||||
|
}
|
||||||
|
return $this->detailFormValidator;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
function add() {
|
||||||
|
if(!$this->can('add')) return;
|
||||||
|
|
||||||
|
return $this->customise(array(
|
||||||
|
'DetailForm' => $this->AddForm(),
|
||||||
|
))->renderWith($this->templatePopup);
|
||||||
|
}
|
||||||
|
|
||||||
|
function AddForm($childID = null) {
|
||||||
|
$className = $this->sourceClass();
|
||||||
|
$childData = new $className();
|
||||||
|
|
||||||
|
$fields = $this->getFieldsFor($childData);
|
||||||
|
$validator = $this->getValidatorFor($childData);
|
||||||
|
|
||||||
|
$form = Object::create(
|
||||||
|
$this->popupClass,
|
||||||
|
$this, "AddForm",
|
||||||
|
$fields, $validator, false, $childData);
|
||||||
|
|
||||||
|
return $form;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use the URL-Parameter "action_saveComplexTableField"
|
||||||
|
* to provide a clue to the main controller if the main form has to be rendered,
|
||||||
|
* even if there is no action relevant for the main controller (to provide the instance of ComplexTableField
|
||||||
|
* which in turn saves the record.
|
||||||
|
*
|
||||||
|
* @see {Form::ReferencedField}).
|
||||||
|
*/
|
||||||
|
function saveComplexTableField($params) {
|
||||||
|
$className = $this->sourceClass();
|
||||||
|
$childData = new $className();
|
||||||
|
|
||||||
|
$this->saveInto($childData);
|
||||||
|
$childData->write();
|
||||||
|
|
||||||
|
// 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->controller->DetailForm();
|
||||||
|
//$form->loadDataFrom($this->dataObject);
|
||||||
|
FormResponse::update_dom_id($form->FormName(), $form->formHtmlContent(), true, 'update');
|
||||||
|
return FormResponse::respond();
|
||||||
|
} else {
|
||||||
|
Director::redirectBack();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -463,21 +579,6 @@ class ComplexTableField_ItemRequest extends RequestHandlingData {
|
|||||||
echo $this->renderWith($this->ctf->templatePopup);
|
echo $this->renderWith($this->ctf->templatePopup);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Just a hook, processed in {DetailForm()}
|
|
||||||
*
|
|
||||||
* @return String
|
|
||||||
*/
|
|
||||||
function add() {
|
|
||||||
if($this->Can('add') !== true) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->methodName = "add";
|
|
||||||
|
|
||||||
echo $this->renderWith($this->templatePopup);
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -506,89 +607,46 @@ class ComplexTableField_ItemRequest extends RequestHandlingData {
|
|||||||
function DetailForm($childID = null) {
|
function DetailForm($childID = null) {
|
||||||
$childData = $this->obj();
|
$childData = $this->obj();
|
||||||
|
|
||||||
// If the fieldset is passed, use it, else use the formfields returned
|
$fields = $this->ctf->getFieldsFor($childData);
|
||||||
// from the object via a string method call.
|
$validator = $this->ctf->getValidatorFor($childData);
|
||||||
if(is_a($this->ctf->detailFormFields,"Fieldset")){
|
|
||||||
$detailFields = $this->detailFormFields;
|
|
||||||
|
|
||||||
} else if( isset( $childData ) && is_string($this->ctf->detailFormFields)){
|
|
||||||
$functioncall = $this->ctf->detailFormFields;
|
|
||||||
if($childData->hasMethod($functioncall)){
|
|
||||||
$detailFields = $childData->$functioncall();
|
|
||||||
}
|
|
||||||
|
|
||||||
} elseif(! isset( $childData ) || $this->methodName == 'add') {
|
|
||||||
$SNG_sourceClass = singleton($this->ctf->sourceClass());
|
|
||||||
if($childData && is_numeric($childData->ID) && $this->ctf->getParentClass()) {
|
|
||||||
// make sure the relation-link is existing, even if we just add the sourceClass
|
|
||||||
// and didn't save it
|
|
||||||
$parentIDName = $this->ctf->getParentIdName( $this->getParentClass(), $this->sourceClass() );
|
|
||||||
$SNG_sourceClass->$parentIDName = $childData->ID;
|
|
||||||
}
|
|
||||||
$functioncall = $this->detailFormFields;
|
|
||||||
if($SNG_sourceClass->hasMethod($functioncall)){
|
|
||||||
$detailFields = $SNG_sourceClass->$functioncall();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
$detailFields = $SNG_sourceClass->getCMSFields();
|
|
||||||
} else {
|
|
||||||
$detailFields = $childData->getCMSFields();
|
|
||||||
}
|
|
||||||
|
|
||||||
if($this->ctf->getParentClass()) {
|
|
||||||
$parentIdName = $this->ctf->getParentIdName($this->ctf->getParentClass(), $this->ctf->sourceClass());
|
|
||||||
/*
|
|
||||||
if(!$parentIdName) {
|
|
||||||
user_error("ComplexTableField::DetailForm() Cannot automatically
|
|
||||||
determine 'has-one'-relationship to parent class " . $this->ctf->getParentClass() . ",
|
|
||||||
please use setParentClass() to set it manually",
|
|
||||||
E_USER_WARNING);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
if($parentIdName) {
|
|
||||||
// add relational fields
|
|
||||||
$detailFields->push(new HiddenField("ctf[parentClass]"," ",$this->ctf->getParentClass()));
|
|
||||||
|
|
||||||
if( $this->relationAutoSetting )
|
|
||||||
$detailFields->push(new HiddenField("$parentIdName"," ",$childData->ID));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// the ID field confuses the Controller-logic in finding the right view for ReferencedField
|
|
||||||
$detailFields->removeByName('ID');
|
|
||||||
|
|
||||||
// only add childID if we're not adding a record
|
|
||||||
if($this->methodName != 'add') {
|
|
||||||
$detailFields->push(new HiddenField("ctf[childID]","",$childData->ID));
|
|
||||||
}
|
|
||||||
|
|
||||||
// add a namespaced ID instead thats "converted" by saveComplexTableField()
|
|
||||||
$detailFields->push(new HiddenField("ctf[ClassName]","",$this->ctf->sourceClass()));
|
|
||||||
|
|
||||||
$readonly = ($this->methodName == "show");
|
$readonly = ($this->methodName == "show");
|
||||||
|
|
||||||
// if no custom validator is set, and there's on present on the object (e.g. Member), use it
|
$form = Object::create(
|
||||||
if(!isset($this->ctf->detailFormValidator) && singleton($this->ctf->sourceClass())->hasMethod('getValidator')) {
|
$this->ctf->popupClass,
|
||||||
$this->ctf->detailFormValidator = singleton($this->ctf->sourceClass())->getValidator();
|
$this, "DetailForm",
|
||||||
}
|
$fields, $validator, $readonly, $childData);
|
||||||
|
|
||||||
$form = Object::create($this->ctf->popupClass,$this, "DetailForm", $detailFields, $this->ctf->detailFormValidator, $readonly, $childData);
|
$form->loadDataFrom($childData);
|
||||||
|
if ($readonly) $form->makeReadonly();
|
||||||
if (is_numeric($childData->ID)) {
|
|
||||||
if ($this->methodName == "show" || $this->methodName == "edit") {
|
|
||||||
$form->loadDataFrom($childData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->methodName == "show") {
|
|
||||||
$form->makeReadonly();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $form;
|
return $form;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use the URL-Parameter "action_saveComplexTableField"
|
||||||
|
* to provide a clue to the main controller if the main form has to be rendered,
|
||||||
|
* even if there is no action relevant for the main controller (to provide the instance of ComplexTableField
|
||||||
|
* which in turn saves the record.
|
||||||
|
*
|
||||||
|
* @see {Form::ReferencedField}).
|
||||||
|
*/
|
||||||
|
function saveComplexTableField($data, $form, $request) {
|
||||||
|
$form->saveInto($this->obj());
|
||||||
|
$this->obj()->write();
|
||||||
|
|
||||||
|
// 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->controller->DetailForm();
|
||||||
|
//$form->loadDataFrom($this->dataObject);
|
||||||
|
FormResponse::update_dom_id($form->FormName(), $form->formHtmlContent(), true, 'update');
|
||||||
|
return FormResponse::respond();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Director::redirectBack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function PopupBaseLink() {
|
function PopupBaseLink() {
|
||||||
$link = $this->FormAction() . "&action_callfieldmethod&fieldName={$this->Name()}";
|
$link = $this->FormAction() . "&action_callfieldmethod&fieldName={$this->Name()}";
|
||||||
if(!strpos($link,'ctf[ID]')) {
|
if(!strpos($link,'ctf[ID]')) {
|
||||||
@ -790,7 +848,6 @@ class ComplexTableField_Popup extends Form {
|
|||||||
* WARNING: DO NOT CHANGE THE ORDER OF THESE JS FILES
|
* WARNING: DO NOT CHANGE THE ORDER OF THESE JS FILES
|
||||||
* Some have special requirements.
|
* Some have special requirements.
|
||||||
*/
|
*/
|
||||||
Requirements::clear();
|
|
||||||
//Requirements::css('cms/css/layout.css');
|
//Requirements::css('cms/css/layout.css');
|
||||||
Requirements::css('jsparty/tabstrip/tabstrip.css');
|
Requirements::css('jsparty/tabstrip/tabstrip.css');
|
||||||
Requirements::css('sapphire/css/Form.css');
|
Requirements::css('sapphire/css/Form.css');
|
||||||
@ -812,7 +869,7 @@ class ComplexTableField_Popup extends Form {
|
|||||||
Requirements::javascript("sapphire/javascript/ComplexTableField_popup.js");
|
Requirements::javascript("sapphire/javascript/ComplexTableField_popup.js");
|
||||||
|
|
||||||
if($this->dataObject->hasMethod('getRequirementsForPopup')) {
|
if($this->dataObject->hasMethod('getRequirementsForPopup')) {
|
||||||
$this->data->getRequirementsForPopup();
|
$this->dataObject->getRequirementsForPopup();
|
||||||
}
|
}
|
||||||
|
|
||||||
$actions = new FieldSet();
|
$actions = new FieldSet();
|
||||||
@ -833,31 +890,6 @@ class ComplexTableField_Popup extends Form {
|
|||||||
function ShowPagination() {
|
function ShowPagination() {
|
||||||
return $this->controller->ShowPagination();
|
return $this->controller->ShowPagination();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use the URL-Parameter "action_saveComplexTableField"
|
|
||||||
* to provide a clue to the main controller if the main form has to be rendered,
|
|
||||||
* even if there is no action relevant for the main controller (to provide the instance of ComplexTableField
|
|
||||||
* which in turn saves the record.
|
|
||||||
*
|
|
||||||
* @see {Form::ReferencedField}).
|
|
||||||
*/
|
|
||||||
function saveComplexTableField($params) {
|
|
||||||
$this->saveInto($this->dataObject);
|
|
||||||
$this->dataObject->write();
|
|
||||||
|
|
||||||
// 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->controller->DetailForm();
|
|
||||||
//$form->loadDataFrom($this->dataObject);
|
|
||||||
FormResponse::update_dom_id($form->FormName(), $form->formHtmlContent(), true, 'update');
|
|
||||||
return FormResponse::respond();
|
|
||||||
} else {
|
|
||||||
Director::redirectBack();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
@ -182,11 +182,11 @@ class Form extends RequestHandlingData {
|
|||||||
|
|
||||||
// First, try a handler method on the controller
|
// First, try a handler method on the controller
|
||||||
if($this->controller->hasMethod($funcName)) {
|
if($this->controller->hasMethod($funcName)) {
|
||||||
return $this->controller->$funcName($vars, $this);
|
return $this->controller->$funcName($vars, $this, $request);
|
||||||
|
|
||||||
// Otherwise, try a handler method on the form object
|
// Otherwise, try a handler method on the form object
|
||||||
} else {
|
} else {
|
||||||
return $this->$funcName($vars, $this);
|
return $this->$funcName($vars, $this, $request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -456,7 +456,6 @@ class Form extends RequestHandlingData {
|
|||||||
*/
|
*/
|
||||||
function setFormMethod($method) {
|
function setFormMethod($method) {
|
||||||
$this->formMethod = strtolower($method);
|
$this->formMethod = strtolower($method);
|
||||||
if($this->formMethod == 'get') $this->fields->push(new HiddenField('executeForm', '', $this->name));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -466,20 +465,46 @@ class Form extends RequestHandlingData {
|
|||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
function FormAction() {
|
function FormAction() {
|
||||||
if($this->controller->hasMethod("FormObjectLink")) {
|
if ($this->formActionPath) {
|
||||||
|
return $this->formActionPath;
|
||||||
|
} elseif($this->controller->hasMethod("FormObjectLink")) {
|
||||||
return $this->controller->FormObjectLink($this->name);
|
return $this->controller->FormObjectLink($this->name);
|
||||||
} else {
|
} else {
|
||||||
$link = $this->controller->Link();
|
return Controller::join_links($this->controller->Link(), $this->name);
|
||||||
if(substr($link,-1) != '/') $link .= '/';
|
|
||||||
return $link . $this->name;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @ignore */
|
||||||
|
private $formActionPath = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the form action attribute to a custom URL.
|
||||||
|
*
|
||||||
|
* Note: For "normal" forms, you shouldn't need to use this method. It is recommended only for situations where you have
|
||||||
|
* two relatively distinct parts of the system trying to communicate via a form post.
|
||||||
|
*/
|
||||||
|
function setFormAction($path) {
|
||||||
|
$this->formActionPath = $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
private $htmlID = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the name of the form
|
* Returns the name of the form
|
||||||
*/
|
*/
|
||||||
function FormName() {
|
function FormName() {
|
||||||
return $this->class . '_' . str_replace('.', '', $this->name);
|
if($this->htmlID) return $this->htmlID;
|
||||||
|
else return $this->class . '_' . str_replace('.','',$this->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the HTML ID attribute of the form
|
||||||
|
*/
|
||||||
|
function setHTMLID($id) {
|
||||||
|
$this->htmlID = $id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -920,7 +945,6 @@ class Form extends RequestHandlingData {
|
|||||||
*/
|
*/
|
||||||
function testSubmission($action, $data) {
|
function testSubmission($action, $data) {
|
||||||
$data['action_' . $action] = true;
|
$data['action_' . $action] = true;
|
||||||
$data['executeForm'] = $this->name;
|
|
||||||
|
|
||||||
return Director::test($this->FormAction(), $data, Controller::curr()->getSession());
|
return Director::test($this->FormAction(), $data, Controller::curr()->getSession());
|
||||||
|
|
||||||
|
@ -465,16 +465,6 @@ HTML;
|
|||||||
user_error('FormField::setExtraClass() is deprecated. Use FormField::addExtraClass() instead.', E_USER_NOTICE);
|
user_error('FormField::setExtraClass() is deprecated. Use FormField::addExtraClass() instead.', E_USER_NOTICE);
|
||||||
$this->extraClasses[] = $extraClass;
|
$this->extraClasses[] = $extraClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This returns a link used to specify a controller in a form action.
|
|
||||||
* This lets us use FormFields as controllers.
|
|
||||||
*/
|
|
||||||
function FormObjectLink($formName) {
|
|
||||||
return $this->form->Controller()->Link() . "?executeController=" . $this->form->Name() . ".FieldMap." . $this->Name() . "&executeForm=" . $formName;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
@ -72,8 +72,6 @@ ComplexTableField.prototype = {
|
|||||||
popupLink = _popupLink;
|
popupLink = _popupLink;
|
||||||
table = _table;
|
table = _table;
|
||||||
} else {
|
} else {
|
||||||
alert(this.innerHTML);
|
|
||||||
|
|
||||||
// if clicked item is an input-element, don't trigger popup
|
// if clicked item is an input-element, don't trigger popup
|
||||||
var el = Event.element(e);
|
var el = Event.element(e);
|
||||||
var input = Event.findElement(e,"input");
|
var input = Event.findElement(e,"input");
|
||||||
|
@ -75,6 +75,12 @@ class SearchContext extends Object {
|
|||||||
parent::__construct();
|
parent::__construct();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns scaffolded search fields for UI.
|
||||||
|
*
|
||||||
|
* @todo is this necessary in the SearchContext? - ModelAdmin could unwrap this and just use DataObject::scaffoldSearchFields
|
||||||
|
* @return FieldSet
|
||||||
|
*/
|
||||||
public function getSearchFields() {
|
public function getSearchFields() {
|
||||||
return ($this->fields) ? $this->fields : singleton($this->modelClass)->scaffoldSearchFields();
|
return ($this->fields) ? $this->fields : singleton($this->modelClass)->scaffoldSearchFields();
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
* @package sapphire
|
* @package sapphire
|
||||||
* @subpackage search
|
* @subpackage search
|
||||||
*/
|
*/
|
||||||
class ExactMatchSearchFilter extends SearchFilter {
|
class ExactMatchFilter extends SearchFilter {
|
||||||
|
|
||||||
}
|
}
|
||||||
?>
|
?>
|
@ -5,7 +5,7 @@
|
|||||||
* @package sapphire
|
* @package sapphire
|
||||||
* @subpackage search
|
* @subpackage search
|
||||||
*/
|
*/
|
||||||
class FulltextSearchFilter extends SearchFilter {
|
class FulltextFilter extends SearchFilter {
|
||||||
|
|
||||||
}
|
}
|
||||||
?>
|
?>
|
@ -5,7 +5,7 @@
|
|||||||
* @package sapphire
|
* @package sapphire
|
||||||
* @subpackage search
|
* @subpackage search
|
||||||
*/
|
*/
|
||||||
class PartialMatchSearchFilter extends SearchFilter {
|
class PartialMatchFilter extends SearchFilter {
|
||||||
|
|
||||||
}
|
}
|
||||||
?>
|
?>
|
13
search/filters/SubstringMatchFilter.php
Normal file
13
search/filters/SubstringMatchFilter.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Uses a substring match against content in column rows.
|
||||||
|
*
|
||||||
|
* @package sapphire
|
||||||
|
* @subpackage search
|
||||||
|
*/
|
||||||
|
class SubstringMatchFilter extends SearchFilter {
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
@ -124,6 +124,20 @@ class DataObjectTest extends SapphireTest {
|
|||||||
$this->assertTrue($comment->Name == 'Joe');
|
$this->assertTrue($comment->Name == 'Joe');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test writing of database columns which don't correlate to a DBField,
|
||||||
|
* e.g. all relation fields on has_one/has_many like "ParentID".
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function testWritePropertyWithoutDBField() {
|
||||||
|
$page = $this->objFromFixture('Page', 'page1');
|
||||||
|
$page->ParentID = 99;
|
||||||
|
$page->write();
|
||||||
|
// reload the page from the database
|
||||||
|
$savedPage = DataObject::get_by_id('Page', $page->ID);
|
||||||
|
$this->assertTrue($savedPage->ParentID == 99);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test has many relationships
|
* Test has many relationships
|
||||||
* - Test getComponents() gets the ComponentSet of the other side of the relation
|
* - Test getComponents() gets the ComponentSet of the other side of the relation
|
||||||
|
18
tests/HTTPRequestTest.php
Normal file
18
tests/HTTPRequestTest.php
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class HTTPRequestTest extends SapphireTest {
|
||||||
|
static $fixture_file = null;
|
||||||
|
|
||||||
|
function testMatch() {
|
||||||
|
$request = new HTTPRequest("GET", "admin/crm/add");
|
||||||
|
|
||||||
|
/* When a rule matches, but has no variables, array("_matched" => true) is returned. */
|
||||||
|
$this->assertEquals(array("_matched" => true), $request->match('admin/crm', true));
|
||||||
|
|
||||||
|
/* Becasue we shifted admin/crm off the stack, just "add" should be remaining */
|
||||||
|
$this->assertEquals("add", $request->remaining());
|
||||||
|
|
||||||
|
$this->assertEquals(array("_matched" => true), $request->match('add', true));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
35
tests/SearchContextTest.php
Normal file
35
tests/SearchContextTest.php
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class SearchContextTest extends SapphireTest {
|
||||||
|
static $fixture_file = 'sapphire/tests/SearchContextTest.yml';
|
||||||
|
|
||||||
|
function testResultSetFilterReturnsExpectedCount() {
|
||||||
|
$person = singleton('PersonBubble');
|
||||||
|
$context = $person->getDefaultSearchContext();
|
||||||
|
|
||||||
|
$results = $context->getResultSet(array('Name'=>''));
|
||||||
|
$this->assertEquals(5, $results->Count());
|
||||||
|
|
||||||
|
$results = $context->getResultSet(array('EyeColor'=>'green'));
|
||||||
|
$this->assertEquals(2, $results->Count());
|
||||||
|
|
||||||
|
$results = $context->getResultSet(array('EyeColor'=>'green', 'HairColor'=>'black'));
|
||||||
|
$this->assertEquals(1, $results->Count());
|
||||||
|
}
|
||||||
|
|
||||||
|
//function
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class PersonBubble extends DataObject {
|
||||||
|
|
||||||
|
static $db = array(
|
||||||
|
"Name" => "Text",
|
||||||
|
"Email" => "Text",
|
||||||
|
"HairColor" => "Text",
|
||||||
|
"EyeColor" => "Text"
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
28
tests/SearchContextTest.yml
Normal file
28
tests/SearchContextTest.yml
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
PersonBubble:
|
||||||
|
person1:
|
||||||
|
Name: James
|
||||||
|
Email: james@example.com
|
||||||
|
HairColor: brown
|
||||||
|
EyeColor: brown
|
||||||
|
person2:
|
||||||
|
Name: John
|
||||||
|
Email: john@example.com
|
||||||
|
HairColor: blond
|
||||||
|
EyeColor: blue
|
||||||
|
person3:
|
||||||
|
Name: Jane
|
||||||
|
Email: jane@example.com
|
||||||
|
HairColor: brown
|
||||||
|
EyeColor: green
|
||||||
|
person4:
|
||||||
|
Name: Hemi
|
||||||
|
Email: hemi@example.com
|
||||||
|
HairColor: black
|
||||||
|
EyeColor: brown
|
||||||
|
person5:
|
||||||
|
Name: Sara
|
||||||
|
Email: sara@example.com
|
||||||
|
HairColor: black
|
||||||
|
EyeColor: green
|
||||||
|
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user