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:
Hayden Smith 2008-10-08 02:00:12 +00:00
parent f2abae719e
commit 634ed7b70c
28 changed files with 416 additions and 131 deletions

View File

@ -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
*/

View File

@ -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;

View File

@ -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;

View File

@ -44,8 +44,7 @@ class Convert extends Object {
} else {
$val = str_replace(array('&','"',"'",'<','>'),array('&amp;','&quot;','&#39;','&lt;','&gt;'),$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;
}
}

View File

@ -608,4 +608,4 @@ class ManifestBuilder {
}
?>
?>

View File

@ -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

View File

@ -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;
}

View File

@ -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.

View File

@ -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;

View File

@ -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;

View File

@ -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) {

View File

@ -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);

View File

@ -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;

View File

@ -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;
}
}
?>

View File

@ -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;
}

View File

@ -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;
}
}
?>

View File

@ -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) {

View File

@ -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);
}
}

View File

@ -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)]);
}
};
}

View File

@ -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;
}

View File

@ -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(

View File

@ -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);
}
}

View File

@ -1,6 +1,6 @@
<% if Markable %><th width="16">&nbsp;</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">&nbsp;</th><% end_if %>

View File

@ -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">&nbsp;</th><% end_if %>
<% if Markable %><th width="16"><% if MarkableTitle %>$MarkableTitle<% else %>&nbsp;<% end_if %></th><% end_if %>
<% if Print %>
<% control Headings %>
<th class="$Name">

View File

@ -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() {

View File

@ -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/";
}
}

View File

@ -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

View File

@ -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
*/