mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Merged from branches/nzct-trunk. Use 'svn log -c <changeset> -g' for full commit message. Merge includes stability fixes and minor refactor of TableListField and ComplexTableField.
git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@63806 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
parent
f2abae719e
commit
634ed7b70c
@ -48,12 +48,6 @@ Object::useCustomClass('Datetime','SSDatetime',true);
|
||||
$path = Director::baseFolder().'/sapphire/parsers/';
|
||||
set_include_path(get_include_path() . PATH_SEPARATOR . $path);
|
||||
|
||||
/**
|
||||
* Register the {@link OpenIDAuthenticator OpenID authenticator}
|
||||
*/
|
||||
Authenticator::register_authenticator('MemberAuthenticator');
|
||||
Authenticator::set_default_authenticator('MemberAuthenticator');
|
||||
|
||||
/**
|
||||
* Define a default language different than english
|
||||
*/
|
||||
|
@ -12,7 +12,8 @@ abstract class CliController extends Controller {
|
||||
|
||||
function index() {
|
||||
// Always re-compile the manifest (?flush=1)
|
||||
ManifestBuilder::compileManifest();
|
||||
ManifestBuilder::update_db_tables(DB::getConn()->tableList(), $_ALL_CLASSES);
|
||||
ManifestBuilder::write_manifest();
|
||||
|
||||
foreach( ClassInfo::subclassesFor( $this->class ) as $subclass ) {
|
||||
echo $subclass;
|
||||
|
@ -23,7 +23,7 @@ class ArrayData extends ViewableData {
|
||||
public function __construct($array) {
|
||||
if(is_object($array)) {
|
||||
$this->array = self::object_to_array($array);
|
||||
} elseif(is_array($array) && ArrayLib::is_associative($array)) {
|
||||
} elseif(is_array($array) && (ArrayLib::is_associative($array) || count($array) === 0)) {
|
||||
$this->array = $array;
|
||||
} else {
|
||||
$this->array = $array;
|
||||
|
@ -44,8 +44,7 @@ class Convert extends Object {
|
||||
|
||||
} else {
|
||||
$val = str_replace(array('&','"',"'",'<','>'),array('&','"',''','<','>'),$val);
|
||||
$val = preg_replace('^[a-zA-Z0-9\-_]','_', $val);
|
||||
$val = preg_replace('^[0-9]*','', $val); //
|
||||
$val = preg_replace('/[^a-zA-Z0-9\-_]*/','', $val);
|
||||
return $val;
|
||||
}
|
||||
}
|
||||
|
@ -608,4 +608,4 @@ class ManifestBuilder {
|
||||
}
|
||||
|
||||
|
||||
?>
|
||||
?>
|
||||
|
@ -1705,10 +1705,9 @@ class DataObject extends ViewableData implements DataObjectInterface {
|
||||
}
|
||||
|
||||
if ($fields) foreach($fields as $name => $level) {
|
||||
if(!isset($this->original[$name])) continue;
|
||||
$changedFields[$name] = array(
|
||||
'before' => $this->original[$name],
|
||||
'after' => $this->record[$name],
|
||||
'before' => array_key_exists($name, $this->original) ? $this->original[$name] : null,
|
||||
'after' => array_key_exists($name, $this->record) ? $this->record[$name] : null,
|
||||
'level' => $level
|
||||
);
|
||||
}
|
||||
@ -1733,18 +1732,15 @@ class DataObject extends ViewableData implements DataObjectInterface {
|
||||
} else {
|
||||
$defaults = $this->stat('defaults');
|
||||
// if a field is not existing or has strictly changed
|
||||
if(!isset($this->record[$fieldName]) || $this->record[$fieldName] !== $val) {
|
||||
if(!array_key_exists($fieldName, $this->record) || $this->record[$fieldName] !== $val) {
|
||||
// TODO Add check for php-level defaults which are not set in the db
|
||||
// TODO Add check for hidden input-fields (readonly) which are not set in the db
|
||||
if(
|
||||
// Main non type-based check
|
||||
(isset($this->record[$fieldName]) && $this->record[$fieldName] != $val)
|
||||
) {
|
||||
// Non-strict check fails, so value really changed, e.g. "abc" != "cde"
|
||||
// At the very least, the type has changed
|
||||
$this->changed[$fieldName] = 1;
|
||||
|
||||
if(!array_key_exists($fieldName, $this->record) || $this->record[$fieldName] != $val) {
|
||||
// Value has changed as well, not just the type
|
||||
$this->changed[$fieldName] = 2;
|
||||
} else {
|
||||
// Record change-level 1 if only the type changed, e.g. 0 !== NULL
|
||||
$this->changed[$fieldName] = 1;
|
||||
}
|
||||
|
||||
// value is always saved back when strict check succeeds
|
||||
|
@ -424,27 +424,31 @@ class SiteTree extends DataObject {
|
||||
|
||||
|
||||
/**
|
||||
* Return a breadcrumb trail to this page.
|
||||
* Return a breadcrumb trail to this page. Excludes "hidden" pages
|
||||
* (with ShowInMenus=0).
|
||||
*
|
||||
* @param int $maxDepth The maximum depth to traverse.
|
||||
* @param boolean $unlinked Do not make page names links
|
||||
* @param string $stopAtPageType ClassName of a page to stop the upwards traversal.
|
||||
* @param boolean $showHidden Include pages marked with the attribute ShowInMenus = 0
|
||||
* @return string The breadcrumb trail.
|
||||
*/
|
||||
public function Breadcrumbs($maxDepth = 20, $unlinked = false,
|
||||
$stopAtPageType = false) {
|
||||
public function Breadcrumbs($maxDepth = 20, $unlinked = false, $stopAtPageType = false, $showHidden = false) {
|
||||
$page = $this;
|
||||
$parts = array();
|
||||
$i = 0;
|
||||
while(($page && (sizeof($parts) < $maxDepth)) ||
|
||||
($stopAtPageType && $page->ClassName != $stopAtPageType)) {
|
||||
if($page->ShowInMenus || ($page->ID == $this->ID)) {
|
||||
if($page->URLSegment == 'home') {
|
||||
$hasHome = true;
|
||||
while(
|
||||
$page
|
||||
&& (!$maxDepth || sizeof($parts) < $maxDepth)
|
||||
&& ($stopAtPageType && $page->ClassName != $stopAtPageType)
|
||||
) {
|
||||
if($showHidden || $page->ShowInMenus || ($page->ID == $this->ID)) {
|
||||
if($page->URLSegment == 'home') $hasHome = true;
|
||||
if(($page->ID == $this->ID) || $unlinked) {
|
||||
$parts[] = Convert::raw2xml($page->Title);
|
||||
} else {
|
||||
$parts[] = ("<a href=\"" . $page->Link() . "\">" . Convert::raw2xml($page->Title) . "</a>");
|
||||
}
|
||||
$parts[] = (($page->ID == $this->ID) || $unlinked)
|
||||
? Convert::raw2xml($page->Title)
|
||||
: ("<a href=\"" . $page->Link() . "\">" . Convert::raw2xml($page->Title) . "</a>");
|
||||
}
|
||||
$page = $page->Parent;
|
||||
}
|
||||
|
@ -23,6 +23,10 @@ class Boolean extends DBField {
|
||||
function Nice() {
|
||||
return ($this->value) ? "yes" : "no";
|
||||
}
|
||||
|
||||
function NiceAsBoolean() {
|
||||
return ($this->value) ? "true" : "false";
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves this field to the given data object.
|
||||
|
@ -24,27 +24,6 @@
|
||||
.ComplexTableField {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.ComplexTableField .PageControls {
|
||||
margin: 5px 0;
|
||||
text-align:center;
|
||||
display:block;
|
||||
margin-bottom: 5px;
|
||||
background:#ebeadb;
|
||||
border: 1px #cbc7b7 solid;
|
||||
}
|
||||
.ComplexTableField .PageControls * {
|
||||
display:inline;
|
||||
vertical-align: middle;
|
||||
font-weight: bold;
|
||||
}
|
||||
.ComplexTableField .PageControls .Last{
|
||||
float:right; display:block;
|
||||
width:40px; text-align:right;
|
||||
}
|
||||
.ComplexTableField .PageControls .First{
|
||||
float:left; display:block;
|
||||
width:40px; text-align:left;
|
||||
}
|
||||
|
||||
.ComplexTableField tbody td {
|
||||
cursor: pointer;
|
||||
|
@ -171,6 +171,33 @@ form .TableField .message {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.TableListField .PageControls {
|
||||
margin: 5px 0;
|
||||
text-align:center;
|
||||
display:block;
|
||||
margin-bottom: 5px;
|
||||
background:#ebeadb;
|
||||
border: 1px #cbc7b7 solid;
|
||||
position: relative;
|
||||
}
|
||||
.TableListField .PageControls * {
|
||||
display:inline;
|
||||
vertical-align: middle;
|
||||
font-weight: bold;
|
||||
}
|
||||
.TableListField .PageControls .Last{
|
||||
display: block;
|
||||
width: 40px;
|
||||
text-align: right;
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
top: 0px;
|
||||
}
|
||||
.TableListField .PageControls .First{
|
||||
float:left; display:block;
|
||||
width:40px; text-align:left;
|
||||
}
|
||||
|
||||
|
||||
#Pagination {
|
||||
margin-top: 10px;
|
||||
|
@ -93,7 +93,12 @@ class Email extends ViewableData {
|
||||
}
|
||||
|
||||
public function attachFile($filename, $attachedFilename = null, $mimetype = null) {
|
||||
$this->attachFileFromString(file_get_contents(Director::getAbsFile($filename)), $attachedFilename, $mimetype);
|
||||
$absoluteFileName = Director::getAbsFile($filename);
|
||||
if(file_exists($absoluteFileName)) {
|
||||
$this->attachFileFromString(file_get_contents($absoluteFileName), $attachedFilename, $mimetype);
|
||||
} else {
|
||||
user_error("Could not attach '$absoluteFileName' to email. File does not exist.", E_USER_NOTICE);
|
||||
}
|
||||
}
|
||||
|
||||
public function setFormat($format) {
|
||||
|
@ -40,7 +40,7 @@ class BankAccountField extends FormField {
|
||||
$field = new FieldGroup($this->name);
|
||||
$field->setID("{$this->name}_Holder");
|
||||
|
||||
$valueArr = $this->valueArray;
|
||||
$valueArr = $this->valueArr;
|
||||
|
||||
$valueArr = self::convert_format_nz($valueArr);
|
||||
|
||||
|
@ -29,7 +29,7 @@ class CompositeField extends FormField {
|
||||
protected $columnCount = null;
|
||||
|
||||
public function __construct($children = null) {
|
||||
if(is_a($children, 'FieldSet')) {
|
||||
if($children instanceof FieldSet) {
|
||||
$this->children = $children;
|
||||
} elseif(is_array($children)) {
|
||||
$this->children = new FieldSet($children);
|
||||
@ -239,7 +239,7 @@ class CompositeField extends FormField {
|
||||
|
||||
$valid = true;
|
||||
foreach($this->children as $idx => $child){
|
||||
$valid = ($child->validate($validator) && $valid);
|
||||
$valid = ($child && $child->validate($validator) && $valid);
|
||||
}
|
||||
|
||||
return $valid;
|
||||
|
@ -38,9 +38,8 @@ class DateField extends TextField {
|
||||
return $field;
|
||||
}
|
||||
|
||||
function jsValidation($formID = null)
|
||||
{
|
||||
if(!$formID)$formID = $this->form->FormName();
|
||||
function jsValidation() {
|
||||
$formID = $this->form->FormName();
|
||||
$error = _t('DateField.VALIDATIONJS', 'Please enter a valid date format (DD/MM/YYYY).');
|
||||
$jsFunc =<<<JS
|
||||
Behaviour.register({
|
||||
@ -77,7 +76,7 @@ JS;
|
||||
{
|
||||
$validator->validationError(
|
||||
$this->name,
|
||||
_t('DateField.VALIDDATEFORMAT', "Please enter a valid date format (DD/MM/YYYY)."),
|
||||
_t('DateField.VALIDDATEFORMAT', "Please enter a valid date format (DD/MM/YYYY)."),
|
||||
"validation",
|
||||
false
|
||||
);
|
||||
@ -130,5 +129,9 @@ class DateField_Disabled extends DateField {
|
||||
function php() {
|
||||
return true;
|
||||
}
|
||||
|
||||
function validate($validator) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
?>
|
@ -370,7 +370,7 @@ class TableField extends TableListField {
|
||||
if($dataObjects) {
|
||||
foreach ($dataObjects as $objectid => $fieldValues) {
|
||||
// we have to "sort" new data first, and process it in a seperate saveData-call (see setValue())
|
||||
if($objectid === "new") continue;
|
||||
if($objectid === "new") continue;
|
||||
|
||||
// extra data was creating fields, but
|
||||
if($this->extraData) {
|
||||
@ -381,7 +381,7 @@ class TableField extends TableListField {
|
||||
$obj = new $this->sourceClass();
|
||||
|
||||
if($ExistingValues) {
|
||||
$obj->ID = $objectid;
|
||||
$obj = DataObject::get_by_id($this->sourceClass, $objectid);
|
||||
}
|
||||
|
||||
// Legacy: Use the filter as a predefined relationship-ID
|
||||
@ -553,7 +553,10 @@ JS;
|
||||
if($data['methodName'] != 'delete'){
|
||||
$fields = $this->FieldSet();
|
||||
$fields = new FieldSet($fields);
|
||||
|
||||
foreach($fields as $field){
|
||||
$valid = $field->validate($this) && $valid;
|
||||
}
|
||||
return $valid;
|
||||
}else{
|
||||
return $valid;
|
||||
}
|
||||
|
@ -211,6 +211,8 @@ class TableListField extends FormField {
|
||||
*/
|
||||
public $fieldFormatting = array();
|
||||
|
||||
public $csvFieldFormatting = array();
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
@ -227,6 +229,8 @@ class TableListField extends FormField {
|
||||
*/
|
||||
protected $extraLinkParams;
|
||||
|
||||
protected $__cachedQuery;
|
||||
|
||||
function __construct($name, $sourceClass, $fieldList = null, $sourceFilter = null,
|
||||
$sourceSort = null, $sourceJoin = null) {
|
||||
|
||||
@ -259,6 +263,19 @@ class TableListField extends FormField {
|
||||
return $this->FieldHolder();
|
||||
}
|
||||
|
||||
static $url_handlers = array(
|
||||
'item/$ID' => 'handleItem',
|
||||
'$Action!' => '$Action',
|
||||
);
|
||||
|
||||
function sourceClass() {
|
||||
return $this->sourceClass;
|
||||
}
|
||||
|
||||
function handleItem($request) {
|
||||
return new TableListField_ItemRequest($this, $request->param('ID'));
|
||||
}
|
||||
|
||||
function FieldHolder() {
|
||||
|
||||
if($this->clickAction) {
|
||||
@ -321,7 +338,7 @@ JS
|
||||
return false;
|
||||
}
|
||||
|
||||
if($this->__cachedSQL) {
|
||||
if($this->__cachedQuery) {
|
||||
$query = $this->__cachedQuery;
|
||||
} else {
|
||||
$query = $this->__cachedQuery = $this->getQuery();
|
||||
@ -423,7 +440,7 @@ JS
|
||||
*/
|
||||
function getQuery() {
|
||||
if($this->customQuery) {
|
||||
$query = $this->customQuery;
|
||||
$query = clone $this->customQuery;
|
||||
$baseClass = ClassInfo::baseDataClass($this->sourceClass);
|
||||
$query->select[] = "{$baseClass}.ID AS ID";
|
||||
$query->select[] = "{$baseClass}.ClassName AS ClassName";
|
||||
@ -448,7 +465,7 @@ JS
|
||||
$query->orderby = $SQL_sort;
|
||||
}
|
||||
}
|
||||
return clone $query;
|
||||
return $query;
|
||||
}
|
||||
|
||||
function getCsvQuery() {
|
||||
@ -605,6 +622,8 @@ JS
|
||||
$summaryFields[] = new ArrayData(array(
|
||||
'Function' => $function,
|
||||
'SummaryValue' => $summaryValue,
|
||||
'Name' => DBField::create('Varchar', $fieldName),
|
||||
'Title' => DBField::create('Varchar', $fieldTitle),
|
||||
));
|
||||
}
|
||||
return new DataObjectSet($summaryFields);
|
||||
@ -879,6 +898,7 @@ JS
|
||||
function export() {
|
||||
$now = Date("d-m-Y-H-i");
|
||||
$fileName = "export-$now.csv";
|
||||
|
||||
$separator = $this->csvSeparator;
|
||||
$csvColumns = ($this->fieldListCsv) ? $this->fieldListCsv : $this->fieldList;
|
||||
$fileData = "";
|
||||
@ -889,14 +909,15 @@ JS
|
||||
}
|
||||
|
||||
// get data
|
||||
$dataQuery = $this->getCsvQuery();
|
||||
$records = $dataQuery->execute();
|
||||
|
||||
$sourceClass = $this->sourceClass;
|
||||
$dataobject = new $sourceClass();
|
||||
|
||||
// @todo Will create a large unpaginated dataobjectset based on how many records are in table (performance issue)
|
||||
$items = $dataobject->buildDataObjectSet($records, 'DataObjectSet');
|
||||
if(isset($this->customSourceItems)){
|
||||
$items = $this->customSourceItems;
|
||||
}else{
|
||||
$dataQuery = $this->getCsvQuery();
|
||||
$records = $dataQuery->execute();
|
||||
$sourceClass = $this->sourceClass;
|
||||
$dataobject = new $sourceClass();
|
||||
$items = $dataobject->buildDataObjectSet($records, 'DataObjectSet');
|
||||
}
|
||||
|
||||
$fieldItems = new DataObjectSet();
|
||||
if($items && $items->count()) foreach($items as $item) {
|
||||
@ -911,26 +932,31 @@ JS
|
||||
|
||||
if($fieldItems) {
|
||||
foreach($fieldItems as $fieldItem) {
|
||||
$columnData = array();
|
||||
$fields = $fieldItem->Fields();
|
||||
foreach($fields as $field) {
|
||||
// replace <br/ >s with newlines for csv
|
||||
$field->Value = str_replace('<br />', "\n", $field->Value);
|
||||
// remove double quotes
|
||||
$field->Value = str_replace('"', "", $field->Value);
|
||||
$fileData .= "\"" . $field->Value . "\"";
|
||||
if($field->Last()) {
|
||||
$fileData .= "\n";
|
||||
} else {
|
||||
$fileData .= $this->csvSeparator;
|
||||
|
||||
$value = $field->Value;
|
||||
|
||||
// TODO This should be replaced with casting
|
||||
if(array_key_exists($field->Name, $this->csvFieldFormatting)) {
|
||||
$format = str_replace('$value', "__VAL__", $this->csvFieldFormatting[$columnName]);
|
||||
$format = preg_replace('/\$([A-Za-z0-9-_]+)/','$item->$1', $format);
|
||||
$format = str_replace('__VAL__', '$value', $format);
|
||||
eval('$value = "' . $format . '";');
|
||||
}
|
||||
|
||||
$value = str_replace(array("\r", "\n"), "\n", $value);
|
||||
$tmpColumnData = "\"" . str_replace("\"", "\"\"", $value) . "\"";
|
||||
$columnData[] = $tmpColumnData;
|
||||
}
|
||||
|
||||
$fileData .= implode($separator, $columnData);
|
||||
$fileData .= "\n";
|
||||
}
|
||||
return HTTPRequest::send_file($fileData, $fileName);
|
||||
} else {
|
||||
user_error("No records found", E_USER_ERROR);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -948,12 +974,20 @@ JS
|
||||
Requirements::css(CMS_DIR . '/css/typography.css');
|
||||
Requirements::css(CMS_DIR . '/css/cms_right.css');
|
||||
Requirements::css(SAPPHIRE_DIR . '/css/TableListField_print.css');
|
||||
$vd = new ViewableData();
|
||||
return $vd->customise(array(
|
||||
'Content' => $this->customise(array(
|
||||
'Print' => true
|
||||
))->renderWith($this->template)
|
||||
))->renderWith('TableListField_printable');
|
||||
|
||||
unset($this->cachedSourceItems);
|
||||
$oldShowPagination = $this->showPagination;
|
||||
$this->showPagination = false;
|
||||
$oldLimit = ini_get('max_execution_time');
|
||||
set_time_limit(0);
|
||||
|
||||
|
||||
$result = $this->renderWith(array($this->template . '_printable', 'TableListField_printable'));
|
||||
|
||||
$this->showPagination = $oldShowPagination;
|
||||
set_time_limit($oldLimit);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
function PrintLink() {
|
||||
@ -1010,6 +1044,10 @@ JS
|
||||
$this->fieldFormatting = $formatting;
|
||||
}
|
||||
|
||||
function setCSVFieldFormatting($formatting) {
|
||||
$this->csvFieldFormatting = $formatting;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return String
|
||||
*/
|
||||
@ -1024,19 +1062,19 @@ JS
|
||||
// adding this to TODO probably add a method to the classes
|
||||
// to return they're translated string
|
||||
// added by ruibarreiros @ 27/11/2007
|
||||
return singleton($this->sourceClass)->singular_name();
|
||||
return $this->sourceClass ? singleton($this->sourceClass)->singular_name() : $this->Name();
|
||||
}
|
||||
|
||||
function NameSingular() {
|
||||
// same as Title()
|
||||
// added by ruibarreiros @ 27/11/2007
|
||||
return singleton($this->sourceClass)->singular_name();
|
||||
return $this->sourceClass ? singleton($this->sourceClass)->singular_name() : $this->Name();
|
||||
}
|
||||
|
||||
function NamePlural() {
|
||||
// same as Title()
|
||||
// added by ruibarreiros @ 27/11/2007
|
||||
return singleton($this->sourceClass)->plural_name();
|
||||
return $this->sourceClass ? singleton($this->sourceClass)->plural_name() : $this->Name();
|
||||
}
|
||||
|
||||
function setTemplate($template) {
|
||||
@ -1216,6 +1254,16 @@ class TableListField_Item extends ViewableData {
|
||||
return $this->parent->Can($mode);
|
||||
}
|
||||
|
||||
function Link() {
|
||||
if($this->parent->getForm()) {
|
||||
return Controller::join_links($this->parent->Link() . "item/" . $this->item->ID);
|
||||
} else {
|
||||
// allow for instanciation of this FormField outside of a controller/form
|
||||
// context (e.g. for unit tests)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all row-based actions not disallowed through permissions.
|
||||
* See TableListField->Action for a similiar dummy-function to work
|
||||
@ -1240,17 +1288,7 @@ class TableListField_Item extends ViewableData {
|
||||
|
||||
return $allowedActions;
|
||||
}
|
||||
|
||||
function Link() {
|
||||
if($this->parent->getForm()) {
|
||||
return Controller::join_links($this->parent->Link() . "item/" . $this->item->ID);
|
||||
} else {
|
||||
// allow for instanciation of this FormField outside of a controller/form
|
||||
// context (e.g. for unit tests)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function BaseLink() {
|
||||
user_error("TableListField_Item::BaseLink() deprecated, use Link() instead", E_USER_NOTICE);
|
||||
return $this->Link();
|
||||
@ -1293,7 +1331,78 @@ class TableListField_Item extends ViewableData {
|
||||
function isReadonly() {
|
||||
return $this->parent->Can('delete');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
class TableListField_ItemRequest extends RequestHandlingData {
|
||||
protected $ctf;
|
||||
protected $itemID;
|
||||
protected $methodName;
|
||||
|
||||
static $url_handlers = array(
|
||||
'$Action!' => '$Action',
|
||||
'' => 'index',
|
||||
);
|
||||
|
||||
function Link() {
|
||||
return $this->ctf->Link() . '/item/' . $this->itemID;
|
||||
}
|
||||
|
||||
function __construct($ctf, $itemID) {
|
||||
$this->ctf = $ctf;
|
||||
$this->itemID = $itemID;
|
||||
}
|
||||
|
||||
function delete() {
|
||||
if($this->ctf->Can('delete') !== true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->dataObj()->delete();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Return the data object being manipulated
|
||||
*/
|
||||
function dataObj() {
|
||||
// used to discover fields if requested and for population of field
|
||||
if(is_numeric($this->itemID)) {
|
||||
// we have to use the basedataclass, otherwise we might exclude other subclasses
|
||||
return DataObject::get_by_id(ClassInfo::baseDataClass(Object::getCustomClass($this->ctf->sourceClass())), $this->itemID);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the db-fieldname of the currently used has_one-relationship.
|
||||
*/
|
||||
function getParentIdName( $parentClass, $childClass ) {
|
||||
return $this->getParentIdNameRelation( $childClass, $parentClass, 'has_one' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually overwrites the parent-ID relations.
|
||||
* @see setParentClass()
|
||||
*
|
||||
* @param String $str Example: FamilyID (when one Individual has_one Family)
|
||||
*/
|
||||
function setParentIdName($str) {
|
||||
$this->parentIdName = $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the db-fieldname of the currently used relationship.
|
||||
*/
|
||||
function getParentIdNameRelation($parentClass, $childClass, $relation) {
|
||||
if($this->parentIdName) return $this->parentIdName;
|
||||
|
||||
$relations = singleton($parentClass)->$relation();
|
||||
$classes = ClassInfo::ancestry($childClass);
|
||||
foreach($relations as $k => $v) {
|
||||
if(array_key_exists($v, $classes)) return $k . 'ID';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
@ -4,13 +4,16 @@ GB_RefreshLink = "";
|
||||
ComplexTableField = Class.create();
|
||||
ComplexTableField.prototype = {
|
||||
|
||||
// TODO adjust dynamically
|
||||
popupWidth: 560,
|
||||
popupHeight: 390,
|
||||
// These are defaults used if setPopupSize encounters errors
|
||||
defaultPopupWidth: 560,
|
||||
defaultPopupHeight: 390,
|
||||
|
||||
initialize: function() {
|
||||
var rules = {};
|
||||
rules['#'+this.id+' table.data a.popuplink'] = {onclick: this.openPopup.bind(this)};
|
||||
|
||||
// Assume that the delete link uses the deleteRecord method
|
||||
rules['#'+this.id+' table.data a.deletelink'] = {onclick: this.deleteRecord.bind(this)};
|
||||
rules['#'+this.id+' table.data tbody td'] = {onclick: this.openPopup.bind(this)};
|
||||
|
||||
// invoke row action-link based on default-action set in classname
|
||||
@ -25,10 +28,22 @@ ComplexTableField.prototype = {
|
||||
}
|
||||
Behaviour.register('ComplexTableField_'+this.id,rules);
|
||||
|
||||
this.setPopupSize();
|
||||
|
||||
// HACK If already in a popup, we can't allow add (doesn't save existing relation correctly)
|
||||
if(window != top) $$('#'+this.id+' table.data a.addlink').each(function(el) {Element.hide(el);});
|
||||
},
|
||||
|
||||
setPopupSize: function() {
|
||||
try {
|
||||
this.popupHeight = parseInt($(this.id + '_PopupHeight').value);
|
||||
this.popupWidth = parseInt($(this.id + '_PopupWidth').value);
|
||||
} catch (ex) {
|
||||
this.popupHeight = this.defaultPopupHeight;
|
||||
this.popupWidth = this.defaultPopupWidth;
|
||||
}
|
||||
},
|
||||
|
||||
getDefaultAction: function() {
|
||||
// try to get link class from <td class="action default"><a href="...
|
||||
var links = $$('#'+this.id+' table.data tbody .default a');
|
||||
@ -45,6 +60,8 @@ ComplexTableField.prototype = {
|
||||
// of opening a nested lightwindow
|
||||
if(window != top) return true;
|
||||
|
||||
this.setPopupSize();
|
||||
|
||||
var el,type;
|
||||
var popupLink = "";
|
||||
if(_popupLink) {
|
||||
|
@ -102,6 +102,7 @@ function require(fieldName,cachedError) {
|
||||
}
|
||||
|
||||
var baseEl;
|
||||
var fieldHolder = el;
|
||||
|
||||
// Sometimes require events are triggered of
|
||||
// associative elements like labels ;-p
|
||||
@ -155,7 +156,8 @@ function require(fieldName,cachedError) {
|
||||
|
||||
} else {
|
||||
if(!hasHadFormError()) {
|
||||
clearErrorMessage(baseEl.parentNode);
|
||||
if(baseEl) fieldHolder = baseEl.parentNode;
|
||||
clearErrorMessage(fieldHolder);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -195,6 +197,12 @@ function findParentLabel(el) {
|
||||
return findParentLabel(el.parentNode);
|
||||
}
|
||||
} else {
|
||||
// Try to find a label with a for value of this field.
|
||||
if(el.id) {
|
||||
var labels = $$('label[for=' + el.id + ']');
|
||||
if(labels && labels.length > 0) return labels[0].innerHTML;
|
||||
}
|
||||
|
||||
return findParentLabel(el.parentNode);
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ abstract class Authenticator extends Object {
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $authenticators = array();
|
||||
private static $authenticators = array('MemberAuthenticator');
|
||||
|
||||
/**
|
||||
* Used to influence the order of authenticators on the login-screen
|
||||
@ -24,7 +24,7 @@ abstract class Authenticator extends Object {
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private static $default_authenticator = '';
|
||||
private static $default_authenticator = 'MemberAuthenticator';
|
||||
|
||||
|
||||
/**
|
||||
@ -107,7 +107,9 @@ abstract class Authenticator extends Object {
|
||||
*/
|
||||
public static function unregister_authenticator($authenticator) {
|
||||
if(call_user_func(array($authenticator, 'on_unregister')) === true) {
|
||||
unset(self::$authenticators[$authenticator]);
|
||||
if(in_array($authenticator, self::$authenticators)) {
|
||||
unset(self::$authenticators[array_search($authenticator, self::$authenticators)]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,7 @@ abstract class LoginForm extends Form {
|
||||
|
||||
public function getAuthenticator() {
|
||||
if(!class_exists($this->authenticator_class) || !is_subclass_of($this->authenticator_class, 'Authenticator')) {
|
||||
user_error('The form uses an invalid authenticator class!', E_USER_ERROR);
|
||||
user_error("The form uses an invalid authenticator class! '{$this->authenticator_class}' is not a subclass of 'Authenticator'", E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,8 @@
|
||||
*/
|
||||
class MemberLoginForm extends LoginForm {
|
||||
|
||||
protected $authenticator_class = 'MemberAuthenticator';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
@ -22,11 +24,13 @@ class MemberLoginForm extends LoginForm {
|
||||
* @param bool $checkCurrentUser If set to TRUE, it will be checked if a
|
||||
* the user is currently logged in, and if
|
||||
* so, only a logout button will be rendered
|
||||
* @param string $authenticatorClassName Name of the authenticator class that this form uses.
|
||||
*/
|
||||
function __construct($controller, $name, $fields = null, $actions = null,
|
||||
$checkCurrentUser = true) {
|
||||
|
||||
$this->authenticator_class = 'MemberAuthenticator';
|
||||
// This is now set on the class directly to make it easier to create subclasses
|
||||
// $this->authenticator_class = $authenticatorClassName;
|
||||
|
||||
$customCSS = project() . '/css/member_login.css';
|
||||
if(Director::fileExists($customCSS)) {
|
||||
@ -48,10 +52,16 @@ class MemberLoginForm extends LoginForm {
|
||||
new HiddenField("AuthenticationMethod", null, $this->authenticator_class, $this),
|
||||
new TextField("Email", _t('Member.EMAIL'),
|
||||
Session::get('SessionForms.MemberLoginForm.Email'), null, $this),
|
||||
new EncryptField("Password", _t('Member.PASSWORD'), null, $this),
|
||||
new CheckboxField("Remember", _t('Member.REMEMBERME', "Remember me next time?"),
|
||||
Session::get('SessionForms.MemberLoginForm.Remember'), $this)
|
||||
new EncryptField("Password", _t('Member.PASSWORD'), null, $this)
|
||||
);
|
||||
if(Security::$autologin_enabled) {
|
||||
$fields->push(new CheckboxField(
|
||||
"Remember",
|
||||
_t('Member.REMEMBERME', "Remember me next time?"),
|
||||
Session::get('SessionForms.MemberLoginForm.Remember'),
|
||||
$this
|
||||
));
|
||||
}
|
||||
}
|
||||
if(!$actions) {
|
||||
$actions = new FieldSet(
|
||||
@ -109,7 +119,7 @@ class MemberLoginForm extends LoginForm {
|
||||
Session::clear("BackURL");
|
||||
Director::redirect($backURL);
|
||||
} else {
|
||||
Director::redirect(Security::default_login_dest());
|
||||
Director::redirectBack();
|
||||
}
|
||||
} else {
|
||||
Session::set('SessionForms.MemberLoginForm.Email', $data['Email']);
|
||||
@ -187,7 +197,7 @@ class MemberLoginForm extends LoginForm {
|
||||
$member->sendInfo('forgotPassword', array('PasswordResetLink' =>
|
||||
Security::getPasswordResetLink($member->AutoLoginHash)));
|
||||
|
||||
Director::redirect('Security/passwordsent/?email=' . urlencode($data['Email']));
|
||||
Director::redirect('Security/passwordsent/' . urlencode($data['Email']));
|
||||
|
||||
} else if($data['Email']) {
|
||||
$this->sessionMessage(
|
||||
|
@ -52,6 +52,14 @@ class Security extends Controller {
|
||||
*/
|
||||
protected static $useSalt = true;
|
||||
|
||||
/**
|
||||
* Showing "Remember me"-checkbox
|
||||
* on loginform, and saving encrypted credentials to a cookie.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public static $autologin_enabled = true;
|
||||
|
||||
/**
|
||||
* Location of word list to use for generating passwords
|
||||
*
|
||||
@ -207,8 +215,7 @@ class Security extends Controller {
|
||||
|
||||
$authenticators = Authenticator::get_authenticators();
|
||||
if(in_array($authenticator, $authenticators)) {
|
||||
return call_user_func(array($authenticator, 'get_login_form'),
|
||||
$this);
|
||||
return call_user_func(array($authenticator, 'get_login_form'), $this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
<% if Markable %><th width="16"> </th><% end_if %>
|
||||
<th><i>$SummaryTitle</i></th>
|
||||
<% control SummaryFields %>
|
||||
<th<% if Function %> class="$Function"<% end_if %>>$SummaryValue</th>
|
||||
<th class="field-$Name.HTMLATT<% if Function %> $Function<% end_if %>">$SummaryValue</th>
|
||||
<% end_control %>
|
||||
<% if Can(delete) %><th width="18"> </th><% end_if %>
|
@ -1,9 +1,9 @@
|
||||
<div id="$id" class="$CSSClasses TableField field">
|
||||
<div id="$id" class="$CSSClasses field">
|
||||
<% if Print %><% else %><% include TableListField_PageControls %><% end_if %>
|
||||
<table class="data">
|
||||
<thead>
|
||||
<tr>
|
||||
<% if Markable %><th width="16"> </th><% end_if %>
|
||||
<% if Markable %><th width="16"><% if MarkableTitle %>$MarkableTitle<% else %> <% end_if %></th><% end_if %>
|
||||
<% if Print %>
|
||||
<% control Headings %>
|
||||
<th class="$Name">
|
||||
|
@ -241,6 +241,20 @@ class DataObjectTest extends SapphireTest {
|
||||
),
|
||||
'Changed fields are correctly detected while ignoring type changes (level=2)'
|
||||
);
|
||||
|
||||
$newPage = new Page();
|
||||
$newPage->Title = "New Page Title";
|
||||
$this->assertEquals(
|
||||
$newPage->getChangedFields(false, 2),
|
||||
array(
|
||||
'Title' => array(
|
||||
'before' => null,
|
||||
'after' => 'New Page Title',
|
||||
'level' => 2
|
||||
)
|
||||
),
|
||||
'Initialised fields are correctly detected as full changes'
|
||||
);
|
||||
}
|
||||
|
||||
function testRandomSort() {
|
||||
|
@ -11,6 +11,11 @@ class TableListFieldTest extends SapphireTest {
|
||||
"D" => "Col D",
|
||||
"E" => "Col E",
|
||||
));
|
||||
// A TableListField must be inside a form for its links to be generated
|
||||
$form = new Form(new TableListFieldTest_TestController(), "TestForm", new FieldSet(
|
||||
$table
|
||||
), new FieldSet());
|
||||
|
||||
$result = $table->FieldHolder();
|
||||
|
||||
// Do a quick check to ensure that some of the D() and getE() values got through
|
||||
@ -28,6 +33,11 @@ class TableListFieldTest extends SapphireTest {
|
||||
"D" => "Col D",
|
||||
"E" => "Col E",
|
||||
));
|
||||
// A TableListField must be inside a form for its links to be generated
|
||||
$form = new Form(new TableListFieldTest_TestController(), "TestForm", new FieldSet(
|
||||
$table
|
||||
), new FieldSet());
|
||||
|
||||
$items = $table->sourceItems();
|
||||
$this->assertNotNull($items);
|
||||
|
||||
@ -44,6 +54,11 @@ class TableListFieldTest extends SapphireTest {
|
||||
"D" => "Col D",
|
||||
"E" => "Col E",
|
||||
));
|
||||
// A TableListField must be inside a form for its links to be generated
|
||||
$form = new Form(new TableListFieldTest_TestController(), "TestForm", new FieldSet(
|
||||
$table
|
||||
), new FieldSet());
|
||||
|
||||
$table->ShowPagination = true;
|
||||
$table->PageSize = 2;
|
||||
|
||||
@ -63,6 +78,11 @@ class TableListFieldTest extends SapphireTest {
|
||||
"D" => "Col D",
|
||||
"E" => "Col E",
|
||||
));
|
||||
// A TableListField must be inside a form for its links to be generated
|
||||
$form = new Form(new TableListFieldTest_TestController(), "TestForm", new FieldSet(
|
||||
$table
|
||||
), new FieldSet());
|
||||
|
||||
$table->ShowPagination = true;
|
||||
$table->PageSize = 2;
|
||||
$_REQUEST['ctf']['Tester']['start'] = 2;
|
||||
@ -73,6 +93,46 @@ class TableListFieldTest extends SapphireTest {
|
||||
$itemMap = $items->toDropdownMap("ID", "A") ;
|
||||
$this->assertEquals(array(3 => "a3", 4 => "a4"), $itemMap);
|
||||
}
|
||||
|
||||
function testCsvExport() {
|
||||
$table = new TableListField("Tester", "TableListFieldTest_CsvExport", array(
|
||||
"A" => "Col A",
|
||||
"B" => "Col B"
|
||||
));
|
||||
|
||||
$form = new Form(new TableListFieldTest_TestController(), "TestForm", new FieldSet(
|
||||
$table
|
||||
), new FieldSet());
|
||||
|
||||
$csvResponse = $table->export();
|
||||
|
||||
$csvOutput = $csvResponse->getBody();
|
||||
|
||||
$this->assertNotEquals($csvOutput, false);
|
||||
|
||||
// Create a temporary file and write the CSV to it.
|
||||
$csvFileName = tempnam(TEMP_FOLDER, 'csv-export');
|
||||
$csvFile = fopen($csvFileName, 'w');
|
||||
fwrite($csvFile, $csvOutput);
|
||||
fclose($csvFile);
|
||||
|
||||
$csvFile = fopen($csvFileName, 'r');
|
||||
$csvRow = fgetcsv($csvFile);
|
||||
$this->assertEquals(
|
||||
$csvRow,
|
||||
array('Col A', 'Col B')
|
||||
);
|
||||
|
||||
$csvRow = fgetcsv($csvFile);
|
||||
$this->assertEquals(
|
||||
$csvRow,
|
||||
array('"A field, with a comma"', 'A second field')
|
||||
);
|
||||
|
||||
fclose($csvFile);
|
||||
|
||||
unlink($csvFileName);
|
||||
}
|
||||
}
|
||||
|
||||
class TableListFieldTest_Obj extends DataObject implements TestOnly {
|
||||
@ -89,5 +149,17 @@ class TableListFieldTest_Obj extends DataObject implements TestOnly {
|
||||
function getE() {
|
||||
return $this->A . '-e';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TableListFieldTest_CsvExport extends DataObject implements TestOnly {
|
||||
static $db = array(
|
||||
"A" => "Varchar",
|
||||
"B" => "Varchar"
|
||||
);
|
||||
}
|
||||
|
||||
class TableListFieldTest_TestController extends Controller {
|
||||
function Link() {
|
||||
return "TableListFieldTest_TestController/";
|
||||
}
|
||||
}
|
@ -19,4 +19,8 @@ TableListFieldTest_Obj:
|
||||
A: a5
|
||||
B: b5
|
||||
C: c5
|
||||
|
||||
|
||||
TableListFieldTest_CsvExport:
|
||||
exportone:
|
||||
A: "\"A field, with a comma\""
|
||||
B: A second field
|
@ -10,6 +10,33 @@ class SecurityTest extends FunctionalTest {
|
||||
|
||||
protected $autoFollowRedirection = false;
|
||||
|
||||
protected $priorAuthenticators = array();
|
||||
|
||||
protected $priorDefaultAuthenticator = null;
|
||||
|
||||
function setUp() {
|
||||
// This test assumes that MemberAuthenticator is present and the default
|
||||
$this->priorAuthenticators = Authenticator::get_authenticators();
|
||||
$this->priorDefaultAuthenticator = Authenticator::get_default_authenticator();
|
||||
|
||||
Authenticator::register('MemberAuthenticator');
|
||||
Authenticator::set_default_authenticator('MemberAuthenticator');
|
||||
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
// Restore selected authenticator
|
||||
|
||||
// MemberAuthenticator might not actually be present
|
||||
if(!in_array('MemberAuthenticator', $this->priorAuthenticators)) {
|
||||
Authenticator::unregister('MemberAuthenticator');
|
||||
}
|
||||
Authenticator::set_default_authenticator($this->priorDefaultAuthenticator);
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the login form redirects to the change password form after logging in with an expired password
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user