diff --git a/docs/en/02_Developer_Guides/03_Forms/06_Tabbed_Forms.md b/docs/en/02_Developer_Guides/03_Forms/06_Tabbed_Forms.md index f2826de0d..43ddf31e5 100644 --- a/docs/en/02_Developer_Guides/03_Forms/06_Tabbed_Forms.md +++ b/docs/en/02_Developer_Guides/03_Forms/06_Tabbed_Forms.md @@ -19,7 +19,7 @@ name, and normally they all exist under the `Root` [api:TabSet]. display up to two levels of tabs in the interface. If you want to group data further than that, try [api:ToggleField]. -## Adding a field from a tab. +## Adding a field to a tab :::php $fields->addFieldToTab('Root.Main', new TextField(..)); diff --git a/forms/gridfield/GridFieldAddExistingAutocompleter.php b/forms/gridfield/GridFieldAddExistingAutocompleter.php index 201c52736..937f5218f 100644 --- a/forms/gridfield/GridFieldAddExistingAutocompleter.php +++ b/forms/gridfield/GridFieldAddExistingAutocompleter.php @@ -59,7 +59,7 @@ class GridFieldAddExistingAutocompleter * 'Team.Name' * ) * - * @var Array + * @var array */ protected $searchFields = array(); @@ -69,7 +69,7 @@ class GridFieldAddExistingAutocompleter protected $resultsFormat = '$Title'; /** - * @var String Text shown on the search field, instructing what to search for. + * @var string Text shown on the search field, instructing what to search for. */ protected $placeholderText; @@ -90,7 +90,7 @@ class GridFieldAddExistingAutocompleter /** * * @param GridField $gridField - * @return string - HTML + * @return string[] - HTML */ public function getHTMLFragments($gridField) { $dataClass = $gridField->getList()->dataClass(); @@ -220,17 +220,20 @@ class GridFieldAddExistingAutocompleter ->limit($this->getResultsLimit()); $json = array(); - $originalSourceFileComments = Config::inst()->get('SSViewer', 'source_file_comments'); + Config::nest(); Config::inst()->update('SSViewer', 'source_file_comments', false); + $viewer = SSViewer::fromString($this->resultsFormat); foreach($results as $result) { - $json[$result->ID] = html_entity_decode(SSViewer::fromString($this->resultsFormat)->process($result)); + $json[$result->ID] = html_entity_decode($viewer->process($result)); } - Config::inst()->update('SSViewer', 'source_file_comments', $originalSourceFileComments); + Config::unnest(); return Convert::array2json($json); } /** - * @param String + * @param string $format + * + * @return $this */ public function setResultsFormat($format) { $this->resultsFormat = $format; @@ -238,7 +241,7 @@ class GridFieldAddExistingAutocompleter } /** - * @return String + * @return string */ public function getResultsFormat() { return $this->resultsFormat; @@ -252,10 +255,11 @@ class GridFieldAddExistingAutocompleter */ public function setSearchList(SS_List $list) { $this->searchList = $list; + return $this; } /** - * @param Array + * @param array $fields */ public function setSearchFields($fields) { $this->searchFields = $fields; @@ -263,7 +267,7 @@ class GridFieldAddExistingAutocompleter } /** - * @return Array + * @return array */ public function getSearchFields() { return $this->searchFields; @@ -274,8 +278,8 @@ class GridFieldAddExistingAutocompleter * Falls back to {@link DataObject->summaryFields()} if * no custom search fields are defined. * - * @param String the class name - * @return Array|null names of the searchable fields + * @param string $dataClass the class name + * @return array|null names of the searchable fields */ public function scaffoldSearchFields($dataClass) { $obj = singleton($dataClass); @@ -311,8 +315,9 @@ class GridFieldAddExistingAutocompleter } /** - * @param String The class of the object being searched for - * @return String + * @param string $dataClass The class of the object being searched for + * + * @return string */ public function getPlaceholderText($dataClass) { $searchFields = ($this->getSearchFields()) @@ -344,10 +349,13 @@ class GridFieldAddExistingAutocompleter } /** - * @param String + * @param string $text + * + * @return $this */ public function setPlaceholderText($text) { $this->placeholderText = $text; + return $this; } /** @@ -361,8 +369,11 @@ class GridFieldAddExistingAutocompleter /** * @param int $limit + * + * @return $this */ public function setResultsLimit($limit) { $this->resultsLimit = $limit; + return $this; } } diff --git a/model/ArrayList.php b/model/ArrayList.php index 24d4db8b0..903b04421 100644 --- a/model/ArrayList.php +++ b/model/ArrayList.php @@ -431,6 +431,7 @@ class ArrayList extends ViewableData implements SS_List, SS_Filterable, SS_Sorta // This the main sorting algorithm that supports infinite sorting params $multisortArgs = array(); $values = array(); + $firstRun = true; foreach($columnsToSort as $column => $direction) { // The reason these are added to columns is of the references, otherwise when the foreach // is done, all $values and $direction look the same @@ -438,7 +439,7 @@ class ArrayList extends ViewableData implements SS_List, SS_Filterable, SS_Sorta $sortDirection[$column] = $direction; // We need to subtract every value into a temporary array for sorting foreach($this->items as $index => $item) { - $values[$column][] = $this->extractValue($item, $column); + $values[$column][] = strtolower($this->extractValue($item, $column)); } // PHP 5.3 requires below arguments to be reference when using array_multisort together // with call_user_func_array @@ -446,6 +447,10 @@ class ArrayList extends ViewableData implements SS_List, SS_Filterable, SS_Sorta $multisortArgs[] = &$values[$column]; // First argument is the direction to be sorted, $multisortArgs[] = &$sortDirection[$column]; + if ($firstRun) { + $multisortArgs[] = defined('SORT_NATURAL') ? SORT_NATURAL : SORT_STRING; + } + $firstRun = false; } $multisortArgs[] = &$originalKeys; diff --git a/model/HasManyList.php b/model/HasManyList.php index a9cac0818..afc4979f3 100644 --- a/model/HasManyList.php +++ b/model/HasManyList.php @@ -8,6 +8,9 @@ */ class HasManyList extends RelationList { + /** + * @var string + */ protected $foreignKey; /** @@ -34,6 +37,10 @@ class HasManyList extends RelationList { return $this->foreignKey; } + /** + * @param null|int $id + * @return array + */ protected function foreignIDFilter($id = null) { if ($id === null) $id = $this->getForeignID(); @@ -51,7 +58,7 @@ class HasManyList extends RelationList { * * It does so by setting the relationFilters. * - * @param $item The DataObject to be added, or its ID + * @param DataObject|int $item The DataObject to be added, or its ID */ public function add($item) { if(is_numeric($item)) { @@ -83,7 +90,7 @@ class HasManyList extends RelationList { * * Doesn't actually remove the item, it just clears the foreign key value. * - * @param $itemID The ID of the item to be removed. + * @param int $itemID The ID of the item to be removed. */ public function removeByID($itemID) { $item = $this->byID($itemID); @@ -95,7 +102,7 @@ class HasManyList extends RelationList { * Remove an item from this relation. * Doesn't actually remove the item, it just clears the foreign key value. * - * @param $item The DataObject to be removed + * @param DataObject $item The DataObject to be removed * @todo Maybe we should delete the object instead? */ public function remove($item) { diff --git a/model/ManyManyList.php b/model/ManyManyList.php index 54df3b77b..e5b041195 100644 --- a/model/ManyManyList.php +++ b/model/ManyManyList.php @@ -47,7 +47,7 @@ class ManyManyList extends RelationList { * @param string $joinTable The name of the table whose entries define the content of this many_many relation. * @param string $localKey The key in the join table that maps to the dataClass' PK. * @param string $foreignKey The key in the join table that maps to joined class' PK. - * @param string $extraFields A map of field => fieldtype of extra fields on the join table. + * @param array $extraFields A map of field => fieldtype of extra fields on the join table. * * @example new ManyManyList('Group','Group_Members', 'GroupID', 'MemberID'); */ @@ -151,9 +151,9 @@ class ManyManyList extends RelationList { * Return a filter expression for when getting the contents of the * relationship for some foreign ID * - * @param int $id + * @param int|null $id * - * @return string + * @return array */ protected function foreignIDFilter($id = null) { if ($id === null) { @@ -176,7 +176,7 @@ class ManyManyList extends RelationList { * entries. However some subclasses of ManyManyList (Member_GroupSet) modify foreignIDFilter to * include additional calculated entries, so we need different filters when reading and when writing * - * @param array|integer $id (optional) An ID or an array of IDs - if not provided, will use the current ids + * @param array|int|null $id (optional) An ID or an array of IDs - if not provided, will use the current ids * as per getForeignID * @return array Condition In array(SQL => parameters format) */ @@ -188,7 +188,10 @@ class ManyManyList extends RelationList { * Add an item to this many_many relationship * Does so by adding an entry to the joinTable. * - * @param mixed $item + * @throws InvalidArgumentException + * @throws Exception + * + * @param DataObject|int $item * @param array $extraFields A map of additional columns to insert into the joinTable. * Column names should be ANSI quoted. */ @@ -349,7 +352,7 @@ class ManyManyList extends RelationList { */ public function getExtraData($componentName, $itemID) { $result = array(); - + // Skip if no extrafields or unsaved record if(empty($this->extraFields) || empty($itemID)) { return $result; diff --git a/model/RelationList.php b/model/RelationList.php index bd14f98d2..dfd39e19a 100644 --- a/model/RelationList.php +++ b/model/RelationList.php @@ -10,6 +10,9 @@ */ abstract class RelationList extends DataList { + /** + * @return string|null + */ public function getForeignID() { return $this->dataQuery->getQueryParam('Foreign.ID'); } @@ -19,6 +22,8 @@ abstract class RelationList extends DataList { * the given foreign ID. * * @param int|array $id An ID or an array of IDs. + * + * @return DataList */ public function forForeignID($id) { // Turn a 1-element array into a simple value @@ -27,7 +32,8 @@ abstract class RelationList extends DataList { // Calculate the new filter $filter = $this->foreignIDFilter($id); - $list = $this->alterDataQuery(function($query, $list) use ($id, $filter){ + $list = $this->alterDataQuery(function($query) use ($id, $filter){ + /** @var DataQuery $query */ // Check if there is an existing filter, remove if there is $currentFilter = $query->getQueryParam('Foreign.Filter'); if($currentFilter) { diff --git a/security/Member.php b/security/Member.php index 8459d076f..88b1cccf7 100644 --- a/security/Member.php +++ b/security/Member.php @@ -1413,7 +1413,12 @@ class Member extends DataObject implements TemplateGlobalProvider { )); $mainFields->removeByName($self->config()->hidden_fields); + + // make sure that the "LastVisited" field exists + // it may have been removed using $self->config()->hidden_fields + if($mainFields->fieldByName("LastVisited")){ $mainFields->makeFieldReadonly('LastVisited'); + } if( ! $self->config()->lock_out_after_incorrect_logins) { $mainFields->removeByName('FailedLoginCount'); diff --git a/tests/model/ArrayListTest.php b/tests/model/ArrayListTest.php index 8afe5038e..7e2e4630d 100644 --- a/tests/model/ArrayListTest.php +++ b/tests/model/ArrayListTest.php @@ -247,63 +247,135 @@ class ArrayListTest extends SapphireTest { $list = new ArrayList(array( array('Name' => 'Steve'), (object) array('Name' => 'Bob'), - array('Name' => 'John') + array('Name' => 'John'), + array('Name' => 'bonny'), )); // Unquoted name $list1 = $list->sort('Name'); - $this->assertEquals($list1->toArray(), array( + $this->assertEquals(array( (object) array('Name' => 'Bob'), + array('Name' => 'bonny'), array('Name' => 'John'), - array('Name' => 'Steve') - )); + array('Name' => 'Steve'), + ), $list1->toArray()); // Quoted name name $list2 = $list->sort('"Name"'); - $this->assertEquals($list2->toArray(), array( + $this->assertEquals(array( (object) array('Name' => 'Bob'), + array('Name' => 'bonny'), array('Name' => 'John'), - array('Name' => 'Steve') - )); + array('Name' => 'Steve'), + ), $list2->toArray()); // Array (non-associative) $list3 = $list->sort(array('"Name"')); - $this->assertEquals($list3->toArray(), array( + $this->assertEquals(array( (object) array('Name' => 'Bob'), + array('Name' => 'bonny'), array('Name' => 'John'), - array('Name' => 'Steve') - )); + array('Name' => 'Steve'), + ), $list3->toArray()); // Quoted name name with table $list4 = $list->sort('"Record"."Name"'); - $this->assertEquals($list4->toArray(), array( + $this->assertEquals(array( (object) array('Name' => 'Bob'), + array('Name' => 'bonny'), array('Name' => 'John'), array('Name' => 'Steve') - )); + ), $list4->toArray()); // Quoted name name with table (desc) $list5 = $list->sort('"Record"."Name" DESC'); - $this->assertEquals($list5->toArray(), array( + $this->assertEquals(array( array('Name' => 'Steve'), array('Name' => 'John'), + array('Name' => 'bonny'), (object) array('Name' => 'Bob') - )); + ), $list5->toArray()); // Table without quotes $list6 = $list->sort('Record.Name'); - $this->assertEquals($list6->toArray(), array( + $this->assertEquals(array( (object) array('Name' => 'Bob'), + array('Name' => 'bonny'), array('Name' => 'John'), array('Name' => 'Steve') - )); + ), $list6->toArray()); // Check original list isn't altered - $this->assertEquals($list->toArray(), array( + $this->assertEquals(array( array('Name' => 'Steve'), (object) array('Name' => 'Bob'), - array('Name' => 'John') + array('Name' => 'John'), + array('Name' => 'bonny'), + ), $list->toArray()); + } + + public function testNaturalSort() { + //natural sort is only available in 5.4+ + if (version_compare(phpversion(), '5.4.0', '<')) { + $this->markTestSkipped(); + } + $list = new ArrayList(array( + array('Name' => 'Steve'), + (object) array('Name' => 'Bob'), + array('Name' => 'John'), + array('Name' => 'bonny'), + array('Name' => 'bonny1'), + array('Name' => 'bonny10'), + array('Name' => 'bonny2'), )); + + // Unquoted name + $list1 = $list->sort('Name'); + $this->assertEquals(array( + (object) array('Name' => 'Bob'), + array('Name' => 'bonny'), + array('Name' => 'bonny1'), + array('Name' => 'bonny2'), + array('Name' => 'bonny10'), + array('Name' => 'John'), + array('Name' => 'Steve'), + ), $list1->toArray()); + + // Quoted name name + $list2 = $list->sort('"Name"'); + $this->assertEquals(array( + (object) array('Name' => 'Bob'), + array('Name' => 'bonny'), + array('Name' => 'bonny1'), + array('Name' => 'bonny2'), + array('Name' => 'bonny10'), + array('Name' => 'John'), + array('Name' => 'Steve'), + ), $list2->toArray()); + + // Array (non-associative) + $list3 = $list->sort(array('"Name"')); + $this->assertEquals(array( + (object) array('Name' => 'Bob'), + array('Name' => 'bonny'), + array('Name' => 'bonny1'), + array('Name' => 'bonny2'), + array('Name' => 'bonny10'), + array('Name' => 'John'), + array('Name' => 'Steve'), + ), $list3->toArray()); + + // Check original list isn't altered + $this->assertEquals(array( + array('Name' => 'Steve'), + (object) array('Name' => 'Bob'), + array('Name' => 'John'), + array('Name' => 'bonny'), + array('Name' => 'bonny1'), + array('Name' => 'bonny10'), + array('Name' => 'bonny2'), + ), $list->toArray()); + } public function testSortSimpleASCOrder() {