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@60232 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
parent
4ec93162a0
commit
75f2cf2654
@ -27,6 +27,15 @@ abstract class DataFormatter extends Object {
|
||||
*/
|
||||
public $relationDepth = 1;
|
||||
|
||||
/**
|
||||
* Allows overriding of the fields which are rendered for the
|
||||
* processed dataobjects. By default, this includes all
|
||||
* fields in {@link DataObject::inheritedDatabaseFields()}.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $customFields = null;
|
||||
|
||||
/**
|
||||
* Get a DataFormatter object suitable for handling the given file extension
|
||||
*/
|
||||
@ -46,6 +55,50 @@ abstract class DataFormatter extends Object {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $fields
|
||||
*/
|
||||
public function setCustomFields($fields) {
|
||||
$this->customFields = $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getCustomFields() {
|
||||
return $this->customFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all fields on the object which should be shown
|
||||
* in the output. Can be customised through {@link self::setCustomFields()}.
|
||||
*
|
||||
* @todo Allow for custom getters on the processed object (currently filtered through inheritedDatabaseFields)
|
||||
* @todo Field level permission checks
|
||||
*
|
||||
* @param DataObject $obj
|
||||
* @return array
|
||||
*/
|
||||
protected function getFieldsForObj($obj) {
|
||||
$dbFields = array();
|
||||
|
||||
// if custom fields are specified, only select these
|
||||
if($this->customFields) {
|
||||
foreach($this->customFields as $fieldName) {
|
||||
// @todo Possible security risk by making methods accessible - implement field-level security
|
||||
if($obj->hasField($fieldName) || $obj->hasMethod("get{$fieldName}")) $dbFields[$fieldName] = $fieldName;
|
||||
}
|
||||
} else {
|
||||
// by default, all database fields are selected
|
||||
$dbFields = $obj->inheritedDatabaseFields();
|
||||
}
|
||||
|
||||
// add default required fields
|
||||
$dbFields = array_merge($dbFields, array('ID'=>'Int'));
|
||||
|
||||
return $dbFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of the extensions that this data formatter supports
|
||||
*/
|
||||
@ -54,13 +107,11 @@ abstract class DataFormatter extends Object {
|
||||
|
||||
/**
|
||||
* Convert a single data object to this format. Return a string.
|
||||
* @todo Add parameters for things like selecting output columns
|
||||
*/
|
||||
abstract function convertDataObject(DataObjectInterface $do);
|
||||
|
||||
/**
|
||||
* Convert a data object set to this format. Return a string.
|
||||
* @todo Add parameters for things like selecting output columns
|
||||
*/
|
||||
abstract function convertDataObjectSet(DataObjectSet $set);
|
||||
|
||||
|
@ -22,8 +22,7 @@ class JSONDataFormatter extends DataFormatter {
|
||||
$id = $obj->ID;
|
||||
|
||||
$json = "{\n className : \"$className\",\n";
|
||||
$dbFields = array_merge($obj->inheritedDatabaseFields(), array('ID'=>'Int'));
|
||||
foreach($dbFields as $fieldName => $fieldType) {
|
||||
foreach($this->getFieldsForObj($obj) as $fieldName => $fieldType) {
|
||||
if(is_object($obj->$fieldName)) {
|
||||
$jsonParts[] = "$fieldName : " . $obj->$fieldName->toJSON();
|
||||
} else {
|
||||
|
@ -23,11 +23,19 @@
|
||||
* - DELETE /api/v1/(ClassName)/(ID)/(Relation)/(ForeignID) - remove the relationship between two database records, but don't actually delete the foreign object (NOT IMPLEMENTED YET)
|
||||
*
|
||||
* - POST /api/v1/(ClassName)/(ID)/(MethodName) - executes a method on the given object (e.g, publish)
|
||||
*
|
||||
* @todo Finish RestfulServer_Item and RestfulServer_List implementation and re-enable $url_handlers
|
||||
*
|
||||
* @package sapphire
|
||||
* @subpackage api
|
||||
* You can trigger searches based on the fields specified on {@link DataObject::searchable_fields} and passed
|
||||
* through {@link DataObject::getDefaultSearchContext()}. Just add a key-value pair with the search-term
|
||||
* to the url, e.g. /api/v1/(ClassName)/?Title=mytitle
|
||||
*
|
||||
* Other url-modifiers:
|
||||
* - &limit=<numeric>: Limit the result set
|
||||
* - &relationdepth=<numeric>: Displays links to existing has-one and has-many relationships to a certain depth (Default: 1)
|
||||
* - &fields=<string>: Comma-separated list of fields on the output object (defaults to all database-columns)
|
||||
*
|
||||
* @todo Finish RestfulServer_Item and RestfulServer_List implementation and re-enable $url_handlers
|
||||
* @todo Make SearchContext specification customizeable for each class
|
||||
* @todo Allow for range-searches (e.g. on Created column)
|
||||
*/
|
||||
class RestfulServer extends Controller {
|
||||
static $url_handlers = array(
|
||||
@ -74,6 +82,9 @@ class RestfulServer extends Controller {
|
||||
|
||||
if(!$extension) $extension = "xml";
|
||||
$formatter = DataFormatter::for_extension($extension); //$this->dataFormatterFromMime($contentType);
|
||||
if($customFields = $this->request->getVar('fields')) $formatter->setCustomFields(explode(',',$customFields));
|
||||
$relationDepth = $this->request->getVar('relationdepth');
|
||||
if(is_numeric($relationDepth)) $formatter->relationDepth = (int)$relationDepth;
|
||||
|
||||
switch($requestMethod) {
|
||||
case 'GET':
|
||||
@ -169,7 +180,7 @@ class RestfulServer extends Controller {
|
||||
// show empty serialized result when no records are present
|
||||
if(!$obj) $obj = new DataObjectSet();
|
||||
}
|
||||
|
||||
|
||||
if($obj instanceof DataObjectSet) return $formatter->convertDataObjectSet($obj);
|
||||
else return $formatter->convertDataObject($obj);
|
||||
}
|
||||
|
@ -29,8 +29,7 @@ class XMLDataFormatter extends DataFormatter {
|
||||
$objHref = Director::absoluteURL(self::$api_base . "$obj->class/$obj->ID");
|
||||
|
||||
$json = "<$className href=\"$objHref.xml\">\n";
|
||||
$dbFields = array_merge($obj->inheritedDatabaseFields(), array('ID'=>'Int'));
|
||||
foreach($dbFields as $fieldName => $fieldType) {
|
||||
foreach($this->getFieldsForObj($obj) as $fieldName => $fieldType) {
|
||||
if(is_object($obj->$fieldName)) {
|
||||
$json .= $obj->$fieldName->toXML();
|
||||
} else {
|
||||
|
@ -1261,7 +1261,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
|
||||
// We need to find the inverse component name
|
||||
$otherManyMany = singleton($candidate)->stat('many_many');
|
||||
if(!$otherManyMany) {
|
||||
Debug::message("Inverse component of $candidate not found");
|
||||
user_error("Inverse component of $candidate not found ({$this->class})", E_USER_ERROR);
|
||||
}
|
||||
|
||||
foreach($otherManyMany as $inverseComponentName => $candidateClass) {
|
||||
@ -1641,6 +1641,40 @@ class DataObject extends ViewableData implements DataObjectInterface {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Member $member
|
||||
* @return boolean
|
||||
*/
|
||||
public function canView($member = null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Member $member
|
||||
* @return boolean
|
||||
*/
|
||||
public function canEdit($member = null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Member $member
|
||||
* @return boolean
|
||||
*/
|
||||
public function canDelete($member = null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Should canCreate be a static method?
|
||||
*
|
||||
* @param Member $member
|
||||
* @return boolean
|
||||
*/
|
||||
public function canCreate($member = null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Debugging used by Debug::show()
|
||||
*
|
||||
|
@ -612,6 +612,7 @@ class SiteTree extends DataObject {
|
||||
* It can be overloaded to customise the security model for an
|
||||
* application.
|
||||
*
|
||||
* @param Member $member
|
||||
* @return boolean True if the current user can delete this page.
|
||||
*/
|
||||
public function canDelete($member = null) {
|
||||
@ -637,6 +638,7 @@ class SiteTree extends DataObject {
|
||||
* It can be overloaded to customise the security model for an
|
||||
* application.
|
||||
*
|
||||
* @param Member $member
|
||||
* @return boolean True if the current user can create pages on this
|
||||
* class.
|
||||
*/
|
||||
@ -663,6 +665,7 @@ class SiteTree extends DataObject {
|
||||
* It can be overloaded to customise the security model for an
|
||||
* application.
|
||||
*
|
||||
* @param Member $member
|
||||
* @return boolean True if the current user can edit this page.
|
||||
*/
|
||||
public function canEdit($member = null) {
|
||||
@ -693,6 +696,7 @@ class SiteTree extends DataObject {
|
||||
* It can be overloaded to customise the security model for an
|
||||
* application.
|
||||
*
|
||||
* @param Member $member
|
||||
* @return boolean True if the current user can publish this page.
|
||||
*/
|
||||
public function canPublish($member = null) {
|
||||
|
17
core/model/fieldtypes/USZipcode.php
Normal file
17
core/model/fieldtypes/USZipcode.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
/**
|
||||
* @package sapphire
|
||||
* @subpackage model
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a US zip code.
|
||||
*
|
||||
* Can either be 5 or 9 digits.
|
||||
*/
|
||||
class USZipcode extends Varchar {
|
||||
|
||||
|
||||
}
|
||||
|
||||
?>
|
@ -107,10 +107,11 @@ class Debug {
|
||||
* @param mixed $val
|
||||
*/
|
||||
static function dump($val) {
|
||||
echo '<pre style="background-color:#ccc;padding:5px;">';
|
||||
echo '<pre style="background-color:#ccc;padding:5px;font-size:14px;line-height:18px;">';
|
||||
$caller = Debug::caller();
|
||||
echo "<span style=\"font-size: 60%\">Line $caller[line] of " . basename($caller['file']) . "</span>\n";
|
||||
print_r($val);
|
||||
echo "<span style=\"font-size: 12px;color:#666;\">Line $caller[line] of " . basename($caller['file']) . ":</span>\n";
|
||||
if (is_string($val)) print_r(wordwrap($val, 100));
|
||||
else print_r($val);
|
||||
echo '</pre>';
|
||||
}
|
||||
|
||||
@ -260,7 +261,7 @@ class Debug {
|
||||
echo "ERROR:Error $errno: $errstr\n At l$errline in $errfile\n";
|
||||
Debug::backtrace();
|
||||
} else {
|
||||
$reporter = new DebugReporter();
|
||||
$reporter = new SapphireDebugReporter();
|
||||
$reporter->writeHeader();
|
||||
echo '<div class="info">';
|
||||
echo "<h1>" . strip_tags($errstr) . "</h1>";
|
||||
@ -588,8 +589,9 @@ class SapphireDebugReporter implements DebugReporter {
|
||||
echo 'pre { margin-left:18px; }';
|
||||
echo 'pre span { color:#999;}';
|
||||
echo 'pre .error { color:#f00; }';
|
||||
echo '.pass { padding:2px 20px 2px 40px; color:#006600; background:#E2F9E3 url('.Director::absoluteBaseURL() .'cms/images/alert-good.gif) no-repeat scroll 7px 50%; border:1px solid #8DD38D; }';
|
||||
echo '.fail { padding:2px 20px 2px 40px; color:#C80700; background:#FFE9E9 url('.Director::absoluteBaseURL() .'cms/images/alert-bad.gif) no-repeat scroll 7px 50%; }';
|
||||
echo '.pass { margin-top:18px; padding:2px 20px 2px 40px; color:#006600; background:#E2F9E3 url('.Director::absoluteBaseURL() .'cms/images/alert-good.gif) no-repeat scroll 7px 50%; border:1px solid #8DD38D; }';
|
||||
echo '.fail { margin-top:18px; padding:2px 20px 2px 40px; color:#C80700; background:#FFE9E9 url('.Director::absoluteBaseURL() .'cms/images/alert-bad.gif) no-repeat scroll 7px 50%; border:1px solid #C80700; }';
|
||||
echo '.failure span { color:#C80700; font-weight:bold; }';
|
||||
echo '</style></head>';
|
||||
echo '<body>';
|
||||
echo '<div class="header"><img src="'. Director::absoluteBaseURL() .'cms/images/mainmenu/logo.gif" width="26" height="23"></div>';
|
||||
|
@ -17,7 +17,7 @@ class DevelopmentAdmin extends Controller {
|
||||
);
|
||||
|
||||
function index() {
|
||||
$renderer = new DebugView();
|
||||
$renderer = new SapphireDebugReporter();
|
||||
$renderer->writeHeader();
|
||||
echo <<<HTML
|
||||
<div class="info"><h1>Sapphire Development Tools</h1></div>
|
||||
|
@ -50,6 +50,10 @@ class TestRunner extends Controller {
|
||||
if (!self::$default_reporter) self::set_reporter('SapphireDebugReporter');
|
||||
}
|
||||
|
||||
public function Link() {
|
||||
return Controller::join_links(Director::absoluteBaseURL(), 'dev/tests/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Run all test classes
|
||||
*/
|
||||
@ -69,10 +73,19 @@ class TestRunner extends Controller {
|
||||
* Browse all enabled test cases in the environment
|
||||
*/
|
||||
function browse() {
|
||||
self::$default_reporter->writeHeader();
|
||||
echo '<div class="info">';
|
||||
echo '<h1>Available Tests</h1>';
|
||||
echo '</div>';
|
||||
echo '<div class="trace">';
|
||||
$tests = ClassInfo::subclassesFor('SapphireTest');
|
||||
echo "<h3><a href=\"" . $this->Link() . "all\">Run all " . count($tests) . " tests</a></h3>";
|
||||
echo "<br />";
|
||||
foreach ($tests as $test) {
|
||||
echo "<h3><a href=\"$test\">$test</a></h3>";
|
||||
echo "<h3><a href=\"" . $this->Link() . "$test\">Run $test</a></h3>";
|
||||
}
|
||||
echo '</div>';
|
||||
self::$default_reporter->writeFooter();
|
||||
}
|
||||
|
||||
function coverage() {
|
||||
@ -109,7 +122,6 @@ class TestRunner extends Controller {
|
||||
echo "<p>Running test cases: " . implode(", ", $classList) . "</p>";
|
||||
} else {
|
||||
echo "<h1>{$classList[0]}</h1>";
|
||||
echo "<p>Running test case:</p>";
|
||||
}
|
||||
echo "</div>";
|
||||
echo '<div class="trace">';
|
||||
|
@ -743,35 +743,6 @@ class TableField_Item extends TableListField_Item {
|
||||
return $content;
|
||||
}
|
||||
|
||||
function Can($mode) {
|
||||
return $this->parent->Can($mode);
|
||||
}
|
||||
|
||||
function Parent() {
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the base link for the call below.
|
||||
*/
|
||||
function BaseLink() {
|
||||
$parent = $this->parent;
|
||||
$action = $parent->FormAction();
|
||||
if(substr($action, -1, 1) !== '&'){
|
||||
$action = $action."&";
|
||||
}
|
||||
$action = str_replace('&', '&', $action);
|
||||
|
||||
return $action . "action_callfieldmethod=1&fieldName=". $parent->Name() . "&childID=" . $this->ID;
|
||||
}
|
||||
/**
|
||||
* Runs the delete() method on the Tablefield parent.
|
||||
* Allows the deletion of objects via ajax
|
||||
*/
|
||||
function DeleteLink() {
|
||||
return $this->BaseLink() . "&methodName=delete";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
@ -166,8 +166,6 @@ class SearchContext extends Object {
|
||||
$searchParams = array_filter($searchParams, array($this,'clearEmptySearchFields'));
|
||||
$query = $this->getQuery($searchParams, $sort, $limit);
|
||||
|
||||
//Debug::dump($query->sql());
|
||||
|
||||
// use if a raw SQL query is needed
|
||||
$results = new DataObjectSet();
|
||||
foreach($query->execute() as $row) {
|
||||
@ -198,7 +196,7 @@ class SearchContext extends Object {
|
||||
* @param SQLQuery $query
|
||||
*/
|
||||
protected function processFilters(SQLQuery $query, $searchParams) {
|
||||
$conditions = array();
|
||||
/*$conditions = array();
|
||||
foreach($this->filters as $field => $filter) {
|
||||
if (strstr($field, '.')) {
|
||||
$path = explode('.', $field);
|
||||
@ -207,7 +205,7 @@ class SearchContext extends Object {
|
||||
}
|
||||
}
|
||||
$query->where = $conditions;
|
||||
return $query;
|
||||
return $query;*/
|
||||
}
|
||||
|
||||
/**
|
||||
@ -255,31 +253,5 @@ class SearchContext extends Object {
|
||||
$this->fields = $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Placeholder, until I figure out the rest of the SQLQuery stuff
|
||||
* and link the $searchable_fields array to the SearchContext
|
||||
*
|
||||
* @deprecated in favor of getResults
|
||||
*/
|
||||
public function getResultSet($fields) {
|
||||
$filter = "";
|
||||
$current = 1;
|
||||
$fields = array_filter($fields, array($this,'clearEmptySearchFields'));
|
||||
$length = count($fields);
|
||||
foreach($fields as $key=>$val) {
|
||||
// Array values come from more complex fields - for now let's just disable searching on them
|
||||
if (!is_array($val) && $val != '') {
|
||||
$filter .= "`$key`='$val'";
|
||||
} else {
|
||||
$length--;
|
||||
}
|
||||
if ($current < $length) {
|
||||
$filter .= " AND ";
|
||||
}
|
||||
$current++;
|
||||
}
|
||||
return DataObject::get($this->modelClass, $filter);
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
@ -1,17 +1,20 @@
|
||||
<?php
|
||||
/**
|
||||
* @package sapphire
|
||||
* @subpackage search
|
||||
*/
|
||||
|
||||
/**
|
||||
* Checks if a value is in a given set.
|
||||
* SQL syntax used: Column IN ('val1','val2')
|
||||
*
|
||||
* @todo Add negation (NOT IN)6
|
||||
*
|
||||
* @author Silverstripe Ltd., Ingo Schommer (<firstname>@silverstripe.com)
|
||||
*/
|
||||
class CollectionFilter extends SearchFilter {
|
||||
|
||||
public function apply(SQLQuery $query) {
|
||||
$query = $this->applyRelation($query);
|
||||
$values = explode(',',$this->value);
|
||||
$values = explode(',',$this->getValue());
|
||||
if(!$values) return false;
|
||||
|
||||
for($i=0; $i<count($values); $i++) {
|
||||
|
32
search/filters/EndsWithFilter.php
Normal file
32
search/filters/EndsWithFilter.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/**
|
||||
* @package sapphire
|
||||
* @subpackage search
|
||||
*/
|
||||
|
||||
/**
|
||||
* Matches textual content with a substring match on a text fragment leading
|
||||
* to the end of the string.
|
||||
*
|
||||
* <code>
|
||||
* "abcdefg" => "defg" # true
|
||||
* "abcdefg" => "abcd" # false
|
||||
* </code>
|
||||
*
|
||||
* @package sapphire
|
||||
* @subpackage search
|
||||
*/
|
||||
class EndsWithFilter extends SearchFilter {
|
||||
|
||||
/**
|
||||
* Applies a match on the trailing characters of a field value.
|
||||
*
|
||||
* @return unknown
|
||||
*/
|
||||
public function apply(SQLQuery $query) {
|
||||
$query = $this->applyRelation($query);
|
||||
$query->where($this->getName(), "RLIKE", "{$this->getValue()}$");
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
@ -1,6 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* Matches textual content with a columnname = 'keyword' construct
|
||||
* @package sapphire
|
||||
* @subpackage search
|
||||
*/
|
||||
|
||||
/**
|
||||
* Selects textual content with an exact match between columnname and keyword.
|
||||
*
|
||||
* @todo case sensitivity switch
|
||||
* @todo documentation
|
||||
@ -17,7 +22,7 @@ class ExactMatchFilter extends SearchFilter {
|
||||
*/
|
||||
public function apply(SQLQuery $query) {
|
||||
$query = $this->applyRelation($query);
|
||||
return $query->where("{$this->getName()} = '{$this->value}'");
|
||||
return $query->where("{$this->getName()} = '{$this->getValue()}'");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,4 +1,9 @@
|
||||
<?php
|
||||
/**
|
||||
* @package sapphire
|
||||
* @subpackage search
|
||||
*/
|
||||
|
||||
/**
|
||||
* Filters by full-text matching on the given field.
|
||||
*
|
||||
@ -23,7 +28,8 @@
|
||||
class FulltextFilter extends SearchFilter {
|
||||
|
||||
public function apply(SQLQuery $query) {
|
||||
return "";
|
||||
$query->where("MATCH ({$this->getName()} AGAINST ('{$this->getValue()}')");
|
||||
return $query;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -13,7 +13,7 @@
|
||||
class NegationFilter extends SearchFilter {
|
||||
|
||||
public function apply(SQLQuery $query) {
|
||||
return $query->where("{$this->name} != '{$this->value}'");
|
||||
return $query->where("{$this->name} != '{$this->getValue()}'");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,4 +1,9 @@
|
||||
<?php
|
||||
/**
|
||||
* @package sapphire
|
||||
* @subpackage search
|
||||
*/
|
||||
|
||||
/**
|
||||
* Matches textual content with a LIKE '%keyword%' construct.
|
||||
*
|
||||
@ -9,7 +14,7 @@ class PartialMatchFilter extends SearchFilter {
|
||||
|
||||
public function apply(SQLQuery $query) {
|
||||
$query = $this->applyRelation($query);
|
||||
return $query->where("{$this->getName()} LIKE '%{$this->value}%'");
|
||||
return $query->where("{$this->getName()} LIKE '%{$this->getValue()}%'");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,4 +1,9 @@
|
||||
<?php
|
||||
/**
|
||||
* @package sapphire
|
||||
* @subpackage search
|
||||
*/
|
||||
|
||||
/**
|
||||
* @todo documentation
|
||||
*
|
||||
@ -17,12 +22,48 @@ abstract class SearchFilter extends Object {
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by constructor to convert a string pathname into
|
||||
* a well defined relationship sequence.
|
||||
*
|
||||
* @param unknown_type $name
|
||||
*/
|
||||
protected function addRelation($name) {
|
||||
if (strstr($name, '.')) {
|
||||
$parts = explode('.', $name);
|
||||
$this->name = array_pop($parts);
|
||||
$this->relation = $parts;
|
||||
} else {
|
||||
$this->name = $name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the root model class to be selected by this
|
||||
* search query.
|
||||
*
|
||||
* @param string $className
|
||||
*/
|
||||
public function setModel($className) {
|
||||
$this->model = $className;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current value to be filtered on.
|
||||
*
|
||||
* @param string $value
|
||||
*/
|
||||
public function setValue($value) {
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public function setModel($className) {
|
||||
$this->model = $className;
|
||||
/**
|
||||
* Accessor for the current value to be filtered on.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getValue() {
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -42,16 +83,6 @@ abstract class SearchFilter extends Object {
|
||||
return $candidateClass . "." . $this->name;
|
||||
}
|
||||
|
||||
protected function addRelation($name) {
|
||||
if (strstr($name, '.')) {
|
||||
$parts = explode('.', $name);
|
||||
$this->name = array_pop($parts);
|
||||
$this->relation = $parts;
|
||||
} else {
|
||||
$this->name = $name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies multiple-table inheritance to straight joins on the data objects
|
||||
*
|
||||
@ -66,11 +97,10 @@ abstract class SearchFilter extends Object {
|
||||
/**
|
||||
* Traverse the relationship fields, and add the table
|
||||
* mappings to the query object state.
|
||||
*
|
||||
* @todo move join specific crap into SQLQuery
|
||||
*
|
||||
* @param unknown_type $query
|
||||
* @return unknown
|
||||
* @todo try to make this implicitly triggered so it doesn't have to be manually called in child filters
|
||||
* @param SQLQuery $query
|
||||
* @return SQLQuery
|
||||
*/
|
||||
protected function applyRelation($query) {
|
||||
if (is_array($this->relation)) {
|
||||
@ -84,6 +114,8 @@ abstract class SearchFilter extends Object {
|
||||
$model = singleton($component);
|
||||
$this->applyJoin($query, $model, $component);
|
||||
$this->model = $component;
|
||||
} elseif ($component = $model->many_many($rel)) {
|
||||
Debug::dump("Many-Many traversals not implemented");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -94,6 +126,7 @@ abstract class SearchFilter extends Object {
|
||||
* Apply filter criteria to a SQL query.
|
||||
*
|
||||
* @param SQLQuery $query
|
||||
* @return SQLQuery
|
||||
*/
|
||||
abstract public function apply(SQLQuery $query);
|
||||
|
||||
|
32
search/filters/StartsWithFilter.php
Normal file
32
search/filters/StartsWithFilter.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/**
|
||||
* @package sapphire
|
||||
* @subpackage search
|
||||
*/
|
||||
|
||||
/**
|
||||
* Matches textual content with a substring match from the beginning
|
||||
* of the string.
|
||||
*
|
||||
* <code>
|
||||
* "abcdefg" => "defg" # false
|
||||
* "abcdefg" => "abcd" # true
|
||||
* </code>
|
||||
*
|
||||
* @package sapphire
|
||||
* @subpackage search
|
||||
*/
|
||||
class StartsWithFilter extends SearchFilter {
|
||||
|
||||
/**
|
||||
* Applies a substring match on a field value.
|
||||
*
|
||||
* @return unknown
|
||||
*/
|
||||
public function apply(SQLQuery $query) {
|
||||
$query = $this->applyRelation($query);
|
||||
$query->where("LOCATE('{$this->getValue()}', {$this->getName()}) = 1");
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
@ -1,4 +1,9 @@
|
||||
<?php
|
||||
/**
|
||||
* @package sapphire
|
||||
* @subpackage search
|
||||
*/
|
||||
|
||||
/**
|
||||
* Uses a substring match against content in column rows.
|
||||
*
|
||||
@ -8,7 +13,7 @@
|
||||
class SubstringFilter extends SearchFilter {
|
||||
|
||||
public function apply(SQLQuery $query) {
|
||||
return $query->where("LOCATE({$this->name}, $value)");
|
||||
return $query->where("LOCATE('{$this->getValue()}', {$this->getName()}) != 0");
|
||||
}
|
||||
|
||||
}
|
||||
|
34
search/filters/WithinRangeFilter.php
Normal file
34
search/filters/WithinRangeFilter.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
/**
|
||||
* @package sapphire
|
||||
* @subpackage search
|
||||
*/
|
||||
|
||||
/**
|
||||
* Incomplete.
|
||||
*
|
||||
* @todo add to tests
|
||||
*
|
||||
* @package sapphire
|
||||
* @subpackage search
|
||||
*/
|
||||
class WithinRangeFilter extends SearchFilter {
|
||||
|
||||
private $min;
|
||||
private $max;
|
||||
|
||||
function setMin($min) {
|
||||
$this->min = $min;
|
||||
}
|
||||
|
||||
function setMax($max) {
|
||||
$this->max = $max;
|
||||
}
|
||||
|
||||
function apply(SQLQuery $query) {
|
||||
$query->where("{$this->getName()} >= {$this->min} AND {$this->getName()} <= {$this->max}");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
@ -101,6 +101,8 @@ class SearchContextTest extends SapphireTest {
|
||||
"PartialMatch" => "partially",
|
||||
"Negation" => "undisclosed",
|
||||
"CollectionMatch" => "ExistingCollectionValue,NonExistingCollectionValue,4,Inline'Quotes'",
|
||||
"StartsWith" => "12345",
|
||||
"EndsWith" => "ijkl"
|
||||
);
|
||||
|
||||
$results = $context->getResults($params);
|
||||
@ -209,8 +211,10 @@ class SearchContextTest_AllFilterTypes extends DataObject implements TestOnly {
|
||||
"PartialMatch" => "Text",
|
||||
"Negation" => "Text",
|
||||
"SubstringMatch" => "Text",
|
||||
"HiddenValue" => "Text",
|
||||
"CollectionMatch" => "Text",
|
||||
"StartsWith" => "Text",
|
||||
"EndsWith" => "Text",
|
||||
"HiddenValue" => "Text"
|
||||
);
|
||||
|
||||
static $searchable_fields = array(
|
||||
@ -219,6 +223,8 @@ class SearchContextTest_AllFilterTypes extends DataObject implements TestOnly {
|
||||
"Negation" => "NegationFilter",
|
||||
"SubstringMatch" => "SubstringFilter",
|
||||
"CollectionMatch" => "CollectionFilter",
|
||||
"StartsWith" => "StartsWithFilter",
|
||||
"EndsWith" => "EndsWithFilter"
|
||||
);
|
||||
|
||||
}
|
||||
|
@ -62,4 +62,6 @@ SearchContextTest_AllFilterTypes:
|
||||
PartialMatch: Match me partially
|
||||
Negation: Shouldnt match me
|
||||
HiddenValue: Filtered value
|
||||
CollectionMatch: ExistingCollectionValue
|
||||
CollectionMatch: ExistingCollectionValue
|
||||
StartsWith: 12345-6789
|
||||
EndsWith: abcd-efgh-ijkl
|
Loading…
x
Reference in New Issue
Block a user