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;
|
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
|
* 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
|
* 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.
|
* 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);
|
abstract function convertDataObject(DataObjectInterface $do);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a data object set to this format. Return a string.
|
* 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);
|
abstract function convertDataObjectSet(DataObjectSet $set);
|
||||||
|
|
||||||
|
@ -22,8 +22,7 @@ class JSONDataFormatter extends DataFormatter {
|
|||||||
$id = $obj->ID;
|
$id = $obj->ID;
|
||||||
|
|
||||||
$json = "{\n className : \"$className\",\n";
|
$json = "{\n className : \"$className\",\n";
|
||||||
$dbFields = array_merge($obj->inheritedDatabaseFields(), array('ID'=>'Int'));
|
foreach($this->getFieldsForObj($obj) as $fieldName => $fieldType) {
|
||||||
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 {
|
||||||
|
@ -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)
|
* - 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)
|
* - 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
|
* You can trigger searches based on the fields specified on {@link DataObject::searchable_fields} and passed
|
||||||
* @subpackage api
|
* 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 {
|
class RestfulServer extends Controller {
|
||||||
static $url_handlers = array(
|
static $url_handlers = array(
|
||||||
@ -74,6 +82,9 @@ class RestfulServer extends Controller {
|
|||||||
|
|
||||||
if(!$extension) $extension = "xml";
|
if(!$extension) $extension = "xml";
|
||||||
$formatter = DataFormatter::for_extension($extension); //$this->dataFormatterFromMime($contentType);
|
$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) {
|
switch($requestMethod) {
|
||||||
case 'GET':
|
case 'GET':
|
||||||
@ -169,7 +180,7 @@ class RestfulServer extends Controller {
|
|||||||
// show empty serialized result when no records are present
|
// show empty serialized result when no records are present
|
||||||
if(!$obj) $obj = new DataObjectSet();
|
if(!$obj) $obj = new DataObjectSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
if($obj instanceof DataObjectSet) return $formatter->convertDataObjectSet($obj);
|
if($obj instanceof DataObjectSet) return $formatter->convertDataObjectSet($obj);
|
||||||
else return $formatter->convertDataObject($obj);
|
else return $formatter->convertDataObject($obj);
|
||||||
}
|
}
|
||||||
|
@ -29,8 +29,7 @@ class XMLDataFormatter extends DataFormatter {
|
|||||||
$objHref = Director::absoluteURL(self::$api_base . "$obj->class/$obj->ID");
|
$objHref = Director::absoluteURL(self::$api_base . "$obj->class/$obj->ID");
|
||||||
|
|
||||||
$json = "<$className href=\"$objHref.xml\">\n";
|
$json = "<$className href=\"$objHref.xml\">\n";
|
||||||
$dbFields = array_merge($obj->inheritedDatabaseFields(), array('ID'=>'Int'));
|
foreach($this->getFieldsForObj($obj) as $fieldName => $fieldType) {
|
||||||
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 {
|
||||||
|
@ -1261,7 +1261,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
|
|||||||
// We need to find the inverse component name
|
// We need to find the inverse component name
|
||||||
$otherManyMany = singleton($candidate)->stat('many_many');
|
$otherManyMany = singleton($candidate)->stat('many_many');
|
||||||
if(!$otherManyMany) {
|
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) {
|
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()
|
* Debugging used by Debug::show()
|
||||||
*
|
*
|
||||||
|
@ -612,6 +612,7 @@ class SiteTree extends DataObject {
|
|||||||
* It can be overloaded to customise the security model for an
|
* It can be overloaded to customise the security model for an
|
||||||
* application.
|
* application.
|
||||||
*
|
*
|
||||||
|
* @param Member $member
|
||||||
* @return boolean True if the current user can delete this page.
|
* @return boolean True if the current user can delete this page.
|
||||||
*/
|
*/
|
||||||
public function canDelete($member = null) {
|
public function canDelete($member = null) {
|
||||||
@ -637,6 +638,7 @@ class SiteTree extends DataObject {
|
|||||||
* It can be overloaded to customise the security model for an
|
* It can be overloaded to customise the security model for an
|
||||||
* application.
|
* application.
|
||||||
*
|
*
|
||||||
|
* @param Member $member
|
||||||
* @return boolean True if the current user can create pages on this
|
* @return boolean True if the current user can create pages on this
|
||||||
* class.
|
* class.
|
||||||
*/
|
*/
|
||||||
@ -663,6 +665,7 @@ class SiteTree extends DataObject {
|
|||||||
* It can be overloaded to customise the security model for an
|
* It can be overloaded to customise the security model for an
|
||||||
* application.
|
* application.
|
||||||
*
|
*
|
||||||
|
* @param Member $member
|
||||||
* @return boolean True if the current user can edit this page.
|
* @return boolean True if the current user can edit this page.
|
||||||
*/
|
*/
|
||||||
public function canEdit($member = null) {
|
public function canEdit($member = null) {
|
||||||
@ -693,6 +696,7 @@ class SiteTree extends DataObject {
|
|||||||
* It can be overloaded to customise the security model for an
|
* It can be overloaded to customise the security model for an
|
||||||
* application.
|
* application.
|
||||||
*
|
*
|
||||||
|
* @param Member $member
|
||||||
* @return boolean True if the current user can publish this page.
|
* @return boolean True if the current user can publish this page.
|
||||||
*/
|
*/
|
||||||
public function canPublish($member = null) {
|
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
|
* @param mixed $val
|
||||||
*/
|
*/
|
||||||
static function dump($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();
|
$caller = Debug::caller();
|
||||||
echo "<span style=\"font-size: 60%\">Line $caller[line] of " . basename($caller['file']) . "</span>\n";
|
echo "<span style=\"font-size: 12px;color:#666;\">Line $caller[line] of " . basename($caller['file']) . ":</span>\n";
|
||||||
print_r($val);
|
if (is_string($val)) print_r(wordwrap($val, 100));
|
||||||
|
else print_r($val);
|
||||||
echo '</pre>';
|
echo '</pre>';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,7 +261,7 @@ class Debug {
|
|||||||
echo "ERROR:Error $errno: $errstr\n At l$errline in $errfile\n";
|
echo "ERROR:Error $errno: $errstr\n At l$errline in $errfile\n";
|
||||||
Debug::backtrace();
|
Debug::backtrace();
|
||||||
} else {
|
} else {
|
||||||
$reporter = new DebugReporter();
|
$reporter = new SapphireDebugReporter();
|
||||||
$reporter->writeHeader();
|
$reporter->writeHeader();
|
||||||
echo '<div class="info">';
|
echo '<div class="info">';
|
||||||
echo "<h1>" . strip_tags($errstr) . "</h1>";
|
echo "<h1>" . strip_tags($errstr) . "</h1>";
|
||||||
@ -588,8 +589,9 @@ class SapphireDebugReporter implements DebugReporter {
|
|||||||
echo 'pre { margin-left:18px; }';
|
echo 'pre { margin-left:18px; }';
|
||||||
echo 'pre span { color:#999;}';
|
echo 'pre span { color:#999;}';
|
||||||
echo 'pre .error { color:#f00; }';
|
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 '.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 { padding:2px 20px 2px 40px; color:#C80700; background:#FFE9E9 url('.Director::absoluteBaseURL() .'cms/images/alert-bad.gif) no-repeat scroll 7px 50%; }';
|
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 '</style></head>';
|
||||||
echo '<body>';
|
echo '<body>';
|
||||||
echo '<div class="header"><img src="'. Director::absoluteBaseURL() .'cms/images/mainmenu/logo.gif" width="26" height="23"></div>';
|
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() {
|
function index() {
|
||||||
$renderer = new DebugView();
|
$renderer = new SapphireDebugReporter();
|
||||||
$renderer->writeHeader();
|
$renderer->writeHeader();
|
||||||
echo <<<HTML
|
echo <<<HTML
|
||||||
<div class="info"><h1>Sapphire Development Tools</h1></div>
|
<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');
|
if (!self::$default_reporter) self::set_reporter('SapphireDebugReporter');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function Link() {
|
||||||
|
return Controller::join_links(Director::absoluteBaseURL(), 'dev/tests/');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run all test classes
|
* Run all test classes
|
||||||
*/
|
*/
|
||||||
@ -69,10 +73,19 @@ class TestRunner extends Controller {
|
|||||||
* Browse all enabled test cases in the environment
|
* Browse all enabled test cases in the environment
|
||||||
*/
|
*/
|
||||||
function browse() {
|
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');
|
$tests = ClassInfo::subclassesFor('SapphireTest');
|
||||||
|
echo "<h3><a href=\"" . $this->Link() . "all\">Run all " . count($tests) . " tests</a></h3>";
|
||||||
|
echo "<br />";
|
||||||
foreach ($tests as $test) {
|
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() {
|
function coverage() {
|
||||||
@ -109,7 +122,6 @@ class TestRunner extends Controller {
|
|||||||
echo "<p>Running test cases: " . implode(", ", $classList) . "</p>";
|
echo "<p>Running test cases: " . implode(", ", $classList) . "</p>";
|
||||||
} else {
|
} else {
|
||||||
echo "<h1>{$classList[0]}</h1>";
|
echo "<h1>{$classList[0]}</h1>";
|
||||||
echo "<p>Running test case:</p>";
|
|
||||||
}
|
}
|
||||||
echo "</div>";
|
echo "</div>";
|
||||||
echo '<div class="trace">';
|
echo '<div class="trace">';
|
||||||
|
@ -743,35 +743,6 @@ class TableField_Item extends TableListField_Item {
|
|||||||
return $content;
|
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'));
|
$searchParams = array_filter($searchParams, array($this,'clearEmptySearchFields'));
|
||||||
$query = $this->getQuery($searchParams, $sort, $limit);
|
$query = $this->getQuery($searchParams, $sort, $limit);
|
||||||
|
|
||||||
//Debug::dump($query->sql());
|
|
||||||
|
|
||||||
// use if a raw SQL query is needed
|
// use if a raw SQL query is needed
|
||||||
$results = new DataObjectSet();
|
$results = new DataObjectSet();
|
||||||
foreach($query->execute() as $row) {
|
foreach($query->execute() as $row) {
|
||||||
@ -198,7 +196,7 @@ class SearchContext extends Object {
|
|||||||
* @param SQLQuery $query
|
* @param SQLQuery $query
|
||||||
*/
|
*/
|
||||||
protected function processFilters(SQLQuery $query, $searchParams) {
|
protected function processFilters(SQLQuery $query, $searchParams) {
|
||||||
$conditions = array();
|
/*$conditions = array();
|
||||||
foreach($this->filters as $field => $filter) {
|
foreach($this->filters as $field => $filter) {
|
||||||
if (strstr($field, '.')) {
|
if (strstr($field, '.')) {
|
||||||
$path = explode('.', $field);
|
$path = explode('.', $field);
|
||||||
@ -207,7 +205,7 @@ class SearchContext extends Object {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
$query->where = $conditions;
|
$query->where = $conditions;
|
||||||
return $query;
|
return $query;*/
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -255,31 +253,5 @@ class SearchContext extends Object {
|
|||||||
$this->fields = $fields;
|
$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
|
<?php
|
||||||
|
/**
|
||||||
|
* @package sapphire
|
||||||
|
* @subpackage search
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a value is in a given set.
|
* Checks if a value is in a given set.
|
||||||
* SQL syntax used: Column IN ('val1','val2')
|
* SQL syntax used: Column IN ('val1','val2')
|
||||||
*
|
*
|
||||||
* @todo Add negation (NOT IN)6
|
* @todo Add negation (NOT IN)6
|
||||||
*
|
|
||||||
* @author Silverstripe Ltd., Ingo Schommer (<firstname>@silverstripe.com)
|
|
||||||
*/
|
*/
|
||||||
class CollectionFilter extends SearchFilter {
|
class CollectionFilter extends SearchFilter {
|
||||||
|
|
||||||
public function apply(SQLQuery $query) {
|
public function apply(SQLQuery $query) {
|
||||||
$query = $this->applyRelation($query);
|
$query = $this->applyRelation($query);
|
||||||
$values = explode(',',$this->value);
|
$values = explode(',',$this->getValue());
|
||||||
if(!$values) return false;
|
if(!$values) return false;
|
||||||
|
|
||||||
for($i=0; $i<count($values); $i++) {
|
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
|
<?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 case sensitivity switch
|
||||||
* @todo documentation
|
* @todo documentation
|
||||||
@ -17,7 +22,7 @@ class ExactMatchFilter extends SearchFilter {
|
|||||||
*/
|
*/
|
||||||
public function apply(SQLQuery $query) {
|
public function apply(SQLQuery $query) {
|
||||||
$query = $this->applyRelation($query);
|
$query = $this->applyRelation($query);
|
||||||
return $query->where("{$this->getName()} = '{$this->value}'");
|
return $query->where("{$this->getName()} = '{$this->getValue()}'");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
|
/**
|
||||||
|
* @package sapphire
|
||||||
|
* @subpackage search
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filters by full-text matching on the given field.
|
* Filters by full-text matching on the given field.
|
||||||
*
|
*
|
||||||
@ -23,7 +28,8 @@
|
|||||||
class FulltextFilter extends SearchFilter {
|
class FulltextFilter extends SearchFilter {
|
||||||
|
|
||||||
public function apply(SQLQuery $query) {
|
public function apply(SQLQuery $query) {
|
||||||
return "";
|
$query->where("MATCH ({$this->getName()} AGAINST ('{$this->getValue()}')");
|
||||||
|
return $query;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
class NegationFilter extends SearchFilter {
|
class NegationFilter extends SearchFilter {
|
||||||
|
|
||||||
public function apply(SQLQuery $query) {
|
public function apply(SQLQuery $query) {
|
||||||
return $query->where("{$this->name} != '{$this->value}'");
|
return $query->where("{$this->name} != '{$this->getValue()}'");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
|
/**
|
||||||
|
* @package sapphire
|
||||||
|
* @subpackage search
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Matches textual content with a LIKE '%keyword%' construct.
|
* Matches textual content with a LIKE '%keyword%' construct.
|
||||||
*
|
*
|
||||||
@ -9,7 +14,7 @@ class PartialMatchFilter extends SearchFilter {
|
|||||||
|
|
||||||
public function apply(SQLQuery $query) {
|
public function apply(SQLQuery $query) {
|
||||||
$query = $this->applyRelation($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
|
<?php
|
||||||
|
/**
|
||||||
|
* @package sapphire
|
||||||
|
* @subpackage search
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @todo documentation
|
* @todo documentation
|
||||||
*
|
*
|
||||||
@ -17,12 +22,48 @@ abstract class SearchFilter extends Object {
|
|||||||
$this->value = $value;
|
$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) {
|
public function setValue($value) {
|
||||||
$this->value = $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;
|
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
|
* 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
|
* Traverse the relationship fields, and add the table
|
||||||
* mappings to the query object state.
|
* mappings to the query object state.
|
||||||
*
|
|
||||||
* @todo move join specific crap into SQLQuery
|
|
||||||
*
|
*
|
||||||
* @param unknown_type $query
|
* @todo try to make this implicitly triggered so it doesn't have to be manually called in child filters
|
||||||
* @return unknown
|
* @param SQLQuery $query
|
||||||
|
* @return SQLQuery
|
||||||
*/
|
*/
|
||||||
protected function applyRelation($query) {
|
protected function applyRelation($query) {
|
||||||
if (is_array($this->relation)) {
|
if (is_array($this->relation)) {
|
||||||
@ -84,6 +114,8 @@ abstract class SearchFilter extends Object {
|
|||||||
$model = singleton($component);
|
$model = singleton($component);
|
||||||
$this->applyJoin($query, $model, $component);
|
$this->applyJoin($query, $model, $component);
|
||||||
$this->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.
|
* Apply filter criteria to a SQL query.
|
||||||
*
|
*
|
||||||
* @param SQLQuery $query
|
* @param SQLQuery $query
|
||||||
|
* @return SQLQuery
|
||||||
*/
|
*/
|
||||||
abstract public function apply(SQLQuery $query);
|
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
|
<?php
|
||||||
|
/**
|
||||||
|
* @package sapphire
|
||||||
|
* @subpackage search
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uses a substring match against content in column rows.
|
* Uses a substring match against content in column rows.
|
||||||
*
|
*
|
||||||
@ -8,7 +13,7 @@
|
|||||||
class SubstringFilter extends SearchFilter {
|
class SubstringFilter extends SearchFilter {
|
||||||
|
|
||||||
public function apply(SQLQuery $query) {
|
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",
|
"PartialMatch" => "partially",
|
||||||
"Negation" => "undisclosed",
|
"Negation" => "undisclosed",
|
||||||
"CollectionMatch" => "ExistingCollectionValue,NonExistingCollectionValue,4,Inline'Quotes'",
|
"CollectionMatch" => "ExistingCollectionValue,NonExistingCollectionValue,4,Inline'Quotes'",
|
||||||
|
"StartsWith" => "12345",
|
||||||
|
"EndsWith" => "ijkl"
|
||||||
);
|
);
|
||||||
|
|
||||||
$results = $context->getResults($params);
|
$results = $context->getResults($params);
|
||||||
@ -209,8 +211,10 @@ class SearchContextTest_AllFilterTypes extends DataObject implements TestOnly {
|
|||||||
"PartialMatch" => "Text",
|
"PartialMatch" => "Text",
|
||||||
"Negation" => "Text",
|
"Negation" => "Text",
|
||||||
"SubstringMatch" => "Text",
|
"SubstringMatch" => "Text",
|
||||||
"HiddenValue" => "Text",
|
|
||||||
"CollectionMatch" => "Text",
|
"CollectionMatch" => "Text",
|
||||||
|
"StartsWith" => "Text",
|
||||||
|
"EndsWith" => "Text",
|
||||||
|
"HiddenValue" => "Text"
|
||||||
);
|
);
|
||||||
|
|
||||||
static $searchable_fields = array(
|
static $searchable_fields = array(
|
||||||
@ -219,6 +223,8 @@ class SearchContextTest_AllFilterTypes extends DataObject implements TestOnly {
|
|||||||
"Negation" => "NegationFilter",
|
"Negation" => "NegationFilter",
|
||||||
"SubstringMatch" => "SubstringFilter",
|
"SubstringMatch" => "SubstringFilter",
|
||||||
"CollectionMatch" => "CollectionFilter",
|
"CollectionMatch" => "CollectionFilter",
|
||||||
|
"StartsWith" => "StartsWithFilter",
|
||||||
|
"EndsWith" => "EndsWithFilter"
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -62,4 +62,6 @@ SearchContextTest_AllFilterTypes:
|
|||||||
PartialMatch: Match me partially
|
PartialMatch: Match me partially
|
||||||
Negation: Shouldnt match me
|
Negation: Shouldnt match me
|
||||||
HiddenValue: Filtered value
|
HiddenValue: Filtered value
|
||||||
CollectionMatch: ExistingCollectionValue
|
CollectionMatch: ExistingCollectionValue
|
||||||
|
StartsWith: 12345-6789
|
||||||
|
EndsWith: abcd-efgh-ijkl
|
Loading…
x
Reference in New Issue
Block a user