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@60330 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
parent
395acde8d7
commit
b6abd40783
@ -28,6 +28,7 @@ Director::addRules(10, array(
|
||||
'' => 'RootURLController',
|
||||
'sitemap.xml' => 'GoogleSitemap',
|
||||
'api/v1' => 'RestfulServer',
|
||||
'soap/v1' => 'SOAPModelAccess',
|
||||
'dev' => 'DevelopmentAdmin'
|
||||
));
|
||||
|
||||
|
247
api/SOAPModelAccess.php
Executable file
247
api/SOAPModelAccess.php
Executable file
@ -0,0 +1,247 @@
|
||||
<?
|
||||
/**
|
||||
* Basic SOAP Server to access and modify DataObject instances.
|
||||
* You can enable SOAP access on a DataObject by setting {@link DataObject::$api_access} to true.
|
||||
*
|
||||
* Usage - Getting a record:
|
||||
* <code>
|
||||
* $c = new SoapClient('http://mysite.com/soap/v1/wsdl');
|
||||
* echo $c->getXML("MyClassName", 99); // gets record #99 as xml
|
||||
* </code>
|
||||
*
|
||||
* Usage - Updating a record:
|
||||
* <code>
|
||||
* $c = new SoapClient('http://mysite.com/soap/v1/wsdl');
|
||||
* $data = array('MyProperty' => 'MyUpdatedValue');
|
||||
* echo $c->putXML("MyClassName", 99, null, $data);
|
||||
* </code>
|
||||
*
|
||||
* Usage - Creating a record:
|
||||
* <code>
|
||||
* $c = new SoapClient('http://mysite.com/soap/v1/wsdl');
|
||||
* $data = array('MyProperty' => 'MyValue');
|
||||
* echo $c->putXML("MyClassName", null, null, $data);
|
||||
* </code>
|
||||
*
|
||||
* Usage - Creating a record:
|
||||
* <code>
|
||||
* $c = new SoapClient('http://mysite.com/soap/v1/wsdl');
|
||||
* echo $c->deleteXML("MyClassName");
|
||||
* </code>
|
||||
*
|
||||
* @todo Test relation methods
|
||||
*
|
||||
* @package sapphire
|
||||
* @subpackage api
|
||||
*/
|
||||
class SOAPModelAccess extends SapphireSoapServer {
|
||||
|
||||
public static $methods = array(
|
||||
'getXML' => array(
|
||||
'class' => 'string',
|
||||
'id' => 'int',
|
||||
'relation' => 'string',
|
||||
'_returns' => 'string',
|
||||
),
|
||||
'getJSON' => array(
|
||||
'class' => 'string',
|
||||
'id' => 'int',
|
||||
'relation' => 'string',
|
||||
'_returns' => 'string',
|
||||
),
|
||||
'putXML' => array(
|
||||
'class' => 'string',
|
||||
'id' => 'int',
|
||||
'relation' => 'string',
|
||||
'data' => 'string',
|
||||
'username' => 'string',
|
||||
'password' => 'string',
|
||||
'_returns' => 'boolean',
|
||||
),
|
||||
'putJSON' => array(
|
||||
'class' => 'string',
|
||||
'id' => 'int',
|
||||
'relation' => 'string',
|
||||
'_returns' => 'boolean',
|
||||
),
|
||||
);
|
||||
|
||||
function Link() {
|
||||
return "soap/v1/";
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to emulate RESTful GET requests with XML data.
|
||||
*
|
||||
* @param string $class
|
||||
* @param Number $id
|
||||
* @param string $relation Relation name
|
||||
* @return string
|
||||
*/
|
||||
function getXML($class, $id, $relation = false, $username = null, $password = null) {
|
||||
$this->authenticate($username, $password);
|
||||
|
||||
$response = Director::test(
|
||||
$this->buildRestfulURL($class, $id, $relation, 'xml'),
|
||||
null,
|
||||
null,
|
||||
'GET'
|
||||
);
|
||||
|
||||
return ($response->isError()) ? $this->getErrorMessage($response) : $response->getBody();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to emulate RESTful GET requests with JSON data.
|
||||
*
|
||||
* @param string $class
|
||||
* @param Number $id
|
||||
* @param string $relation Relation name
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @return string
|
||||
*/
|
||||
function getJSON($class, $id, $relation = false, $username = null, $password = null) {
|
||||
$this->authenticate($username, $password);
|
||||
|
||||
$response = Director::test(
|
||||
$this->buildRestfulURL($class, $id, $relation, 'json'),
|
||||
null,
|
||||
null,
|
||||
'GET'
|
||||
);
|
||||
|
||||
return ($response->isError()) ? $this->getErrorMessage($response) : $response->getBody();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to emulate RESTful POST and PUT requests with XML data.
|
||||
*
|
||||
* @param string $class
|
||||
* @param Number $id
|
||||
* @param string $relation Relation name
|
||||
* @param array $data
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @return string
|
||||
*/
|
||||
function putXML($class, $id = false, $relation = false, $data, $username = null, $password = null) {
|
||||
$this->authenticate($username, $password);
|
||||
|
||||
$response = Director::test(
|
||||
$this->buildRestfulURL($class, $id, $relation, 'xml'),
|
||||
$data,
|
||||
null,
|
||||
($id) ? 'PUT' : 'POST'
|
||||
);
|
||||
|
||||
return ($response->isError()) ? $this->getErrorMessage($response) : $response->getBody();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to emulate RESTful POST and PUT requests with JSON data.
|
||||
*
|
||||
* @param string $class
|
||||
* @param Number $id
|
||||
* @param string $relation Relation name
|
||||
* @param array $data
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @return string
|
||||
*/
|
||||
function putJSON($class = false, $id = false, $relation = false, $data, $username = null, $password = null) {
|
||||
$this->authenticate($username, $password);
|
||||
|
||||
$response = Director::test(
|
||||
$this->buildRestfulURL($class, $id, $relation, 'json'),
|
||||
$data,
|
||||
null,
|
||||
($id) ? 'PUT' : 'POST'
|
||||
);
|
||||
|
||||
return ($response->isError()) ? $this->getErrorMessage($response) : $response->getBody();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to emulate RESTful DELETE requests.
|
||||
*
|
||||
* @param string $class
|
||||
* @param Number $id
|
||||
* @param string $relation Relation name
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @return string
|
||||
*/
|
||||
function deleteXML($class, $id, $relation = false, $username = null, $password = null) {
|
||||
$this->authenticate($username, $password);
|
||||
|
||||
$response = Director::test(
|
||||
$this->buildRestfulURL($class, $id, $relation, 'xml'),
|
||||
null,
|
||||
null,
|
||||
'DELETE'
|
||||
);
|
||||
|
||||
return ($response->isError()) ? $this->getErrorMessage($response) : $response->getBody();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to emulate RESTful DELETE requests.
|
||||
*
|
||||
* @param string $class
|
||||
* @param Number $id
|
||||
* @param string $relation Relation name
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @return string
|
||||
*/
|
||||
function deleteJSON($class, $id, $relation = false, $username = null, $password = null) {
|
||||
$this->authenticate($username, $password);
|
||||
|
||||
$response = Director::test(
|
||||
$this->buildRestfulURL($class, $id, $relation, 'json'),
|
||||
null,
|
||||
null,
|
||||
'DELETE'
|
||||
);
|
||||
|
||||
return ($response->isError()) ? $this->getErrorMessage($response) : $response->getBody();
|
||||
}
|
||||
|
||||
/**
|
||||
* Faking an HTTP Basicauth login in the PHP environment
|
||||
* that RestfulServer can pick up.
|
||||
*
|
||||
* @param string $username Username
|
||||
* @param string $password Plaintext password
|
||||
*/
|
||||
protected function authenticate($username, $password) {
|
||||
$_SERVER['PHP_AUTH_USER'] = $username;
|
||||
$_SERVER['PHP_AUTH_PW'] = $password;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @param Number $id
|
||||
* @param string $relation
|
||||
* @param string $extension
|
||||
* @return string
|
||||
*/
|
||||
protected function buildRestfulURL($class, $id, $relation, $extension) {
|
||||
$url = "api/v1/{$class}";
|
||||
if($id) $url .= "/{$id}";
|
||||
if($relation) $url .= "/{$relation}";
|
||||
if($extension) $url .= "/.{$extension}";
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param HTTPResponse $response
|
||||
* @return string XML string containing the HTTP error message
|
||||
*/
|
||||
protected function getErrorMessage($response) {
|
||||
return "<error type=\"authentication\" code=\"" . $response->getStatusCode() . "\">" . $response->getStatusDescription() . "</error>";
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -9,6 +9,7 @@ class SapphireSoapServer extends Controller {
|
||||
static $methods = array();
|
||||
static $xsd_types = array(
|
||||
'int' => 'xsd:int',
|
||||
'boolean' => 'xsd:boolean',
|
||||
'string' => 'xsd:string',
|
||||
'binary' => 'xsd:base64Binary',
|
||||
);
|
||||
@ -20,7 +21,7 @@ class SapphireSoapServer extends Controller {
|
||||
}
|
||||
|
||||
function getWSDLURL() {
|
||||
return Director::absoluteBaseURLWithAuth() . $this->class . "/wsdl";
|
||||
return Director::absoluteBaseURLWithAuth() . $this->Link() . "wsdl";
|
||||
}
|
||||
|
||||
function Methods() {
|
||||
|
46
api/SapphireSoapServer_wsdl.ss
Executable file
46
api/SapphireSoapServer_wsdl.ss
Executable file
@ -0,0 +1,46 @@
|
||||
<?xml version="1.0"?>
|
||||
<definitions xmlns="http://schemas.xmlsoap.org/wsdl/"
|
||||
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
|
||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:tns="{$ServiceURL}wsdl"
|
||||
targetNamespace="{$ServiceURL}wsdl">
|
||||
<% control Methods %>
|
||||
<message name="{$Name}Request" targetNamespace="$CurrentPage.TargetNamespace">
|
||||
<% control Arguments %>
|
||||
<part name="$Name" type="$Type"/>
|
||||
<% end_control %>
|
||||
</message>
|
||||
<message name="{$Name}Response" targetNamespace="$CurrentPage.TargetNamespace">
|
||||
<part name="{$Name}Return" type="$ReturnType" />
|
||||
</message>
|
||||
<% end_control %>
|
||||
|
||||
<portType name="SapphireSOAP_methodsPortType">
|
||||
<% control Methods %>
|
||||
<operation name="$Name">
|
||||
<input message="tns:{$Name}Request"/>
|
||||
<output message="tns:{$Name}Response"/>
|
||||
</operation>
|
||||
<% end_control %>
|
||||
</portType>
|
||||
<binding name="SapphireSOAP_methodsBinding" type="tns:SapphireSOAP_methodsPortType">
|
||||
<soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
|
||||
<% control Methods %>
|
||||
<operation name="$Name">
|
||||
<soap:operation soapAction="$CurrentPage.ServiceURL?method=$Name" style="rpc"/>
|
||||
<input>
|
||||
<soap:body use="encoded" namespace="$CurrentPage.TargetNamespace" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
|
||||
</input>
|
||||
<output>
|
||||
<soap:body use="encoded" namespace="$CurrentPage.TargetNamespace" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
|
||||
</output>
|
||||
</operation>
|
||||
<% end_control %>
|
||||
</binding>
|
||||
<service name="SapphireSOAP_methods">
|
||||
<port name="SapphireSOAP_methodsPort" binding="tns:SapphireSOAP_methodsBinding">
|
||||
<soap:address location="$CurrentPage.ServiceURL" />
|
||||
</port>
|
||||
</service>
|
||||
</definitions>
|
||||
|
@ -164,6 +164,8 @@ class Director {
|
||||
|
||||
/**
|
||||
* Handle an HTTP request, defined with a HTTPRequest object.
|
||||
*
|
||||
* @return HTTPResponse|string
|
||||
*/
|
||||
protected static function handleRequest(HTTPRequest $request, Session $session) {
|
||||
krsort(Director::$rules);
|
||||
@ -452,10 +454,12 @@ class Director {
|
||||
* Returns the Absolute URL of the site root, embedding the current basic-auth credentials into the URL.
|
||||
*/
|
||||
static function absoluteBaseURLWithAuth() {
|
||||
if($_SERVER['PHP_AUTH_USER'])
|
||||
$login = "$_SERVER[PHP_AUTH_USER]:$_SERVER[PHP_AUTH_PW]@";
|
||||
|
||||
if($_SERVER['SSL']) $s = "s";
|
||||
$s = "";
|
||||
$login = "";
|
||||
|
||||
if(isset($_SERVER['PHP_AUTH_USER'])) $login = "$_SERVER[PHP_AUTH_USER]:$_SERVER[PHP_AUTH_PW]@";
|
||||
if(isset($_SERVER['SSL']) && $_SERVER['SSL'] != 'Off') $s = "s";
|
||||
|
||||
return "http$s://" . $login . $_SERVER['HTTP_HOST'] . Director::baseURL();
|
||||
}
|
||||
|
||||
|
@ -79,6 +79,17 @@ class HTTPResponse extends Object {
|
||||
function getStatusCode() {
|
||||
return $this->statusCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Description for a HTTP status code
|
||||
*/
|
||||
function getStatusDescription() {
|
||||
if(isset(self::$status_codes[$this->statusCode])) {
|
||||
return self::$status_codes[$this->statusCode];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this HTTP response is in error
|
||||
@ -148,7 +159,7 @@ class HTTPResponse extends Object {
|
||||
function output() {
|
||||
if(in_array($this->statusCode, self::$redirect_codes) && headers_sent($file, $line)) {
|
||||
$url = $this->headers['Location'];
|
||||
echo
|
||||
echo
|
||||
"<p>Redirecting to <a href=\"$url\" title=\"Please click this link if your browser does not redirect you\">$url... (output started on $file, line $line)</a></p>
|
||||
<meta http-equiv=\"refresh\" content=\"1; url=$url\" />
|
||||
<script type=\"text/javascript\">setTimeout('window.location.href = \"$url\"', 50);</script>";
|
||||
|
@ -68,6 +68,7 @@ class RequestHandlingData extends ViewableData {
|
||||
* @param $params The parameters taken from the parsed URL of the parent url handler
|
||||
* @param $request The {@link HTTPRequest} object that is reponsible for distributing URL parsing
|
||||
* @uses HTTPRequest
|
||||
* @return HTTPResponse|RequestHandlingData|string|array
|
||||
*/
|
||||
function handleRequest($request) {
|
||||
$this->request = $request;
|
||||
|
@ -157,36 +157,31 @@ class ComponentSet extends DataObjectSet {
|
||||
function setByIDList($idList) {
|
||||
$has = array();
|
||||
// Index current data
|
||||
if($this->items) {
|
||||
foreach( $this->items as $item )
|
||||
$has[$item->ID] = true;
|
||||
if($this->items) foreach($this->items as $item) {
|
||||
$has[$item->ID] = true;
|
||||
}
|
||||
|
||||
// Keep track of items to delete
|
||||
if( isset( $has ) )
|
||||
$itemsToDelete = $has;
|
||||
$itemsToDelete = $has;
|
||||
|
||||
if($idList){
|
||||
foreach($idList as $id) {
|
||||
if( isset( $itemsToDelete ) )
|
||||
$itemsToDelete[$id] = false;
|
||||
if( ! isset( $has ) || ( $id && ! isset( $has[$id] ) ) )
|
||||
$this->add($id);
|
||||
}
|
||||
// add items in the list
|
||||
// $id is the database ID of the record
|
||||
if($idList) foreach($idList as $id) {
|
||||
$itemsToDelete[$id] = false;
|
||||
if($id && !isset($has[$id])) $this->add($id);
|
||||
}
|
||||
// Delete any unmentionedItems
|
||||
if( isset( $itemsToDelete ) ) {
|
||||
foreach($itemsToDelete as $id => $actuallyDelete) {
|
||||
if($actuallyDelete) $removeList[] = $id;
|
||||
}
|
||||
|
||||
if( isset( $removeList ) )
|
||||
$this->removeMany($removeList);
|
||||
|
||||
// delete items not in the list
|
||||
$removeList = array();
|
||||
foreach($itemsToDelete as $id => $actuallyDelete) {
|
||||
if($actuallyDelete) $removeList[] = $id;
|
||||
}
|
||||
$this->removeMany($removeList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an item from this set.
|
||||
*
|
||||
* @param DataObject|string|int $item Item to remove, either as a DataObject or as the ID.
|
||||
*/
|
||||
function remove($item) {
|
||||
@ -226,20 +221,19 @@ class ComponentSet extends DataObjectSet {
|
||||
|
||||
/**
|
||||
* Remove many items from this set.
|
||||
* @param array $itemList The items to remove.
|
||||
Ü
|
||||
* @param array $itemList The items to remove, as a numerical array with IDs or as a DataObjectSet
|
||||
*/
|
||||
function removeMany($itemList) {
|
||||
if(!count($itemList)) return false;
|
||||
|
||||
if($this->type == '1-to-many') {
|
||||
if(isset($itemList)) {
|
||||
foreach($itemList as $item) $this->remove($item);
|
||||
}
|
||||
foreach($itemList as $item) $this->remove($item);
|
||||
} else {
|
||||
if(isset($itemList)) {
|
||||
$itemCSV = implode(", ", $itemList);
|
||||
$parentField = $this->ownerClass . 'ID';
|
||||
$childField = ($this->childClass == $this->ownerClass) ? "ChildID" : ($this->childClass . 'ID');
|
||||
DB::query("DELETE FROM `$this->tableName` WHERE $parentField = {$this->ownerObj->ID} AND $childField IN ($itemCSV)");
|
||||
}
|
||||
$itemCSV = implode(", ", $itemList);
|
||||
$parentField = $this->ownerClass . 'ID';
|
||||
$childField = ($this->childClass == $this->ownerClass) ? "ChildID" : ($this->childClass . 'ID');
|
||||
DB::query("DELETE FROM `$this->tableName` WHERE $parentField = {$this->ownerObj->ID} AND $childField IN ($itemCSV)");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1803,7 +1803,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
|
||||
|
||||
$object = $component->dbObject($fieldName);
|
||||
|
||||
if (!($object instanceof DBField)) {
|
||||
if (!($object instanceof DBField) && !($object instanceof ComponentSet)) {
|
||||
user_error("Unable to traverse to related object field [$fieldPath] on [$this->class]", E_USER_ERROR);
|
||||
}
|
||||
return $object;
|
||||
@ -2057,7 +2057,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush the cached results for get_one()
|
||||
* Flush the cached results for all relations (has_one, has_many, many_many)
|
||||
*/
|
||||
public function flushCache() {
|
||||
if($this->class == 'DataObject') {
|
||||
@ -2073,6 +2073,8 @@ class DataObject extends ViewableData implements DataObjectInterface {
|
||||
// if(DataObject::$cache_get_one[$class]) foreach(DataObject::$cache_get_one[$class] as $obj) if($obj) $obj->destroy();
|
||||
DataObject::$cache_get_one[$class] = null;
|
||||
}
|
||||
|
||||
$this->componentCache = array();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2450,6 +2452,10 @@ class DataObject extends ViewableData implements DataObjectInterface {
|
||||
$filters = array();
|
||||
foreach($this->searchableFields() as $name => $spec) {
|
||||
$filterClass = $spec['filter'];
|
||||
// if $filterClass is not set a name of any subclass of SearchFilter than assing 'PartiailMatchFilter' to it
|
||||
if (!is_subclass_of($filterClass, 'SearchFilter')) {
|
||||
$filterClass = 'PartialMatchFilter';
|
||||
}
|
||||
$filters[$name] = new $filterClass($name);
|
||||
}
|
||||
return $filters;
|
||||
|
@ -141,7 +141,7 @@ class SQLQuery extends Object {
|
||||
* @return SQLQuery This instance
|
||||
*/
|
||||
public function leftJoin($table, $onPredicate) {
|
||||
$this->from[$table] = "LEFT JOIN $table ON $onPredicate";
|
||||
$this->from[$table] = "LEFT JOIN `$table` ON $onPredicate";
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -119,7 +119,11 @@ class CheckboxSetField extends OptionsetField {
|
||||
$fieldname = $this->name ;
|
||||
|
||||
if($fieldname && $record && ($record->has_many($fieldname) || $record->many_many($fieldname))) {
|
||||
$record->$fieldname()->setByIDList($this->value);
|
||||
$idList = array();
|
||||
foreach($this->value as $id => $bool) {
|
||||
if($bool) $idList[] = $id;
|
||||
}
|
||||
$record->$fieldname()->setByIDList($idList);
|
||||
} elseif($fieldname && $record) {
|
||||
if($this->value) {
|
||||
$this->value = str_replace(",", "{comma}", $this->value);
|
||||
@ -130,6 +134,20 @@ class CheckboxSetField extends OptionsetField {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the CheckboxSetField value, as an array of the selected item keys
|
||||
*/
|
||||
function dataValue() {
|
||||
if($this->value){
|
||||
// Filter items to those who aren't 0
|
||||
$filtered = array();
|
||||
foreach($this->value as $item) if($item) $filtered[] = str_replace(",", "{comma}", $item);
|
||||
return implode(",", $filtered);
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a pretty readonly field
|
||||
*/
|
||||
|
@ -154,6 +154,94 @@ class DataObjectTest extends SapphireTest {
|
||||
$this->assertTrue($comment->ParentID == $page->ID);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Test removeMany() and addMany() on $many_many relationships
|
||||
*/
|
||||
function testManyManyRelationships() {
|
||||
$player1 = $this->fixture->objFromFixture('DataObjectTest_Player', 'player1');
|
||||
$player2 = $this->fixture->objFromFixture('DataObjectTest_Player', 'player2');
|
||||
$team1 = $this->fixture->objFromFixture('DataObjectTest_Team', 'team1');
|
||||
$team2 = $this->fixture->objFromFixture('DataObjectTest_Team', 'team2');
|
||||
|
||||
// Test adding single DataObject by reference
|
||||
$player1->Teams()->add($team1);
|
||||
$player1->flushCache();
|
||||
$compareTeams = new ComponentSet($team1);
|
||||
$this->assertEquals(
|
||||
$player1->Teams()->column('ID'),
|
||||
$compareTeams->column('ID'),
|
||||
"Adding single record as DataObject to many_many"
|
||||
);
|
||||
|
||||
// test removing single DataObject by reference
|
||||
$player1->Teams()->remove($team1);
|
||||
$player1->flushCache();
|
||||
$compareTeams = new ComponentSet();
|
||||
$this->assertEquals(
|
||||
$player1->Teams()->column('ID'),
|
||||
$compareTeams->column('ID'),
|
||||
"Removing single record as DataObject from many_many"
|
||||
);
|
||||
|
||||
// test adding single DataObject by ID
|
||||
$player1->Teams()->add($team1->ID);
|
||||
$player1->flushCache();
|
||||
$compareTeams = new ComponentSet($team1);
|
||||
$this->assertEquals(
|
||||
$player1->Teams()->column('ID'),
|
||||
$compareTeams->column('ID'),
|
||||
"Adding single record as ID to many_many"
|
||||
);
|
||||
|
||||
// test removing single DataObject by ID
|
||||
$player1->Teams()->remove($team1->ID);
|
||||
$player1->flushCache();
|
||||
$compareTeams = new ComponentSet();
|
||||
$this->assertEquals(
|
||||
$player1->Teams()->column('ID'),
|
||||
$compareTeams->column('ID'),
|
||||
"Removing single record as ID from many_many"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Extend type change tests (e.g. '0'==NULL)
|
||||
*/
|
||||
function testChangedFields() {
|
||||
$page = $this->fixture->objFromFixture('Page', 'home');
|
||||
$page->Title = 'Home-Changed';
|
||||
$page->ShowInMenus = true;
|
||||
|
||||
$this->assertEquals(
|
||||
$page->getChangedFields(false, 1),
|
||||
array(
|
||||
'Title' => array(
|
||||
'before' => 'Home',
|
||||
'after' => 'Home-Changed',
|
||||
'level' => 2
|
||||
),
|
||||
'ShowInMenus' => array(
|
||||
'before' => 1,
|
||||
'after' => true,
|
||||
'level' => 1
|
||||
)
|
||||
),
|
||||
'Changed fields are correctly detected with strict type changes (level=1)'
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
$page->getChangedFields(false, 2),
|
||||
array(
|
||||
'Title' => array(
|
||||
'before'=>'Home',
|
||||
'after'=>'Home-Changed',
|
||||
'level' => 2
|
||||
)
|
||||
),
|
||||
'Changed fields are correctly detected while ignoring type changes (level=2)'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
|
@ -23,3 +23,15 @@ Page:
|
||||
page2:
|
||||
Title: Second Page
|
||||
|
||||
DataObjectTest_Team:
|
||||
team1:
|
||||
Title: Team 1
|
||||
team2:
|
||||
Title: Team 2
|
||||
|
||||
DataObjectTest_Player:
|
||||
player1:
|
||||
FirstName: Player 1
|
||||
player2:
|
||||
FirstName: Player 2
|
||||
Teams: =>DataObjectTest_Team.team1,=>DataObjectTest_Team.team2
|
96
tests/GroupTest.php
Normal file
96
tests/GroupTest.php
Normal file
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
/**
|
||||
* @package sapphire
|
||||
* @subpackage testing
|
||||
*/
|
||||
class GroupTest extends FunctionalTest {
|
||||
|
||||
static $fixture_file = 'sapphire/tests/GroupTest.yml';
|
||||
|
||||
function testMemberGroupRelationForm() {
|
||||
$adminGroup = $this->fixture->objFromFixture('Group', 'admingroup');
|
||||
$parentGroup = $this->fixture->objFromFixture('Group', 'parentgroup');
|
||||
$childGroup = $this->fixture->objFromFixture('Group', 'childgroup');
|
||||
|
||||
// Test single group relation through checkboxsetfield
|
||||
$form = new GroupTest_MemberForm($this, 'Form');
|
||||
$member = $this->fixture->objFromFixture('GroupTest_Member', 'admin');
|
||||
$form->loadDataFrom($member);
|
||||
$checkboxSetField = $form->Fields()->fieldByName('Groups');
|
||||
$checkboxSetField->setValue(array(
|
||||
$adminGroup->ID => $adminGroup->ID, // keep existing relation
|
||||
$parentGroup->ID => $parentGroup->ID, // add new relation
|
||||
));
|
||||
$form->saveInto($member);
|
||||
$updatedGroups = $member->Groups();
|
||||
$controlGroups = new Member_GroupSet(
|
||||
$adminGroup,
|
||||
$parentGroup
|
||||
);
|
||||
$this->assertEquals(
|
||||
$updatedGroups->Map('ID','ID'),
|
||||
$controlGroups->Map('ID','ID'),
|
||||
"Adding a toplevel group works"
|
||||
);
|
||||
|
||||
// Test unsetting relationship
|
||||
$form->loadDataFrom($member);
|
||||
$checkboxSetField = $form->Fields()->fieldByName('Groups');
|
||||
$checkboxSetField->setValue(array(
|
||||
$adminGroup->ID => $adminGroup->ID, // keep existing relation
|
||||
//$parentGroup->ID => $parentGroup->ID, // remove previously set relation
|
||||
));
|
||||
$form->saveInto($member);
|
||||
$member->flushCache();
|
||||
$updatedGroups = $member->Groups();
|
||||
$controlGroups = new Member_GroupSet(
|
||||
$adminGroup
|
||||
);
|
||||
$this->assertEquals(
|
||||
$updatedGroups->Map('ID','ID'),
|
||||
$controlGroups->Map('ID','ID'),
|
||||
"Removing a previously added toplevel group works"
|
||||
);
|
||||
|
||||
// Test adding child group
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class GroupTest_Member extends Member implements TestOnly {
|
||||
|
||||
function getCMSFields() {
|
||||
$groups = DataObject::get('Group');
|
||||
$groupsMap = ($groups) ? $groups->toDropDownMap() : false;
|
||||
$fields = new FieldSet(
|
||||
new HiddenField('ID', 'ID'),
|
||||
new CheckboxSetField(
|
||||
'Groups',
|
||||
'Groups',
|
||||
$groupsMap
|
||||
)
|
||||
);
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class GroupTest_MemberForm extends Form {
|
||||
|
||||
function __construct($controller, $name) {
|
||||
$fields = singleton('GroupTest_Member')->getCMSFields();
|
||||
$actions = new FieldSet(
|
||||
new FormAction('doSave','save')
|
||||
);
|
||||
|
||||
parent::__construct($controller, $name, $fields, $actions);
|
||||
}
|
||||
|
||||
function doSave($data, $form) {
|
||||
// done in testing methods
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
21
tests/GroupTest.yml
Normal file
21
tests/GroupTest.yml
Normal file
@ -0,0 +1,21 @@
|
||||
Group:
|
||||
admingroup:
|
||||
Code: admingroup
|
||||
parentgroup:
|
||||
Code: parentgroup
|
||||
childgroup:
|
||||
Code: childgroup
|
||||
Parent: =>Group.parentgroup
|
||||
GroupTest_Member:
|
||||
admin:
|
||||
FirstName: Admin
|
||||
Groups: =>Group.admingroup
|
||||
parentgroupuser:
|
||||
FirstName: Parent Group User
|
||||
Groups: =>Group.parentgroup
|
||||
childgroupuser:
|
||||
FirstName: Child Group User
|
||||
Groups: =>Group.childgroup
|
||||
allgroupuser:
|
||||
FirstName: All Group User
|
||||
Groups: =>Group.admingroup,=>Group.parentgroup,=>Group.childgroup
|
137
tests/SoapModelAccessTest.php
Normal file
137
tests/SoapModelAccessTest.php
Normal file
@ -0,0 +1,137 @@
|
||||
<?php
|
||||
/**
|
||||
*
|
||||
* @todo Test Relation getters
|
||||
* @todo Test filter and limit through GET params
|
||||
* @todo Test DELETE verb
|
||||
*
|
||||
* @package sapphire
|
||||
* @subpackage testing
|
||||
*/
|
||||
class SoapModelAccessTest extends SapphireTest {
|
||||
|
||||
static $fixture_file = 'sapphire/tests/SoapModelAccessTest.yml';
|
||||
/*
|
||||
public function testApiAccess() {
|
||||
$c = new SoapClient(Director::absoluteBaseURL() . 'soap/v1/wsdl');
|
||||
$soapResponse = $c->getXML(
|
||||
"SoapModelAccessTest_Comment",
|
||||
1,
|
||||
null,
|
||||
null,
|
||||
'editor@test.com',
|
||||
'editor'
|
||||
);
|
||||
var_dump($soapResponse);
|
||||
die();
|
||||
$responseArr = Convert::xml2array($soapResponse);
|
||||
$this->assertEquals($responseArr['ID'], 1);
|
||||
$this->assertEquals($responseArr['Name'], 'Joe');
|
||||
}
|
||||
|
||||
public function testAuthenticatedPUT() {
|
||||
// test wrong details
|
||||
$c = new SoapClient(Director::absoluteBaseURL() . 'soap/v1/wsdl');
|
||||
$soapResponse = $c->getXML(
|
||||
"SoapModelAccessTest_Comment",
|
||||
1,
|
||||
null,
|
||||
array(
|
||||
'Name' => 'Updated Name'
|
||||
),
|
||||
'editor@test.com',
|
||||
'wrongpassword'
|
||||
);
|
||||
$this->assertEquals(
|
||||
$soapResponse,
|
||||
'<error type="authentication" code="403">Forbidden</error>'
|
||||
);
|
||||
|
||||
// test correct details
|
||||
$c = new SoapClient(Director::absoluteBaseURL() . 'soap/v1/wsdl');
|
||||
$soapResponse = $c->getXML(
|
||||
"SoapModelAccessTest_Comment",
|
||||
1,
|
||||
null,
|
||||
array(
|
||||
'Name' => 'Updated Name'
|
||||
),
|
||||
'editor@test.com',
|
||||
'editor'
|
||||
);
|
||||
$responseArr = Convert::xml2array($soapResponse);
|
||||
$this->assertEquals($responseArr['ID'], 1);
|
||||
$this->assertEquals($responseArr['Name'], 'Updated Name');
|
||||
}
|
||||
|
||||
public function testAuthenticatedPOST() {
|
||||
$c = new SoapClient(Director::absoluteBaseURL() . 'soap/v1/wsdl');
|
||||
$soapResponse = $c->getXML(
|
||||
"SoapModelAccessTest_Comment",
|
||||
null,
|
||||
null,
|
||||
array(
|
||||
'Name' => 'Created Name'
|
||||
),
|
||||
'editor@test.com',
|
||||
'editor'
|
||||
);
|
||||
$responseArr = Convert::xml2array($soapResponse);
|
||||
$this->assertEquals($responseArr['Name'], 'Created Name');
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Everybody can view comments, logged in members in the "users" group can create comments,
|
||||
* but only "editors" can edit or delete them.
|
||||
*
|
||||
*/
|
||||
class SoapModelAccessTest_Comment extends DataObject implements PermissionProvider,TestOnly {
|
||||
|
||||
static $api_access = true;
|
||||
|
||||
static $db = array(
|
||||
"Name" => "Varchar(255)",
|
||||
"Comment" => "Text"
|
||||
);
|
||||
|
||||
static $has_many = array();
|
||||
|
||||
public function providePermissions(){
|
||||
return array(
|
||||
'EDIT_Comment' => 'Edit Comment Objects',
|
||||
'CREATE_Comment' => 'Create Comment Objects',
|
||||
'DELETE_Comment' => 'Delete Comment Objects',
|
||||
);
|
||||
}
|
||||
|
||||
public function canView($member = null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function canEdit($member = null) {
|
||||
return Permission::checkMember($member, 'EDIT_Comment');
|
||||
}
|
||||
|
||||
public function canDelete($member = null) {
|
||||
return Permission::checkMember($member, 'DELETE_Comment');
|
||||
}
|
||||
|
||||
public function canCreate($member = null) {
|
||||
return Permission::checkMember($member, 'CREATE_Comment');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class SoapModelAccessTest_Page extends DataObject implements TestOnly {
|
||||
|
||||
static $api_access = false;
|
||||
|
||||
static $db = array(
|
||||
'Title' => 'Text',
|
||||
'Content' => 'HTMLText',
|
||||
);
|
||||
}
|
||||
?>
|
38
tests/SoapModelAccessTest.yml
Normal file
38
tests/SoapModelAccessTest.yml
Normal file
@ -0,0 +1,38 @@
|
||||
SoapModelAccessTest_Comment:
|
||||
comment1:
|
||||
Name: Joe
|
||||
Comment: This is a test comment
|
||||
Member:
|
||||
editor:
|
||||
FirstName: Editor
|
||||
Email: editor@test.com
|
||||
Password: editor
|
||||
user:
|
||||
FirstName: User
|
||||
Email: user@test.com
|
||||
Password: user
|
||||
Group:
|
||||
editorgroup:
|
||||
Title: Editors
|
||||
Code: editors
|
||||
Members: =>Member.editor
|
||||
usergroup:
|
||||
Title: Users
|
||||
Code: users
|
||||
Members: =>Member.user
|
||||
Permission:
|
||||
perm1:
|
||||
Code: CREATE_Comment
|
||||
Group: =>Group.usergroup
|
||||
perm3:
|
||||
Code: EDIT_Comment
|
||||
Group: =>Group.editorgroup
|
||||
perm4:
|
||||
Code: DELETE_Comment
|
||||
Group: =>Group.editorgroup
|
||||
perm5:
|
||||
Code: CREATE_Comment
|
||||
Group: =>Group.editorgroup
|
||||
SoapModelAccessTest_Page:
|
||||
page1:
|
||||
Title: Testpage without API Access
|
Loading…
x
Reference in New Issue
Block a user