(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@60208 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Ingo Schommer 2008-08-09 04:06:52 +00:00
parent b5776e0438
commit a599df309c
18 changed files with 493 additions and 98 deletions

View File

@ -28,6 +28,7 @@ Director::addRules(10, array(
'' => 'RootURLController', '' => 'RootURLController',
'sitemap.xml' => 'GoogleSitemap', 'sitemap.xml' => 'GoogleSitemap',
'api/v1' => 'RestfulServer', 'api/v1' => 'RestfulServer',
'dev' => 'DevelopmentAdmin'
)); ));
Director::addRules(1, array( Director::addRules(1, array(

View File

@ -169,7 +169,7 @@ class RestfulServer extends Controller {
* @param $includeHeader Include <?xml ...?> header (Default: true) * @param $includeHeader Include <?xml ...?> header (Default: true)
* @return String XML * @return String XML
*/ */
protected function dataObjectAsXML(DataObject $obj, $includeHeader = true) { public function dataObjectAsXML(DataObject $obj, $includeHeader = true) {
$className = $obj->class; $className = $obj->class;
$id = $obj->ID; $id = $obj->ID;
$objHref = Director::absoluteURL(self::$api_base . "$obj->class/$obj->ID"); $objHref = Director::absoluteURL(self::$api_base . "$obj->class/$obj->ID");
@ -177,7 +177,8 @@ class RestfulServer extends Controller {
$json = ""; $json = "";
if($includeHeader) $json .= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; if($includeHeader) $json .= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
$json .= "<$className href=\"$objHref.xml\">\n"; $json .= "<$className href=\"$objHref.xml\">\n";
foreach($obj->db() as $fieldName => $fieldType) { $dbFields = array_merge($obj->databaseFields(), array('ID'=>'Int'));
foreach($dbFields as $fieldName => $fieldType) {
if(is_object($obj->$fieldName)) { if(is_object($obj->$fieldName)) {
$json .= $obj->$fieldName->toXML(); $json .= $obj->$fieldName->toXML();
} else { } else {
@ -228,7 +229,7 @@ class RestfulServer extends Controller {
* @param DataObjectSet $set * @param DataObjectSet $set
* @return String XML * @return String XML
*/ */
protected function dataObjectSetAsXML(DataObjectSet $set) { public function dataObjectSetAsXML(DataObjectSet $set) {
$className = $set->class; $className = $set->class;
$xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<$className>\n"; $xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<$className>\n";
@ -248,12 +249,13 @@ class RestfulServer extends Controller {
* @param DataObject $obj * @param DataObject $obj
* @return String JSON * @return String JSON
*/ */
protected function dataObjectAsJSON(DataObject $obj) { public function dataObjectAsJSON(DataObject $obj) {
$className = $obj->class; $className = $obj->class;
$id = $obj->ID; $id = $obj->ID;
$json = "{\n className : \"$className\",\n"; $json = "{\n className : \"$className\",\n";
foreach($obj->db() as $fieldName => $fieldType) { $dbFields = array_merge($obj->databaseFields(), array('ID'=>'Int'));
foreach($dbFields as $fieldName => $fieldType) {
if(is_object($obj->$fieldName)) { if(is_object($obj->$fieldName)) {
$jsonParts[] = "$fieldName : " . $obj->$fieldName->toJSON(); $jsonParts[] = "$fieldName : " . $obj->$fieldName->toJSON();
} else { } else {
@ -302,7 +304,7 @@ class RestfulServer extends Controller {
* @param DataObjectSet $set * @param DataObjectSet $set
* @return String JSON * @return String JSON
*/ */
protected function dataObjectSetAsJSON(DataObjectSet $set) { public function dataObjectSetAsJSON(DataObjectSet $set) {
$jsonParts = array(); $jsonParts = array();
foreach($set as $item) { foreach($set as $item) {
if($item->canView()) $jsonParts[] = $this->dataObjectAsJSON($item); if($item->canView()) $jsonParts[] = $this->dataObjectAsJSON($item);

View File

@ -1158,33 +1158,31 @@ class DataObject extends ViewableData implements DataObjectInterface {
* @return SearchContext * @return SearchContext
*/ */
public function getDefaultSearchContext() { public function getDefaultSearchContext() {
$c = new SearchContext($this->class); return new SearchContext($this->class, $this->searchable_fields(), $this->defaultSearchFilters());
return $c;
} }
/** /**
* Determine which properties on the DataObject are * Determine which properties on the DataObject are
* searchable, and map them to their default {@link FormField} * searchable, and map them to their default {@link FormField}
* representations. Useful for scaffolding a searchform for {@link ModelAdmin}. * representations. Used for scaffolding a searchform for {@link ModelAdmin}.
* *
* @usedby {@link SearchContext} * @usedby {@link SearchContext}
* @return FieldSet * @return FieldSet
*/ */
public function scaffoldSearchFields() { public function scaffoldSearchFields() {
$fields = new FieldSet(); $fields = new FieldSet();
foreach($this->searchableFields() as $fieldName => $fieldType) { foreach($this->searchable_fields() as $fieldName => $fieldType) {
// @todo Pass localized title // @todo Pass localized title
$fields->push($this->dbObject($fieldName)->scaffoldSearchField()); $fields->push($this->dbObject($fieldName)->scaffoldSearchField());
} }
$extras = $this->invokeWithExtensions('extraSearchFields'); /*$extras = $this->invokeWithExtensions('extraSearchFields');
if ($extras) { if ($extras) {
foreach($extras as $result) { foreach($extras as $result) {
foreach($result as $fieldName => $fieldType) { foreach($result as $fieldName => $fieldType) {
$fields->push(new $fieldType($fieldName)); $fields->push(new $fieldType($fieldName));
} }
} }
} }*/
return $fields; return $fields;
} }
@ -1203,6 +1201,13 @@ class DataObject extends ViewableData implements DataObjectInterface {
// 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());
}
foreach($this->has_one() as $relationship => $component) {
$model = singleton($component);
$records = DataObject::get($component);
$collect = ($model->hasMethod('customSelectOption')) ? 'customSelectOption' : current($model->summary_fields());
$options = $records->filter_map('ID', $collect);
$fields->push(new DropdownField($relationship.'ID', $relationship, $options));
} }
return $fields; return $fields;
} }
@ -1212,13 +1217,11 @@ class DataObject extends ViewableData implements DataObjectInterface {
*/ */
protected function addScaffoldRelationFields($fieldSet) { protected function addScaffoldRelationFields($fieldSet) {
foreach($this->has_many() as $relationship => $component) { foreach($this->has_many() as $relationship => $component) {
$relationshipFields = array_keys($this->searchableFields()); $relationshipFields = array_keys($this->searchable_fields());
$fieldSet->push(new ComplexTableField($this, $relationship, $component, $relationshipFields)); $fieldSet->push(new ComplexTableField($this, $relationship, $component, $relationshipFields));
} }
return $fieldSet; return $fieldSet;
} }
/** /**
* Centerpiece of every data administration interface in Silverstripe, * Centerpiece of every data administration interface in Silverstripe,
@ -2059,7 +2062,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
* but still needs to know the properties of its parent. This should be merged into databaseFields or * but still needs to know the properties of its parent. This should be merged into databaseFields or
* customDatabaseFields. * customDatabaseFields.
* *
* @todo integrate with pre-existing crap * @todo review whether this is still needed after recent API changes
*/ */
public function inheritedDatabaseFields() { public function inheritedDatabaseFields() {
$fields = array(); $fields = array();
@ -2073,32 +2076,69 @@ class DataObject extends ViewableData implements DataObjectInterface {
/** /**
* Get the default searchable fields for this object, * Get the default searchable fields for this object,
* excluding any fields that are specifically overriden * as defined in the $searchable_fields list. If searchable
* in the data object itself. * fields are not defined on the data object, uses a default
* selection of summary fields.
* *
* @todo rename $searchable to $excluded * @return array
* @todo overcomplicated, should be simpler way of looking up whether specific fields are supposed to be searchable or not
*/ */
public function searchableFields() { public function searchable_fields() {
$parents = ClassInfo::dataClassesFor($this); $fields = $this->stat('searchable_fields');
$fields = array(); if (!$fields) {
$searchable = array(); $fields = array_fill_keys($this->summary_fields(), 'TextField');
foreach($parents as $class) { }
$fields = array_merge($fields, singleton($class)->stat('db')); return $fields;
$obj = singleton($class); }
$results = $obj->invokeWithExtensions('excludeFromSearch');
if ($results) { /**
foreach($results as $result) { * Get the default summary fields for this object.
if (is_array($result)) { *
$searchable = array_merge($searchable, $result); * @todo use the translation apparatus to return a default field selection for the language
*
* @return array
*/
public function summary_fields() {
$fields = $this->stat('summary_fields');
if (!$fields) {
$fields = array();
if ($this->hasField('Name')) $fields[] = 'Name';
if ($this->hasField('Title')) $fields[] = 'Title';
if ($this->hasField('Description')) $fields[] = 'Description';
if ($this->hasField('Firstname')) $fields[] = 'Firstname';
}
return $fields;
}
/**
* Defines a default list of filters for the search context.
*
* If a filter class mapping is defined on the data object,
* it is constructed here. Otherwise, the default filter specified in
* {@link DBField} is used.
*
* @todo error handling/type checking for valid FormField and SearchFilter subclasses?
*
* @return array
*/
public function defaultSearchFilters() {
$filters = array();
foreach($this->searchable_fields() as $name => $type) {
if (is_int($name)) {
$filters[$type] = $this->dbObject($type)->defaultSearchFilter();
} else {
if (is_array($type)) {
$filter = current($type);
$filters[$name] = new $filter();
} else {
if (is_subclass_of($type, 'SearchFilter')) {
$filters[$name] = new $type($name);
} else {
$filters[$name] = $this->dbObject($name)->defaultSearchFilter();
} }
} }
} }
} }
foreach($searchable as $field) { return $filters;
unset($fields[$field]);
}
return $fields;
} }
/** /**
@ -2126,6 +2166,9 @@ class DataObject extends ViewableData implements DataObjectInterface {
return self::$context_obj; return self::$context_obj;
} }
/**
* @ignore
*/
protected static $context_obj = null; protected static $context_obj = null;
@ -2231,6 +2274,44 @@ class DataObject extends ViewableData implements DataObjectInterface {
*/ */
public static $default_sort = null; public static $default_sort = null;
/**
* Default list of fields that can be scaffolded by the ModelAdmin
* search interface.
*
* Defining a basic set of searchable fields:
* <code>
* static $searchable_fields = array("Name", "Email");
* </code>
*
* Overriding the default form fields, with a custom defined field:
* <code>
* static $searchable_fields = array(
* "Name" => "TextField"
* );
* </code>
*
* Overriding the default filter, with a custom defined filter:
* <code>
* static $searchable_fields = array(
* "Name" => "PartialMatchFilter"
* );
* </code>
*
* Overriding the default form field and filter:
* <code>
* static $searchable_fields = array(
* "Name" => array("TextField" => "PartialMatchFilter")
* );
* </code>
*/
public static $searchable_fields = null;
/**
* Provides a default list of fields to be used by a 'summary'
* view of this object.
*/
public static $summary_fields = null;
} }
?> ?>

View File

@ -596,6 +596,21 @@ class DataObjectSet extends ViewableData implements IteratorAggregate {
} }
return $map; return $map;
} }
/**
* Temporary filter method for filtering a list based on multiple fields of the DataObject.
*
* Question: should any args be passed to the filter function?
*
* @todo deprecate toDropdownMap() and map_multiple(), rename this method to map()
*/
public function filter_map($key, $value) {
$map = array();
foreach($this->items as $object) {
$map[$object->$key] = ($object->hasMethod($value)) ? $object->$value() : $object->$value;
}
return $map;
}
/** /**
* Find an item in this list where the field $key is equal to $value * Find an item in this list where the field $key is equal to $value

View File

@ -96,7 +96,7 @@ class ErrorPage extends Page {
// Run the page // Run the page
Requirements::clear(); Requirements::clear();
$controller = new ErrorPage_Controller($this); $controller = new ErrorPage_Controller($this);
$errorContent = $controller->run( array() )->getBody(); $errorContent = $controller->handleRequest(new HTTPRequest('GET',''))->getBody();
if(!file_exists("../assets")) { if(!file_exists("../assets")) {
mkdir("../assets", 02775); mkdir("../assets", 02775);

View File

@ -60,6 +60,12 @@ class SQLQuery extends Object {
*/ */
public $delete; public $delete;
/**
* The logical connective used to join WHERE clauses. Defaults to AND.
* @var string
*/
private $connective = 'AND';
/** /**
* Construct a new SQLQuery. * Construct a new SQLQuery.
* @param array $select An array of fields to select. * @param array $select An array of fields to select.
@ -81,6 +87,20 @@ class SQLQuery extends Object {
parent::__construct(); parent::__construct();
} }
/**
* Use the disjunctive operator 'OR' to join filter expressions in the WHERE clause.
*/
public function useDisjunction() {
$this->connective = 'OR';
}
/**
* Use the conjunctive operator 'AND' to join filter expressions in the WHERE clause.
*/
public function useConjunction() {
$this->connective = 'AND';
}
/** /**
* Swap the use of one table with another. * Swap the use of one table with another.
@ -121,6 +141,15 @@ class SQLQuery extends Object {
} }
} }
/**
* Return an SQL WHERE clause to filter a SELECT query.
*
* @return string
*/
function getFilter() {
return implode(") {$this->connective} (" , $this->where);
}
/** /**
* Generate the SQL statement for this query. * Generate the SQL statement for this query.
* @return string * @return string
@ -134,7 +163,7 @@ class SQLQuery extends Object {
} }
$text .= " FROM " . implode(" ", $this->from); $text .= " FROM " . implode(" ", $this->from);
if($this->where) $text .= " WHERE (" . implode(") AND (" , $this->where) . ")"; if($this->where) $text .= " WHERE (" . $this->getFilter(). ")";
if($this->groupby) $text .= " GROUP BY " . implode(", ", $this->groupby); if($this->groupby) $text .= " GROUP BY " . implode(", ", $this->groupby);
if($this->having) $text .= " HAVING ( " . implode(" ) AND ( ", $this->having) . " )"; if($this->having) $text .= " HAVING ( " . implode(" ) AND ( ", $this->having) . " )";
if($this->orderby) $text .= " ORDER BY " . $this->orderby; if($this->orderby) $text .= " ORDER BY " . $this->orderby;

View File

@ -222,7 +222,7 @@ abstract class DBField extends ViewableData {
* @return SearchFilter * @return SearchFilter
*/ */
public function defaultSearchFilter() { public function defaultSearchFilter() {
return new ExactMatchSearchFilter(); return new ExactMatchFilter($this->name);
} }
/** /**

View File

@ -12,7 +12,8 @@ class DevelopmentAdmin extends Controller {
static $url_handlers = array( static $url_handlers = array(
'' => 'index', '' => 'index',
'$Action' => '$Action' '$Action' => '$Action',
'$Action//$Action/$ID' => 'handleAction',
); );
function index() { function index() {
@ -31,16 +32,14 @@ HTML;
$renderer->writeFooter(); $renderer->writeFooter();
} }
function tests() { function tests($request) {
if(isset($this->urlParams['NestedAction'])) { $controller = new TestRunner();
Director::redirect("TestRunner/only/" . $this->urlParams['NestedAction']); return $controller->handleRequest($request);
} else {
Director::redirect("TestRunner/");
}
} }
function tasks($request) { function tasks() {
return new TaskRunner(); $controller = new TaskRunner();
return $controller->handleRequest($request);
} }
} }

View File

@ -30,6 +30,11 @@ class TestRunner extends Controller {
/** @ignore */ /** @ignore */
private static $default_reporter; private static $default_reporter;
static $url_handlers = array(
'' => 'index',
'$TestCase' => 'only'
);
/** /**
* Override the default reporter with a custom configured subclass. * Override the default reporter with a custom configured subclass.
* *
@ -76,15 +81,13 @@ class TestRunner extends Controller {
/** /**
* Run only a single test class * Run only a single test class
*/ */
function only() { function only($request) {
$className = $this->urlParams['ID']; $className = $request->param('TestCase');
if(class_exists($className)) { if(class_exists($className)) {
$this->runTests(array($className)); $this->runTests(array($className));
} else { } else {
echo "Class '$className' not found"; echo "Class '$className' not found";
} }
} }
function runTests($classList, $coverage = false) { function runTests($classList, $coverage = false) {

View File

@ -56,17 +56,7 @@ class SearchContext extends Object {
* @var array * @var array
protected $params; protected $params;
*/ */
/**
* Require either all search filters to be evaluated to true,
* or just a single one.
*
* @todo Not Implemented
* @var string
*/
protected $booleanSearchType = 'AND';
function __construct($modelClass, $fields = null, $filters = null) { function __construct($modelClass, $fields = null, $filters = null) {
$this->modelClass = $modelClass; $this->modelClass = $modelClass;
$this->fields = $fields; $this->fields = $fields;
@ -74,7 +64,7 @@ class SearchContext extends Object {
parent::__construct(); parent::__construct();
} }
/** /**
* Returns scaffolded search fields for UI. * Returns scaffolded search fields for UI.
* *
@ -82,43 +72,53 @@ class SearchContext extends Object {
* @return FieldSet * @return FieldSet
*/ */
public function getSearchFields() { public function getSearchFields() {
return ($this->fields) ? $this->fields : singleton($this->modelClass)->scaffoldSearchFields(); // $this->fields is causing weirdness, so we ignore for now, using the default scaffolding
//return ($this->fields) ? $this->fields : singleton($this->modelClass)->scaffoldSearchFields();
return singleton($this->modelClass)->scaffoldSearchFields();
} }
/** /**
* Get the query object augumented with all clauses from * Returns a SQL object representing the search context for the given
* the connected {@link SearchFilter}s * list of query parameters.
*
* @todo query generation
* *
* @param array $searchParams * @param array $searchParams
* @return SQLQuery * @return SQLQuery
*/ */
public function getQuery($searchParams) { public function getQuery($searchParams, $start = false, $limit = false) {
$q = new SQLQuery("*", $this->modelClass); $model = singleton($this->modelClass);
$this->processFilters($q); $fields = array_keys($model->db());
return $q; $query = new SQLQuery($fields, $this->modelClass);
foreach($searchParams as $key => $value) {
$filter = $this->getFilter($key);
if ($filter) {
$query->where[] = $filter->apply($value);
}
}
return $query;
} }
/** /**
* Light wrapper around {@link getQuery()}. * Returns a result set from the given search parameters.
* *
* @todo rearrange start and limit params to reflect DataObject
*
* @param array $searchParams * @param array $searchParams
* @param int $start * @param int $start
* @param int $limit * @param int $limit
* @return DataObjectSet * @return DataObjectSet
*/ */
public function getResults($searchParams, $start = false, $limit = false) { public function getResults($searchParams, $start = false, $limit = false) {
$q = $this->getQuery($searchParams); $query = $this->getQuery($searchParams, $start, $limit);
//$q->limit = $start ? "$start, $limit" : $limit; //
$output = new DataObjectSet(); // use if a raw SQL query is needed
foreach($q->execute() as $row) { //$results = new DataObjectSet();
$className = $row['RecordClassName']; //foreach($query->execute() as $row) {
$output->push(new $className($row)); // $className = $row['ClassName'];
} // $results->push(new $className($row));
//}
// do the setting of start/limit on the dataobjectset //return $results;
return $output; //
return DataObject::get($this->modelClass, $query->getFilter(), "", "", $limit);
} }
/** /**
@ -128,14 +128,25 @@ class SearchContext extends Object {
* @param array $searchFilters * @param array $searchFilters
* @param SQLQuery $query * @param SQLQuery $query
*/ */
protected function processFilters($searchFilters, SQLQuery &$query) { protected function processFilters(SQLQuery $query, $searchParams) {
foreach($this->filters as $filter) { $conditions = array();
$filter->updateQuery($searchFilters, $tableName, $query); foreach($this->filters as $field => $filter) {
if (strstr($field, '.')) {
$path = explode('.', $field);
} else {
$conditions[] = $filter->apply($searchParams[$field]);
}
} }
$query->where = $conditions;
} }
// ############ Getters/Setters ########### public function getFilter($name) {
if (isset($this->filters[$name])) {
return $this->filters[$name];
} else {
return null;
}
}
public function getFields() { public function getFields() {
return $this->fields; return $this->fields;
@ -146,7 +157,7 @@ class SearchContext extends Object {
} }
public function getFilters() { public function getFilters() {
return $this->fields; return $this->filters;
} }
public function setFilters($filters) { public function setFilters($filters) {

View File

@ -10,5 +10,14 @@
*/ */
class ExactMatchFilter extends SearchFilter { class ExactMatchFilter extends SearchFilter {
/**
* Applies an exact match (equals) on a field value.
*
* @return unknown
*/
public function apply($value) {
return "{$this->name}='$value'";
}
} }
?> ?>

View File

@ -6,6 +6,10 @@
* @subpackage search * @subpackage search
*/ */
class FulltextFilter extends SearchFilter { class FulltextFilter extends SearchFilter {
public function apply($value) {
return "";
}
} }
?> ?>

View File

@ -0,0 +1,21 @@
<?php
/**
* @package search
* @subpackage filters
*/
/**
* Matches on rows where the field is not equal to the given value.
*
* @package search
* @subpackage filters
*/
class NegationFilter extends SearchFilter {
public function apply($value) {
return "{$this->name} != '$value'";
}
}
?>

View File

@ -7,5 +7,9 @@
*/ */
class PartialMatchFilter extends SearchFilter { class PartialMatchFilter extends SearchFilter {
public function apply($value) {
return "{$this->name} LIKE '%$value%'";
}
} }
?> ?>

View File

@ -7,5 +7,13 @@
*/ */
abstract class SearchFilter extends Object { abstract class SearchFilter extends Object {
protected $name;
function __construct($name) {
$this->name = $name;
}
abstract public function apply($value);
} }
?> ?>

View File

@ -6,7 +6,10 @@
* @subpackage search * @subpackage search
*/ */
class SubstringMatchFilter extends SearchFilter { class SubstringMatchFilter extends SearchFilter {
public function apply($value) {
return "";
}
} }

View File

@ -4,7 +4,7 @@ class SearchContextTest extends SapphireTest {
static $fixture_file = 'sapphire/tests/SearchContextTest.yml'; static $fixture_file = 'sapphire/tests/SearchContextTest.yml';
function testResultSetFilterReturnsExpectedCount() { function testResultSetFilterReturnsExpectedCount() {
$person = singleton('PersonBubble'); $person = singleton('SearchContextTest_Person');
$context = $person->getDefaultSearchContext(); $context = $person->getDefaultSearchContext();
$results = $context->getResultSet(array('Name'=>'')); $results = $context->getResultSet(array('Name'=>''));
@ -15,13 +15,90 @@ class SearchContextTest extends SapphireTest {
$results = $context->getResultSet(array('EyeColor'=>'green', 'HairColor'=>'black')); $results = $context->getResultSet(array('EyeColor'=>'green', 'HairColor'=>'black'));
$this->assertEquals(1, $results->Count()); $this->assertEquals(1, $results->Count());
} }
//function function testSummaryIncludesDefaultFieldsIfNotDefined() {
$person = singleton('SearchContextTest_Person');
$this->assertContains('Name', $person->summary_fields());
$book = singleton('SearchContextTest_Book');
$this->assertContains('Title', $book->summary_fields());
}
function testAccessDefinedSummaryFields() {
$company = singleton('SearchContextTest_Company');
$this->assertContains('Industry', $company->summary_fields());
}
function testExactMatchUsedByDefaultWhenNotExplicitlySet() {
$person = singleton('SearchContextTest_Person');
$context = $person->getDefaultSearchContext();
$this->assertEquals(
array(
"Name" => new ExactMatchFilter("Name"),
"HairColor" => new ExactMatchFilter("HairColor"),
"EyeColor" => new ExactMatchFilter("EyeColor")
),
$context->getFilters()
);
}
function testDefaultFiltersDefinedWhenNotSetInDataObject() {
$book = singleton('SearchContextTest_Book');
$context = $book->getDefaultSearchContext();
$this->assertEquals(
array(
"Title" => new ExactMatchFilter("Title")
),
$context->getFilters()
);
}
function testUserDefinedFiltersAppearInSearchContext() {
//$company = singleton('SearchContextTest_Company');
//$context = $company->getDefaultSearchContext();
/*$this->assertEquals(
array(
"Name" => new PartialMatchFilter("Name"),
"Industry" => new ExactMatchFilter("Industry"),
"AnnualProfit" => new PartialMatchFilter("AnnualProfit")
),
$context->getFilters()
);*/
}
function testRelationshipObjectsLinkedInSearch() {
//$project = singleton('SearchContextTest_Project');
//$context = $project->getDefaultSearchContext();
//$query = array("Name"=>"Blog Website");
//$results = $context->getQuery($query);
}
function testCanGenerateQueryUsingAllFilterTypes() {
$all = singleton("SearchContextTest_AllFilterTypes");
$context = $all->getDefaultSearchContext();
$params = array(
"ExactMatch" => "Match Me Exactly",
"PartialMatch" => "partially",
"Negation" => "undisclosed"
);
$results = $context->getResults($params);
$this->assertEquals(1, $results->Count());
$this->assertEquals("Filtered value", $results->First()->HiddenValue);
}
} }
class PersonBubble extends DataObject { class SearchContextTest_Person extends DataObject implements TestOnly {
static $db = array( static $db = array(
"Name" => "Text", "Name" => "Text",
@ -30,6 +107,102 @@ class PersonBubble extends DataObject {
"EyeColor" => "Text" "EyeColor" => "Text"
); );
static $searchable_fields = array(
"Name", "HairColor", "EyeColor"
);
}
class SearchContextTest_Book extends DataObject implements TestOnly {
static $db = array(
"Title" => "Text",
"Summary" => "Text"
);
}
class SearchContextTest_Company extends DataObject implements TestOnly {
static $db = array(
"Name" => "Text",
"Industry" => "Text",
"AnnualProfit" => "Int"
);
static $summary_fields = array(
"Industry"
);
static $searchable_fields = array(
"Name" => "PartialMatchFilter",
"Industry" => "TextareaField",
"AnnualProfit" => array("NumericField" => "PartialMatchFilter")
);
}
class SearchContextTest_Project extends DataObject implements TestOnly {
static $db = array(
"Name" => "Text"
);
static $has_one = array(
"Deadline" => "SearchContextTest_Deadline"
);
static $has_many = array(
"Actions" => "SearchContextTest_Action"
);
static $searchable_fields = array(
"Name" => "PartialMatchFilter",
"Actions.SolutionArea" => "ExactMatchFilter"
);
}
class SearchContextTest_Deadline extends DataObject implements TestOnly {
static $db = array(
"CompletionDate" => "Datetime"
);
static $has_one = array(
"Project" => "SearchContextTest_Project"
);
}
class SearchContextTest_Action extends DataObject implements TestOnly {
static $db = array(
"Description" => "Text",
"SolutionArea" => "Text"
);
static $has_one = array(
"Project" => "SearchContextTest_Project"
);
}
class SearchContextTest_AllFilterTypes extends DataObject implements TestOnly {
static $db = array(
"ExactMatch" => "Text",
"PartialMatch" => "Text",
"Negation" => "Text",
"HiddenValue" => "Text"
);
static $searchable_fields = array(
"ExactMatch" => "ExactMatchFilter",
"PartialMatch" => "PartialMatchFilter",
"Negation" => "NegationFilter"
);
} }
?> ?>

View File

@ -1,4 +1,4 @@
PersonBubble: SearchContextTest_Person:
person1: person1:
Name: James Name: James
Email: james@example.com Email: james@example.com
@ -25,4 +25,36 @@ PersonBubble:
HairColor: black HairColor: black
EyeColor: green EyeColor: green
SearchContextTest_Deadline:
deadline1:
CompletionDate: 2008-05-29 09:00:00
deadline2:
CompletionDate: 2008-05-29 09:00:00
SearchContextTest_Action:
action1:
Description: Get search context working
SolutionArea: backend
action2:
Description: Get relationship editor working
SolutionArea: frontend
action3:
Description: Get RSS feeds working
SolutionArea: technical
SearchContextTest_Project:
project1:
Name: CRM Application
Deadline: =>SearchContextTest_Deadline.deadline1
Actions: =>SearchContextTest_Action.action1,=>SearchContextTest_Action.action2
project2:
Name: Blog Website
Deadline: =>SearchContextTest_Deadline.deadline2
Actions: =>SearchContextTest_Action.action3
SearchContextTest_AllFilterTypes:
filter1:
ExactMatch: Match me exactly
PartialMatch: Match me partially
Negation: Shouldnt match me
HiddenValue: Filtered value