From b5776e04382f3746a7b751cec64b48278668d659 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Sat, 9 Aug 2008 03:54:55 +0000 Subject: [PATCH] (merged from branches/roa. use "svn log -c -g " for detailed commit message) git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@60207 467b73ca-7a2a-4603-9d3b-597d59a354a9 --- api/RestfulServer.php | 14 +- core/control/Controller.php | 15 + core/control/HTTPRequest.php | 31 +- core/control/RequestHandlingData.php | 8 +- core/model/DataObject.php | 38 ++- forms/ComplexTableField.php | 276 ++++++++++-------- forms/Form.php | 44 ++- forms/FormField.php | 10 - javascript/ComplexTableField.js | 2 - search/SearchContext.php | 8 +- ...hSearchFilter.php => ExactMatchFilter.php} | 2 +- ...extSearchFilter.php => FulltextFilter.php} | 2 +- ...earchFilter.php => PartialMatchFilter.php} | 2 +- search/filters/SubstringMatchFilter.php | 13 + tests/DataObjectTest.php | 14 + tests/HTTPRequestTest.php | 18 ++ tests/SearchContextTest.php | 35 +++ tests/SearchContextTest.yml | 28 ++ 18 files changed, 384 insertions(+), 176 deletions(-) rename search/filters/{ExactMatchSearchFilter.php => ExactMatchFilter.php} (78%) rename search/filters/{FulltextSearchFilter.php => FulltextFilter.php} (65%) rename search/filters/{PartialMatchSearchFilter.php => PartialMatchFilter.php} (70%) create mode 100644 search/filters/SubstringMatchFilter.php create mode 100644 tests/HTTPRequestTest.php create mode 100644 tests/SearchContextTest.php create mode 100644 tests/SearchContextTest.yml diff --git a/api/RestfulServer.php b/api/RestfulServer.php index 279a8d25d..6aa321c9a 100644 --- a/api/RestfulServer.php +++ b/api/RestfulServer.php @@ -54,20 +54,14 @@ class RestfulServer extends Controller { */ function index() { ContentNegotiator::disable(); - $requestMethod = $_SERVER['REQUEST_METHOD']; - if(!isset($this->urlParams['ClassName'])) return $this->notFound(); $className = $this->urlParams['ClassName']; $id = (isset($this->urlParams['ID'])) ? $this->urlParams['ID'] : 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 - 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; - + $extension = $this->request->getExtension(); + // Determine mime-type from extension $contentMap = array( 'xml' => 'text/xml', @@ -77,7 +71,7 @@ class RestfulServer extends Controller { 'html' => 'text/html', ); $contentType = isset($contentMap[$extension]) ? $contentMap[$extension] : 'text/xml'; - + switch($requestMethod) { case 'GET': return $this->getHandler($className, $id, $relation, $contentType); @@ -142,6 +136,8 @@ class RestfulServer extends Controller { } else { $obj = DataObject::get($className, ""); + // show empty serialized result when no records are present + if(!$obj) $obj = new DataObjectSet(); if(!singleton($className)->stat('api_access')) { return $this->permissionFailure(); } diff --git a/core/control/Controller.php b/core/control/Controller.php index 58af2f76b..f97ae953d 100644 --- a/core/control/Controller.php +++ b/core/control/Controller.php @@ -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; + } } ?> diff --git a/core/control/HTTPRequest.php b/core/control/HTTPRequest.php index 56e6bd133..8c245d73e 100644 --- a/core/control/HTTPRequest.php +++ b/core/control/HTTPRequest.php @@ -32,6 +32,22 @@ class HTTPRequest extends Object implements ArrayAccess { 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() { return $this->getVars; } @@ -53,6 +69,10 @@ class HTTPRequest extends Object implements ArrayAccess { 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 * 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($arguments === array()) $arguments['_matched'] = true; 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 - * URLs. It will also return true if this is a completely parsed URL and the pattern contains only variable - * references. + * Returns true if this is a URL that will match without shifting off any of the URL. + * This is used by the request handler to prevent infinite parsing loops. */ function isEmptyPattern($pattern) { if(preg_match('/^([A-Za-z]+) +(.*)$/', $pattern, $matches)) { @@ -232,10 +252,6 @@ class HTTPRequest extends Object implements ArrayAccess { } 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() { return sizeof($this->dirParts) <= $this->unshiftedButParsedParts; - } } \ No newline at end of file diff --git a/core/control/RequestHandlingData.php b/core/control/RequestHandlingData.php index d6217691e..84af790da 100644 --- a/core/control/RequestHandlingData.php +++ b/core/control/RequestHandlingData.php @@ -23,6 +23,8 @@ * {@link RequestHandlingData::handleRequest()} is where this behaviour is implemented. */ 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 * be called on this RequestHandlingData object. @@ -68,10 +70,12 @@ class RequestHandlingData extends ViewableData { * @uses HTTPRequest */ function handleRequest($request) { + $this->request = $request; + 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(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', if($action[0] == '$') $action = $params[substr($action,1)]; diff --git a/core/model/DataObject.php b/core/model/DataObject.php index 89588aedd..21d697cec 100644 --- a/core/model/DataObject.php +++ b/core/model/DataObject.php @@ -616,9 +616,14 @@ class DataObject extends ViewableData implements DataObjectInterface { foreach($this->record as $fieldName => $fieldValue) { if(isset($this->changed[$fieldName]) && $this->changed[$fieldName] && $fieldType = $classSingleton->fieldExists($fieldName)) { $fieldObj = $this->obj($fieldName); - $fieldObj->setValue($this->record[$fieldName], $this->record); 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; } } else { - eval("\$items = array_merge((array){$class}::\$db, (array)\$items);"); + eval("\$items = array_merge((array)\$items, (array){$class}::\$db);"); } } @@ -1192,18 +1197,28 @@ class DataObject extends ViewableData implements DataObjectInterface { */ public function scaffoldFormFields() { $fields = new FieldSet(); - + $fields->push(new HeaderField($this->singular_name())); foreach($this->db() as $fieldName => $fieldType) { // @todo Pass localized title // commented out, to be less of a pain in the ass //$fields->addFieldToTab('Root.Main', $this->dbObject($fieldName)->scaffoldFormField()); $fields->push($this->dbObject($fieldName)->scaffoldFormField()); - } - - // @todo Add relation tabs - + } 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, @@ -1228,7 +1243,12 @@ class DataObject extends ViewableData implements DataObjectInterface { * @return FieldSet */ 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; } /** diff --git a/forms/ComplexTableField.php b/forms/ComplexTableField.php index 767e60fbe..d80b7171f 100755 --- a/forms/ComplexTableField.php +++ b/forms/ComplexTableField.php @@ -53,9 +53,6 @@ class ComplexTableField extends TableListField { */ protected $permissions = array( "add", - "edit", - "show", - "delete", //"export", ); @@ -114,6 +111,7 @@ class ComplexTableField extends TableListField { static $url_handlers = array( 'item/$ID' => 'handleItem', + '$Action!' => '$Action', ); function handleItem($request) { @@ -387,6 +385,124 @@ JS; function setTemplatePopup($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); } - /** - * 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); - } - /////////////////////////////////////////////////////////////////////////////////////////////////// /** @@ -505,89 +606,46 @@ class ComplexTableField_ItemRequest extends RequestHandlingData { */ function DetailForm($childID = null) { $childData = $this->obj(); - - // If the fieldset is passed, use it, else use the formfields returned - // from the object via a string method call. - 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())); - + $fields = $this->ctf->getFieldsFor($childData); + $validator = $this->ctf->getValidatorFor($childData); $readonly = ($this->methodName == "show"); - // if no custom validator is set, and there's on present on the object (e.g. Member), use it - if(!isset($this->ctf->detailFormValidator) && singleton($this->ctf->sourceClass())->hasMethod('getValidator')) { - $this->ctf->detailFormValidator = singleton($this->ctf->sourceClass())->getValidator(); - } - - $form = Object::create($this->ctf->popupClass,$this, "DetailForm", $detailFields, $this->ctf->detailFormValidator, $readonly, $childData); + $form = Object::create( + $this->ctf->popupClass, + $this, "DetailForm", + $fields, $validator, $readonly, $childData); - if (is_numeric($childData->ID)) { - if ($this->methodName == "show" || $this->methodName == "edit") { - $form->loadDataFrom($childData); - } - } - - if ($this->methodName == "show") { - $form->makeReadonly(); - } + $form->loadDataFrom($childData); + if ($readonly) $form->makeReadonly(); 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() { $link = $this->FormAction() . "&action_callfieldmethod&fieldName={$this->Name()}"; @@ -790,7 +848,6 @@ class ComplexTableField_Popup extends Form { * WARNING: DO NOT CHANGE THE ORDER OF THESE JS FILES * Some have special requirements. */ - Requirements::clear(); //Requirements::css('cms/css/layout.css'); Requirements::css('jsparty/tabstrip/tabstrip.css'); Requirements::css('sapphire/css/Form.css'); @@ -812,7 +869,7 @@ class ComplexTableField_Popup extends Form { Requirements::javascript("sapphire/javascript/ComplexTableField_popup.js"); if($this->dataObject->hasMethod('getRequirementsForPopup')) { - $this->data->getRequirementsForPopup(); + $this->dataObject->getRequirementsForPopup(); } $actions = new FieldSet(); @@ -833,31 +890,6 @@ class ComplexTableField_Popup extends Form { function 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(); - } - } - } + ?> diff --git a/forms/Form.php b/forms/Form.php index c7e7a68e4..6e907514f 100644 --- a/forms/Form.php +++ b/forms/Form.php @@ -182,11 +182,11 @@ class Form extends RequestHandlingData { // First, try a handler method on the controller 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 } else { - return $this->$funcName($vars, $this); + return $this->$funcName($vars, $this, $request); } } @@ -456,7 +456,6 @@ class Form extends RequestHandlingData { */ function setFormMethod($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 */ function FormAction() { - if($this->controller->hasMethod("FormObjectLink")) { + if ($this->formActionPath) { + return $this->formActionPath; + } elseif($this->controller->hasMethod("FormObjectLink")) { return $this->controller->FormObjectLink($this->name); - } else { - $link = $this->controller->Link(); - if(substr($link,-1) != '/') $link .= '/'; - return $link . $this->name; + } else { + return Controller::join_links($this->controller->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 */ 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) { $data['action_' . $action] = true; - $data['executeForm'] = $this->name; return Director::test($this->FormAction(), $data, Controller::curr()->getSession()); diff --git a/forms/FormField.php b/forms/FormField.php index e7e55ba01..9d550bf4a 100644 --- a/forms/FormField.php +++ b/forms/FormField.php @@ -465,16 +465,6 @@ HTML; user_error('FormField::setExtraClass() is deprecated. Use FormField::addExtraClass() instead.', E_USER_NOTICE); $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; - } - } ?> diff --git a/javascript/ComplexTableField.js b/javascript/ComplexTableField.js index 5646e51a2..f320bf146 100755 --- a/javascript/ComplexTableField.js +++ b/javascript/ComplexTableField.js @@ -72,8 +72,6 @@ ComplexTableField.prototype = { popupLink = _popupLink; table = _table; } else { - alert(this.innerHTML); - // if clicked item is an input-element, don't trigger popup var el = Event.element(e); var input = Event.findElement(e,"input"); diff --git a/search/SearchContext.php b/search/SearchContext.php index 452fe2551..b55bac43f 100644 --- a/search/SearchContext.php +++ b/search/SearchContext.php @@ -75,6 +75,12 @@ class SearchContext extends Object { 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() { return ($this->fields) ? $this->fields : singleton($this->modelClass)->scaffoldSearchFields(); } @@ -140,7 +146,7 @@ class SearchContext extends Object { } public function getFilters() { - return $this->fields; + return $this->fields; } public function setFilters($filters) { diff --git a/search/filters/ExactMatchSearchFilter.php b/search/filters/ExactMatchFilter.php similarity index 78% rename from search/filters/ExactMatchSearchFilter.php rename to search/filters/ExactMatchFilter.php index 5a6f3d50a..745491d89 100644 --- a/search/filters/ExactMatchSearchFilter.php +++ b/search/filters/ExactMatchFilter.php @@ -8,7 +8,7 @@ * @package sapphire * @subpackage search */ -class ExactMatchSearchFilter extends SearchFilter { +class ExactMatchFilter extends SearchFilter { } ?> \ No newline at end of file diff --git a/search/filters/FulltextSearchFilter.php b/search/filters/FulltextFilter.php similarity index 65% rename from search/filters/FulltextSearchFilter.php rename to search/filters/FulltextFilter.php index 2c057c078..6d67def6e 100644 --- a/search/filters/FulltextSearchFilter.php +++ b/search/filters/FulltextFilter.php @@ -5,7 +5,7 @@ * @package sapphire * @subpackage search */ -class FulltextSearchFilter extends SearchFilter { +class FulltextFilter extends SearchFilter { } ?> \ No newline at end of file diff --git a/search/filters/PartialMatchSearchFilter.php b/search/filters/PartialMatchFilter.php similarity index 70% rename from search/filters/PartialMatchSearchFilter.php rename to search/filters/PartialMatchFilter.php index 43947f73d..56ffdb261 100644 --- a/search/filters/PartialMatchSearchFilter.php +++ b/search/filters/PartialMatchFilter.php @@ -5,7 +5,7 @@ * @package sapphire * @subpackage search */ -class PartialMatchSearchFilter extends SearchFilter { +class PartialMatchFilter extends SearchFilter { } ?> \ No newline at end of file diff --git a/search/filters/SubstringMatchFilter.php b/search/filters/SubstringMatchFilter.php new file mode 100644 index 000000000..256debb84 --- /dev/null +++ b/search/filters/SubstringMatchFilter.php @@ -0,0 +1,13 @@ + \ No newline at end of file diff --git a/tests/DataObjectTest.php b/tests/DataObjectTest.php index ea3f9393d..e9b14825e 100644 --- a/tests/DataObjectTest.php +++ b/tests/DataObjectTest.php @@ -123,6 +123,20 @@ class DataObjectTest extends SapphireTest { $comment = DataObject::get_one('PageComment', '', true, 'Name DESC'); $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 diff --git a/tests/HTTPRequestTest.php b/tests/HTTPRequestTest.php new file mode 100644 index 000000000..dd8d6a436 --- /dev/null +++ b/tests/HTTPRequestTest.php @@ -0,0 +1,18 @@ + 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)); + } + +} \ No newline at end of file diff --git a/tests/SearchContextTest.php b/tests/SearchContextTest.php new file mode 100644 index 000000000..cc9f4627a --- /dev/null +++ b/tests/SearchContextTest.php @@ -0,0 +1,35 @@ +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" + ); + +} + +?> \ No newline at end of file diff --git a/tests/SearchContextTest.yml b/tests/SearchContextTest.yml new file mode 100644 index 000000000..34e73efc8 --- /dev/null +++ b/tests/SearchContextTest.yml @@ -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 + + \ No newline at end of file