diff --git a/_config.php b/_config.php index 6368f6179..53de22f9f 100644 --- a/_config.php +++ b/_config.php @@ -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 */ diff --git a/cli/CliController.php b/cli/CliController.php index 878387f03..4d0f71b35 100755 --- a/cli/CliController.php +++ b/cli/CliController.php @@ -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; diff --git a/core/ArrayData.php b/core/ArrayData.php index d03296c87..6c06eb0b8 100755 --- a/core/ArrayData.php +++ b/core/ArrayData.php @@ -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; diff --git a/core/Convert.php b/core/Convert.php index 87bf10fce..d98d86a2c 100755 --- a/core/Convert.php +++ b/core/Convert.php @@ -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; } } diff --git a/core/ManifestBuilder.php b/core/ManifestBuilder.php index cbae853a4..b5e0d4247 100644 --- a/core/ManifestBuilder.php +++ b/core/ManifestBuilder.php @@ -608,4 +608,4 @@ class ManifestBuilder { } -?> \ No newline at end of file +?> diff --git a/core/model/DataObject.php b/core/model/DataObject.php index 428e4968d..02a9a92bb 100644 --- a/core/model/DataObject.php +++ b/core/model/DataObject.php @@ -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 diff --git a/core/model/SiteTree.php b/core/model/SiteTree.php index d2c4e61e7..afe172db9 100644 --- a/core/model/SiteTree.php +++ b/core/model/SiteTree.php @@ -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[] = ("Link() . "\">" . Convert::raw2xml($page->Title) . ""); } - $parts[] = (($page->ID == $this->ID) || $unlinked) - ? Convert::raw2xml($page->Title) - : ("Link() . "\">" . Convert::raw2xml($page->Title) . ""); } $page = $page->Parent; } diff --git a/core/model/fieldtypes/Boolean.php b/core/model/fieldtypes/Boolean.php index c2e4b5b73..92e637007 100644 --- a/core/model/fieldtypes/Boolean.php +++ b/core/model/fieldtypes/Boolean.php @@ -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. diff --git a/css/ComplexTableField.css b/css/ComplexTableField.css index 906b40ddd..2e51cb417 100755 --- a/css/ComplexTableField.css +++ b/css/ComplexTableField.css @@ -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; diff --git a/css/TableListField.css b/css/TableListField.css index 712a28b17..42a99c741 100644 --- a/css/TableListField.css +++ b/css/TableListField.css @@ -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; diff --git a/email/Email.php b/email/Email.php index 4d029c238..cac8dce20 100755 --- a/email/Email.php +++ b/email/Email.php @@ -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) { diff --git a/forms/BankAccountField.php b/forms/BankAccountField.php index c7dbcbbf4..0e5887bb0 100755 --- a/forms/BankAccountField.php +++ b/forms/BankAccountField.php @@ -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); diff --git a/forms/CompositeField.php b/forms/CompositeField.php index fc1d957f8..543027c97 100755 --- a/forms/CompositeField.php +++ b/forms/CompositeField.php @@ -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; diff --git a/forms/DateField.php b/forms/DateField.php index b8d59744b..bf76c9f27 100755 --- a/forms/DateField.php +++ b/forms/DateField.php @@ -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 =<<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; + } } ?> \ No newline at end of file diff --git a/forms/TableField.php b/forms/TableField.php index 3b6779814..b7c1c0496 100644 --- a/forms/TableField.php +++ b/forms/TableField.php @@ -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; } diff --git a/forms/TableListField.php b/forms/TableListField.php index 816db38fc..8d14917b1 100755 --- a/forms/TableListField.php +++ b/forms/TableListField.php @@ -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
s with newlines for csv - $field->Value = str_replace('
', "\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'); } - } -?> \ No newline at end of file +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; + } +} +?> diff --git a/javascript/ComplexTableField.js b/javascript/ComplexTableField.js index 03ad901f7..ea71ad12b 100755 --- a/javascript/ComplexTableField.js +++ b/javascript/ComplexTableField.js @@ -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 0) return labels[0].innerHTML; + } + return findParentLabel(el.parentNode); } } diff --git a/security/Authenticator.php b/security/Authenticator.php index 9a1c4eb0a..e61ca898b 100644 --- a/security/Authenticator.php +++ b/security/Authenticator.php @@ -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)]); + } }; } diff --git a/security/LoginForm.php b/security/LoginForm.php index 21b2a2b1e..92537bf36 100644 --- a/security/LoginForm.php +++ b/security/LoginForm.php @@ -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; } diff --git a/security/MemberLoginForm.php b/security/MemberLoginForm.php index 54f618a65..05f38d65f 100644 --- a/security/MemberLoginForm.php +++ b/security/MemberLoginForm.php @@ -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( diff --git a/security/Security.php b/security/Security.php index 804df3893..19bf7d341 100644 --- a/security/Security.php +++ b/security/Security.php @@ -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); } } diff --git a/templates/Includes/TableListField_Summary.ss b/templates/Includes/TableListField_Summary.ss index 5480c03bf..f42a6820b 100755 --- a/templates/Includes/TableListField_Summary.ss +++ b/templates/Includes/TableListField_Summary.ss @@ -1,6 +1,6 @@ <% if Markable %> <% end_if %> $SummaryTitle <% control SummaryFields %> - class="$Function"<% end_if %>>$SummaryValue + $SummaryValue <% end_control %> <% if Can(delete) %> <% end_if %> \ No newline at end of file diff --git a/templates/TableListField.ss b/templates/TableListField.ss index 91d6ca07b..9e792bdba 100755 --- a/templates/TableListField.ss +++ b/templates/TableListField.ss @@ -1,9 +1,9 @@ -
+
<% if Print %><% else %><% include TableListField_PageControls %><% end_if %> - <% if Markable %><% end_if %> + <% if Markable %><% end_if %> <% if Print %> <% control Headings %>
 <% if MarkableTitle %>$MarkableTitle<% else %> <% end_if %> diff --git a/tests/DataObjectTest.php b/tests/DataObjectTest.php index 6f82feae0..deef7753b 100644 --- a/tests/DataObjectTest.php +++ b/tests/DataObjectTest.php @@ -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() { diff --git a/tests/forms/TableListFieldTest.php b/tests/forms/TableListFieldTest.php index 20469e149..47e512c55 100644 --- a/tests/forms/TableListFieldTest.php +++ b/tests/forms/TableListFieldTest.php @@ -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/"; + } +} \ No newline at end of file diff --git a/tests/forms/TableListFieldTest.yml b/tests/forms/TableListFieldTest.yml index dc46ee327..18def3435 100644 --- a/tests/forms/TableListFieldTest.yml +++ b/tests/forms/TableListFieldTest.yml @@ -19,4 +19,8 @@ TableListFieldTest_Obj: A: a5 B: b5 C: c5 - \ No newline at end of file + +TableListFieldTest_CsvExport: + exportone: + A: "\"A field, with a comma\"" + B: A second field \ No newline at end of file diff --git a/tests/security/SecurityTest.php b/tests/security/SecurityTest.php index d2fc33dc2..08cac634b 100644 --- a/tests/security/SecurityTest.php +++ b/tests/security/SecurityTest.php @@ -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 */