Skip to content
This repository
Browse code

Fixed references to deprecated APIs in docs

  • Loading branch information...
commit 19e087d2262f5971ce11dd713eb2aabd7a1216b3 1 parent 0236a3c
Ingo Schommer authored June 28, 2012

Showing 29 changed files with 243 additions and 357 deletions. Show diff stats Hide diff stats

  1. 2  docs/en/changelogs/3.0.0.md
  2. 4  docs/en/howto/dynamic-default-fields.md
  3. 5  docs/en/howto/extend-cms-interface.md
  4. 25  docs/en/reference/complextablefield.md
  5. 46  docs/en/reference/dataextension.md
  6. 2  docs/en/reference/dataobject.md
  7. 18  docs/en/reference/director.md
  8. 2  docs/en/reference/form-field-types.md
  9. 4  docs/en/reference/image.md
  10. 9  docs/en/reference/member.md
  11. 4  docs/en/reference/searchcontext.md
  12. 10  docs/en/reference/siteconfig.md
  13. 102  docs/en/reference/sqlquery.md
  14. 4  docs/en/reference/tablelistfield.md
  15. 2  docs/en/reference/templates-upgrading-guide.md
  16. 12  docs/en/reference/urlvariabletools.md
  17. 5  docs/en/topics/configuration.md
  18. 14  docs/en/topics/controller.md
  19. 2  docs/en/topics/files.md
  20. 172  docs/en/topics/forms.md
  21. 2  docs/en/topics/grid-field.md
  22. 18  docs/en/topics/i18n.md
  23. 6  docs/en/topics/page-types.md
  24. 2  docs/en/topics/rich-text-editing.md
  25. 4  docs/en/topics/search.md
  26. 1  docs/en/topics/theme-development.md
  27. 119  docs/en/tutorials/3-forms.md
  28. 2  docs/en/tutorials/4-site-search.md
  29. 2  docs/en/tutorials/5-dataobject-relationship-management.md
2  docs/en/changelogs/3.0.0.md
Source Rendered
@@ -625,7 +625,7 @@ Note that its just necessary if SilverStripe is used in a language other than th
625 625
 The SS_Report::register() method is deprecated. You no longer need to explicitly register reports. The CMS now
626 626
 automatically picks up and adds all Report classes to the list of reports in ReportAdmin. You can choose to exclude
627 627
 certain reports by using the SS_Report::add_excluded_reports() method.
628  
-fe
  628
+
629 629
 ### Removed the ability use a SQLQuery object in a SS_Report
630 630
 
631 631
 You can no longer create reports that using the deprecated DataObject->buildSQL and DataObject->extendedSQL
4  docs/en/howto/dynamic-default-fields.md
Source Rendered
@@ -2,7 +2,7 @@
2 2
 
3 3
 The [api:DataObject::$defaults] array allows you to specify simple static values to be the default value for when a
4 4
 record is created, but in many situations default values needs to be dynamically calculated. In order to do this, the
5  
-[api:DataObjectSet->populateDefaults()] method will need to be overloaded.
  5
+`[api:DataObject->populateDefaults()]` method will need to be overloaded.
6 6
 
7 7
 This method is called whenever a new record is instantiated, and you must be sure to call the method on the parent
8 8
 object!
@@ -13,7 +13,6 @@ A simple example is to set a field to the current date and time:
13 13
 	/**
14 14
 	 * Sets the Date field to the current date.
15 15
 	 */
16  
-	
17 16
 	public function populateDefaults() {
18 17
 		$this->Date = date('Y-m-d');
19 18
 		parent::populateDefaults();
@@ -27,7 +26,6 @@ methods. For example:
27 26
 	 * This method combines the Title of the parent object with the Title of this
28 27
 	 * object in the FullTitle field.
29 28
 	 */
30  
-	
31 29
 	public function populateDefaults() {
32 30
 		if($parent = $this->Parent()) {
33 31
 			$this->FullTitle = $parent->Title . ': ' . $this->Title;
5  docs/en/howto/extend-cms-interface.md
Source Rendered
@@ -85,9 +85,8 @@ Create a new file called `zzz_admin/code/BookmarkedPageExtension.php` and insert
85 85
 	:::php
86 86
 	<?php
87 87
 	class BookmarkedPageExtension extends DataExtension {
88  
-		public function extraStatics() {
89  
-			return array('db' => array('IsBookmarked' => 'Boolean'));
90  
-		}
  88
+		static $db = array('IsBookmarked' => 'Boolean');
  89
+		
91 90
 		public function updateCMSFields(&$fields) {
92 91
 			$fields->addFieldToTab('Root.Main',
93 92
 				new CheckboxField('IsBookmarked', "Show in CMS bookmarks?")
25  docs/en/reference/complextablefield.md
Source Rendered
@@ -2,6 +2,10 @@
2 2
 
3 3
 ## Introduction
4 4
 
  5
+<div class="warning" markdown="1">
  6
+	This field is deprecated in favour of the new [GridField](/topics/grid-field) API.
  7
+</div>
  8
+
5 9
 Shows a group of DataObjects as a (readonly) tabular list (similiar to `[api:TableListField]`.)
6 10
 
7 11
 You can specify limits and filters for the resultset by customizing query-settings (mostly the ID-field on the other
@@ -128,23 +132,4 @@ Most of the time, you need to override the following methods:
128 132
 
129 133
 *  ComplexTableField->sourceItems() - querying
130 134
 *  ComplexTableField->DetailForm() - form output
131  
-*  ComplexTableField_Popup->saveComplexTableField() - saving
132  
-
133  
-### Examples
134  
-
135  
-*  `[api:AssetTableField]`
136  
-*  `[api:MemberTableField]`
137  
-
138  
-## API Documentation
139  
-
140  
-`[api:ComplexTableField]`
141  
-
142  
-## Todo
143  
-
144  
-*  Find a less fragile solution for accessing this field through the main controller and ReferencedField, e.g. build a
145  
-seperate CTF-instance (doesn't necessarly have to be connected to the original by ReferencedField)
146  
-*  Control width/height of popup by constructor (hardcoded at the moment)
147  
-*  Integrate search from MemberTableField.php directly on `[api:ComplexTableField]`
148  
-*  Less performance-hungry implementation of detail-view paging (don't return all items on a single view)
149  
-*  Use automatic has-many and many-many functions to return a ComponentSet rather than building the join manually
150  
-*  Javascript/Ajax-Sorting (see [http://www.activewidgets.com/grid/](http://www.activewidgets.com/grid/) and [http://openrico.org/rico/livegrid.page](http://openrico.org/rico/livegrid.page))
  135
+*  ComplexTableField_Popup->saveComplexTableField() - saving
46  docs/en/reference/dataextension.md
Source Rendered
@@ -45,56 +45,42 @@ For example above we want to override Member with a Custom Member so we would wr
45 45
 
46 46
 ###  Adding extra database fields
47 47
 
48  
-Extra database fields can be added with a extension by defining an **extraStatics()** method.  These will be added to the table of the base object - the extension will actually edit the $db, $has_one, etc static variables on load.
  48
+Extra database fields can be added with a extension in the same manner as if they
  49
+were placed on the `DataObject` class they're applied to.  These will be added to the table of the base object - the extension will actually edit the $db, $has_one, etc static variables on load.
49 50
 
50 51
 The function should return a map where the keys are the names of the static variables to update:
51 52
 
52 53
 	:::php
53 54
 	class CustomMember extends DataExtension {
54  
-	
55  
-		public function extraStatics() {
56  
-			return array(
57  
-				'db' => array(
58  
-					'AvatarURL' => 'Varchar',
59  
-				),
60  
-				'has_one' => array(
61  
-					'RelatedMember' => 'Member',
62  
-				),
63  
-			);
64  
-		}
  55
+		static $db = array(
  56
+			'AvatarURL' => 'Varchar',
  57
+		);
  58
+		static $has_one = array(
  59
+			'RelatedMember' => 'Member',
  60
+		);
65 61
 	}
66 62
 
67  
-
68  
-*NOTE*
69  
-If you want to add has_one or db items to a particular class, then that class **must** have that static variable
70  
-explicitly defined, even if it's just a blank array.  For example, the extension method above wouldn't work if you added
71  
-to a class that didn't have static $has_one explicitly declared on the object.  This is because of PHP's crappy support
72  
-for statics.
73  
-
74  
-
75 63
 ### Modifying CMS Fields
76 64
 
77  
-The member class demonstrates an extension that allows you to update the default CMS fields for an object in a
78  
-extension:
  65
+The member class demonstrates an extension that allows you to update the default CMS fields for an 
  66
+object in an extension:
79 67
 
80 68
 	:::php
81 69
 	public function getCMSFields() {
82  
-	   ...
83  
-	   $this->extend('updateCMSFields', $fields);
84  
-	   return $fields;
  70
+	  // ... 
  71
+	  $this->extend('updateCMSFields', $fields);
  72
+	  return $fields;
85 73
 	}
86 74
 
87 75
 
88  
-The $fields parameter is passed by reference, as it is an object.
  76
+The `$`fields parameter is passed by reference, as it is an object.
89 77
 
90 78
 	:::php
91 79
 	public function updateCMSFields(FieldList $fields) {
92  
-	   $fields->push(new TextField('Position', 'Position Title'));
93  
-	   $fields->push(new UploadField('Image', 'Profile Image'));
  80
+	  $fields->push(new TextField('Position', 'Position Title'));
  81
+	  $fields->push(new UploadField('Image', 'Profile Image'));
94 82
 	}
95 83
 
96  
-
97  
-
98 84
 ### Custom database generation
99 85
 
100 86
 Some extensions are designed to transparently add more sophisticated data-collection capabilities to your data object.
2  docs/en/reference/dataobject.md
Source Rendered
@@ -31,7 +31,7 @@ by adding, removing or configuring fields.
31 31
 			'IsActive' => 'Boolean'
32 32
 		);
33 33
 	  public function getCMSFields() {
34  
-	    return new FieldSet(
  34
+	    return new FieldList(
35 35
 	    	new CheckboxField('IsActive')
36 36
 	    );
37 37
 	  }
18  docs/en/reference/director.md
Source Rendered
@@ -10,24 +10,6 @@ the viewer and/or perform processing steps.
10 10
 
11 11
 *  Checking for an Ajax-Request: Use Director::is_ajax() instead of checking for $_REQUEST['ajax'].
12 12
 
13  
-## Redirection
14  
-
15  
-The `[api:Director]` class has a number of methods to facilitate 301 and 302 HTTP redirection.
16  
-
17  
-*  **Director::redirect("action-name")**: If there's no slash in the URL passed to redirect, then it is assumed that you
18  
-want to go to a different action on the current controller.
19  
-*  **Director::redirect("relative/url")**: If there is a slash in the URL, it's taken to be a normal URL.  Relative URLs
20  
-will are assumed to be relative to the site-root; so Director::redirect("home/") will work no matter what the current
21  
-URL is.
22  
-*  **Director::redirect("http://www.absoluteurl.com")**: Of course, you can pass redirect() absolute URL s too.
23  
-*  **Director::redirectPerm("any-url")**: redirectPerm takes the same arguments as redirect, but it will send a 301
24  
-(permanent) instead of a 302 (temporary) header.  It improves search rankings, so this should be used whenever the
25  
-following two conditions are true:
26  
-    * Nothing happens server-side prior to the redirection
27  
-    * The redirection will always occur
28  
-*  **Director::redirectBack()**: This will return you to the previous page.  There's no permanent version of
29  
-redirectBack().
30  
-
31 13
 
32 14
 ## Request processing
33 15
 
2  docs/en/reference/form-field-types.md
Source Rendered
@@ -34,8 +34,6 @@ This is a highlevel overview of available `[api:apiFormField]` subclasses. An au
34 34
  * `[api:NumericField]`: Text input field with validation for numeric values.
35 35
  * `[api:OptionsetField]`: Set of radio buttons designed to emulate a dropdown.
36 36
  * `[api:PhoneNumberField]`: Field for displaying phone numbers. It separates the number, the area code and optionally the country code and extension.
37  
- * `[api:UniqueRestrictedTextField]`: Text field that automatically checks that the value entered is unique for the given set of fields in a given set of tables
38  
- 
39 37
  * `[api:SelectionGroup]`: SelectionGroup represents a number of fields which are selectable by a radio button that appears at the beginning of each item.
40 38
  * `[api:TimeField]`: Input field with time-specific, localized validation.
41 39
 
4  docs/en/reference/image.md
Source Rendered
@@ -72,10 +72,10 @@ You can also create your own functions by extending the image class, for example
72 72
 		public function Exif(){
73 73
 			//http://www.v-nessa.net/2010/08/02/using-php-to-extract-image-exif-data
74 74
 			$image = $this->AbsoluteURL;
75  
-			$d=new DataObjectSet();	
  75
+			$d=new ArrayList();	
76 76
 			$exif = exif_read_data($image, 0, true);
77 77
 			foreach ($exif as $key => $section) {
78  
-				$a=new DataObjectSet();	
  78
+				$a=new ArrayList();	
79 79
 				foreach ($section as $name => $val)
80 80
 					$a->push(new ArrayData(array("Title"=>$name,"Content"=>$val)));
81 81
 				$d->push(new ArrayData(array("Title"=>strtolower($key),"Content"=>$a)));
9  docs/en/reference/member.md
Source Rendered
@@ -113,9 +113,12 @@ things, you should add appropriate `[api:Permission::checkMember()]` calls to th
113 113
 	    }
114 114
 	  }
115 115
 	
116  
-	  public function extraStatics() {
117  
-	    // Return an array containing keys 'db', 'has_one', 'many_many', 'belongs_many_many',
118  
-	  }
  116
+		// define additional properties
  117
+		static $db = array(); 
  118
+		static $has_one = array(); 
  119
+		static $has_many = array(); 
  120
+		static $many_many = array(); 
  121
+		static $belongs_many_many = array(); 
119 122
 	
120 123
 	  public function somethingElse() {
121 124
 	    // You can add any other methods you like, which you can call directly on the member object.
4  docs/en/reference/searchcontext.md
Source Rendered
@@ -12,7 +12,7 @@ The default output of a `[api:SearchContext]` is either a `[api:SQLQuery]` objec
12 12
 In case you need multiple contexts, consider namespacing your request parameters by using `FieldList->namespace()` on
13 13
 the $fields constructor parameter.
14 14
 
15  
-`[api:SearchContext]` is mainly used by `[api:ModelAdmin]`, our generic data administration interface. Another
  15
+`[api:SearchContext]` is mainly used by `[ModelAdmin](/reference/modeladmin)`, our generic data administration interface. Another
16 16
 implementation can be found in generic frontend search forms through the [genericviews](http://silverstripe.org/generic-views-module) module.
17 17
 
18 18
 ## Usage
@@ -187,6 +187,6 @@ See `[api:SearchFilter]` API Documentation
187 187
 
188 188
 ## Related
189 189
 
190  
-*  `[api:ModelAdmin]`
  190
+*  [ModelAdmin](/reference/modeladmin)
191 191
 *  [RestfulServer module](https://github.com/silverstripe/silverstripe-restfulserver)
192 192
 *  [Tutorial: Site Search](/tutorials/4-site-search)
10  docs/en/reference/siteconfig.md
Source Rendered
@@ -39,13 +39,9 @@ Create a mysite/code/CustomSiteConfig.php file.
39 39
 	
40 40
 	class CustomSiteConfig extends DataExtension {
41 41
 		
42  
-		public function extraStatics() {
43  
-			return array(
44  
-				'db' => array(
45  
-					'FooterContent' => 'HTMLText'
46  
-				)
47  
-			);
48  
-		}
  42
+		static $db = array(
  43
+			'FooterContent' => 'HTMLText'
  44
+		);
49 45
 	
50 46
 		public function updateCMSFields(FieldList $fields) {
51 47
 			$fields->addFieldToTab("Root.Main", new HTMLEditorField("FooterContent", "Footer Content"));
102  docs/en/reference/sqlquery.md
Source Rendered
@@ -2,45 +2,40 @@
2 2
 
3 3
 ## Introduction
4 4
 
5  
-An object representing a SQL query. It is easier to deal with object-wrappers than string-parsing a raw SQL-query. This
6  
-object is used by `[api:DataObject]`, though...
  5
+An object representing a SQL query. It is easier to deal with object-wrappers than string-parsing a raw SQL-query. This object is used by the SilverStripe ORM internally.
  6
+Dealing with low-level SQL is not encouraged, since the ORM provides
  7
+powerful abstraction APIs (see [datamodel](/topics/datamodel). 
7 8
 
8  
-A word of caution: Dealing with low-level SQL is not encouraged in the SilverStripe [datamodel](/topics/datamodel) for various
9  
-reasons. You'll break the behaviour of:
  9
+You'll run the risk of breaking various assumptions the ORM and code based on it have:
10 10
 
11  
-*  Custom getters/setters
12  
-*  DataObject::onBeforeWrite/onBeforeDelete
  11
+*  Custom getters/setters (object property can differ from database column)
  12
+*  DataObject hooks like onBeforeWrite() and onBeforeDelete()
13 13
 *  Automatic casting
14  
-*  Default-setting through object-model
15  
-*  `[api:DataObject]`
  14
+*  Default values set through objects
16 15
 *  Database abstraction
17 16
 
18  
-We'll explain some ways to use *SELECT* with the full power of SQL, but still maintain a connection to the SilverStripe
19  
-[datamodel](/topics/datamodel).
  17
+We'll explain some ways to use *SELECT* with the full power of SQL, 
  18
+but still maintain a connection to the ORM where possible.
20 19
 
21 20
 ## Usage
22 21
 
23  
-
24 22
 ### SELECT
25 23
 
26 24
 	:::php
27 25
 	$sqlQuery = new SQLQuery();
28  
-	$sqlQuery->select = array(
29  
-	  'Firstname AS Name',
30  
-	  'YEAR(Birthday) AS BirthYear'
  26
+	$sqlQuery->setFrom('Player');
  27
+	$sqlQuery->selectField('FieldName', 'Name');
  28
+	$sqlQuery->selectField('YEAR("Birthday")', 'BirthYear');
  29
+	$sqlQuery->addLeftJoin(
  30
+		'Team',
  31
+	  '"Player"."TeamID" = "Team"."ID"'
31 32
 	);
32  
-	$sqlQuery->from = "
33  
-	  Player
34  
-	  LEFT JOIN Team ON Player.TeamID = Team.ID
35  
-	";
36  
-	$sqlQuery->where = "
37  
-	  YEAR(Birthday) = 1982
38  
-	";
39  
-	// $sqlQuery->orderby = "";
40  
-	// $sqlQuery->groupby = "";
41  
-	// $sqlQuery->having = "";
42  
-	// $sqlQuery->limit = "";
43  
-	// $sqlQuery->distinct = true;
  33
+	$sqlQuery->addWhere('YEAR("Birthday") = 1982');
  34
+	// $sqlQuery->setOrderBy(...);
  35
+	// $sqlQuery->setGroupBy(...);
  36
+	// $sqlQuery->setHaving(...);
  37
+	// $sqlQuery->setLimit(...);
  38
+	// $sqlQuery->setDistinct(true);
44 39
 	
45 40
 	// get the raw SQL
46 41
 	$rawSQL = $sqlQuery->sql();
@@ -53,7 +48,7 @@ We'll explain some ways to use *SELECT* with the full power of SQL, but still ma
53 48
 
54 49
 	:::php
55 50
 	// ...
56  
-	$sqlQuery->delete = true;
  51
+	$sqlQuery->setDelete(true);
57 52
 
58 53
 
59 54
 ### INSERT/UPDATE
@@ -80,10 +75,10 @@ Raw SQL is handy for performance-optimized calls.
80 75
 	:::php
81 76
 	class Team extends DataObject {
82 77
 	  public function getPlayerCount() {
83  
-	    $sqlQuery = new SQLQuery(
84  
-	      "COUNT(Player.ID)",
85  
-	      "Team LEFT JOIN Player ON Team.ID = Player.TeamID"
86  
-	    );
  78
+	    $sqlQuery = new SQLQuery();
  79
+	    $sqlQuery->setFrom('Player');
  80
+	    $sqlQuery->addSelect('COUNT("Player"."ID")');
  81
+	    $sqlQuery->addLeftJoin('Team', '"Team"."ID" = "Player"."TeamID"');
87 82
 	    return $sqlQuery->execute()->value();
88 83
 	}
89 84
 
@@ -99,10 +94,9 @@ Way faster than dealing with `[api:DataObject]`s, but watch out for premature op
99 94
 Useful for creating dropdowns.
100 95
 
101 96
 	:::php
102  
-	$sqlQuery = new SQLQuery(
103  
-	  array('YEAR(Birthdate)', 'Birthdate'),
104  
-	  'Player'
105  
-	);
  97
+	$sqlQuery = new SQLQuery();
  98
+	$sqlQuery->setFrom('Player');
  99
+	$sqlQuery->selectField('YEAR("Birthdate")', 'Birthdate');
106 100
 	$map = $sqlQuery->execute()->map();
107 101
 	$field = new DropdownField('Birthdates', 'Birthdates', $map);
108 102
 
@@ -112,8 +106,11 @@ Useful for creating dropdowns.
112 106
 This is not recommended for most cases, but you can also use the SilverStripe database-layer to fire off a raw query:
113 107
 
114 108
 	:::php
115  
-	DB::query("UPDATE Player SET Status='Active'");
  109
+	DB::query('UPDATE "Player" SET "Status"=\'Active\'');
116 110
 
  111
+<<<<<<< Updated upstream
  112
+### Transforming a result to `[api:ArrayList]`
  113
+=======
117 114
 One example for using a raw DB::query is when you are wanting to order twice in the database:
118 115
 
119 116
 	:::php
@@ -130,46 +127,41 @@ You can gain some ground on the datamodel-side when involving the selected class
130 127
 need to call *buildSQL* from a specific object-instance, a *singleton* will do just fine.
131 128
 
132 129
 	:::php
133  
-	$sqlQuery = singleton('Player')->buildSQL(
134  
-	  'YEAR(Birthdate) = 1982'
135  
-	);
  130
+	$sqlQuery = singleton('Player')->buildSQL('YEAR("Birthdate") = 1982');
136 131
 
137 132
 
138 133
 This form of building a query has the following advantages:
139 134
 
140 135
 *  Respects DataObject::$default_sort
141  
-*  Automatically LEFT JOIN on all base-tables (see [database-structure](database-structure))
  136
+*  Automatically `LEFT JOIN` on all base-tables (see [database-structure](database-structure))
142 137
 *  Selection of *ID*, *ClassName*, *RecordClassName*, which are necessary to use *buildDataObjectSet* later on
143 138
 *  Filtering records for correct *ClassName*
144 139
 
145 140
 ### Transforming a result to `[api:DataObjectSet]`
  141
+>>>>>>> Stashed changes
146 142
 
147 143
 This is a commonly used technique inside SilverStripe: Use raw SQL, but transfer the resulting rows back into
148 144
 `[api:DataObject]`s.
149 145
 
150 146
 	:::php
151 147
 	$sqlQuery = new SQLQuery();
152  
-	$sqlQuery->select = array(
153  
-	  'Firstname AS Name',
154  
-	  'YEAR(Birthday) AS BirthYear',
  148
+	$sqlQuery->setSelect(array(
  149
+	  '"Firstname" AS "Name"',
  150
+	  'YEAR("Birthday") AS "BirthYear"',
155 151
 	  // IMPORTANT: Needs to be set after other selects to avoid overlays
156  
-	  'Player.ClassName AS ClassName',
157  
-	  'Player.ClassName AS RecordClassName',
158  
-	  'Player.ID AS ID'
159  
-	);
160  
-	$sqlQuery->from = array(
161  
-	  "Player",
162  
-	  "LEFT JOIN Team ON Player.TeamID = Team.ID"
163  
-	);
164  
-	$sqlQuery->where = array(
165  
-	  "YEAR(Player.Birthday) = 1982"
166  
-	);
  152
+	  '"Player"."ClassName" AS "ClassName"',
  153
+	  '"Player"."ClassName" AS "RecordClassName"',
  154
+	  '"Player"."ID" AS "ID"'
  155
+	));
  156
+	$sqlQuery->setFrom('Player');
  157
+	$sqlQuery->addLeftJoin('Team', '"Player"."TeamID" = "Team"."ID"');
  158
+	$sqlQuery->addWhere("YEAR("Player"."Birthday") = 1982");
167 159
 	
168 160
 	$result = $sqlQuery->execute();
169 161
 	var_dump($result->first()); // array
170 162
 	
171 163
 	// let Silverstripe work the magic
172  
-	$myDataObjectSet = singleton('Player')->buildDataObjectSet($result);
  164
+	$myList = singleton('Player')->buildDataObjectSet($result);
173 165
 	var_dump($myDataObjectSet->First()); // DataObject
174 166
 	
175 167
 	// this is where it gets tricky
4  docs/en/reference/tablelistfield.md
Source Rendered
@@ -2,6 +2,10 @@
2 2
 
3 3
 ## Introduction
4 4
 
  5
+<div class="warning" markdown="1">
  6
+	This field is deprecated in favour of the new [GridField](/topics/grid-field) API.
  7
+</div>
  8
+
5 9
 Form field that embeds a list of `[api:DataObject]`s into a form, such as a member list or a file list.
6 10
 Provides customizeable columns, record-deletion by ajax, paging, sorting, CSV-export, printing, input by
7 11
 `[api:DataObject]` or raw SQL.
2  docs/en/reference/templates-upgrading-guide.md
Source Rendered
@@ -4,7 +4,7 @@ These are the main changes to the SiverStripe 3 template language.
4 4
 
5 5
 ## Control blocks: Loops vs. Scope
6 6
 
7  
-The `<% control var %>...<% end_control %>` in SilverStripe prior to version 3 has two different meanings. Firstly, if the control variable is a collection (e.g. DataObjectSet), then `<% control %>` iterates over that set. If it's a non-iteratable object, however, `<% control %>` introduces a new scope, which is used to render the inner template code. This dual-use is confusing to some people, and doesn't allow a collection of objects to be used as a scope.
  7
+The `<% control var %>...<% end_control %>` in SilverStripe prior to version 3 has two different meanings. Firstly, if the control variable is a collection (e.g. DataList), then `<% control %>` iterates over that set. If it's a non-iteratable object, however, `<% control %>` introduces a new scope, which is used to render the inner template code. This dual-use is confusing to some people, and doesn't allow a collection of objects to be used as a scope.
8 8
 
9 9
 In SilverStripe 3, the first usage (iteration) is replaced by `<% loop var %>`. The second usage (scoping) is replaced by `<% with var %>`
10 10
 
12  docs/en/reference/urlvariabletools.md
Source Rendered
@@ -49,20 +49,10 @@ Append the option and corresponding value to your URL in your browser's address
49 49
 
50 50
  | URL Variable     | | Values | | Description                                                                                      | 
51 51
  | ------------     | | ------ | | -----------                                                                                      | 
  52
+ | debug_memory     | | 1      | | Output the number of bytes of memory used for this 
52 53
  | debug_memory     | | 1      | | Output the number of bytes of memory used for this request                                       | 
53 54
  | debug_profile    | | 1      | | Enable the [profiler](/topics/debugging) for the duration of the request                         | 
54 55
  | profile_trace    | | 1      | | Includes full stack traces, must be used with **debug_profile**                                  | 
55  
- | debug_behaviour  | | 1      | | Get profiling of [Behaviour.js](http://bennolan.com/behaviour) performance (Firebug recommended) | 
56  
- | debug_javascript | | 1      | | Force debug-output on live-sites                                                                 | 
57  
-
58  
-## Misc
59  
-
60  
- | URL Variable | | Values     | | Description                                                                                                | 
61  
- | ------------ | | ------     | | -----------                                                                                                | 
62  
- | forceFormat  | | xhtml,html | | Force the content negotiator to deliver HTML or XHTML is allowed                                           | 
63  
- | showspam     | | 1          | | Show comments marked as spam when viewing Comments on a Page (Saving spam to the database must be enabled) | 
64  
- | ajax         | | 1          | | Force request to process as AJAX request, useful for debugging from a browser                              | 
65  
- | force_ajax   | | 1          | | Similar to **ajax**                                                                                        | 
66 56
 
67 57
 ## Security Redirects
68 58
 
5  docs/en/topics/configuration.md
Source Rendered
@@ -36,9 +36,6 @@ incomplete - please add to it** *Try to keep it in alphabetical order too! :)*
36 36
  | Authenticator::register_authenticator($authenticator);|              | Enable an authentication method (for more details see [security](/topics/security)). |        
37 37
  | Authenticator::set_default_authenticator($authenticator); |          | Modify tab-order on login-form.|        
38 38
  | BBCodeParser::disable_autolink_urls(); |                             | Disables plain hyperlinks from being turned into links when bbcode is parsed. |     
39  
- | BlogEntry::allow_wysiwyg_editing();  |                               | Enable rich text editing for blog posts.  |                                                                                  
40  
- | ContentNegotiator::set_encoding(string $encoding)  |					| The encoding charset to use - UTF-8 by default  |        
41  
- | ContentNegotiator::disable()  |										| Disables the negotiation of content type -usually used to stop it from rewriting the DOCTYPE of the document                                                         
42 39
  | DataObject::$create_table_options['MySQLDatabase'] = 'ENGINE=MyISAM';|	| Set the default database engine to MyISAM (versions 2.4 and below already default to MyISAM) |        
43 40
  | Debug::send_errors_to(string $email) |								| Send live errors on your site to this address (site has to be in 'live' mode using Director::set_environment_type(live) for this to occur |        
44 41
  | Director::set_environment_type(string dev,test,live) | 				| Sets the environment type (e.g. dev site will show errors, live site hides them and displays a 500 error instead) | 
@@ -48,8 +45,6 @@ incomplete - please add to it** *Try to keep it in alphabetical order too! :)*
48 45
  | Email::send_all_emails_to(string $email)  |                          | Sends all emails to this address. Useful for debugging your email sending functions  |        
49 46
  | Email::cc_all_emails_to(string $email)  |                            | Useful for CC'ing all emails to someone checking correspondence |        
50 47
  | Email::bcc_all_emails_to(string $email) |                            | BCC all emails to this address, similar to CC'ing emails (above)  |        
51  
- | MathSpamProtection::setEnabled()  |                                  | Adds a math spam question to all page comment forms |        
52  
- | PageComment::enableModeration();  |                                  | Enables comment moderation |        
53 48
  | Requirements::set_suffix_requirements(false); |                      | Disable appending the current date to included files |   
54 49
  | Security::encrypt_passwords($encrypt_passwords);  |                  | Specify if you want store your passwords in clear text or encrypted (for more details see [security](/topics/security)) |        
55 50
  | Security::set_password_encryption_algorithm($algorithm, $use_salt);| | If you choose to encrypt your passwords, you can choose which algorithm is used to and if a salt should be used to increase the security level even more (for more details see [security](/topics/security)). |        
14  docs/en/topics/controller.md
Source Rendered
@@ -88,6 +88,20 @@ after it.  If the URLSegment is **order** then `/order/tag/34` and `/order/tag/a
88 88
 
89 89
 You can use the `debug_request=1` switch from the [urlvariabletools](/reference/urlvariabletools) to see these in action.
90 90
 
  91
+## Redirection
  92
+
  93
+Controllers facilitate HTTP redirection.
  94
+Note: These methods have been formerly located on the `[api:Director]` class.
  95
+
  96
+*  `redirect("action-name")`: If there's no slash in the URL passed to redirect, then it is assumed that you want to go to a different action on the current controller.
  97
+*  `redirect("relative/url")`: If there is a slash in the URL, it's taken to be a normal URL.  Relative URLs
  98
+will are assumed to be relative to the site-root.
  99
+*  `redirect("http://www.absoluteurl.com")`: Of course, you can pass `redirect()` absolute URLs too.
  100
+*  `redirectBack()`: This will return you to the previous page.
  101
+
  102
+The `redirect()` method takes an optional HTTP status code,
  103
+either `301` for permanent redirects, or `302` for temporary redirects (default).
  104
+
91 105
 ## API Documentation
92 106
 
93 107
 `[api:Controller]`
2  docs/en/topics/files.md
Source Rendered
@@ -10,7 +10,7 @@ TODO Screenshot of admin interface
10 10
 
11 11
 ## Upload
12 12
 
13  
-TODO Link to Upload and FileIframeField classes
  13
+TODO Link to UploadField and FileField classes
14 14
 
15 15
 ## Image Resizing
16 16
 
172  docs/en/topics/forms.md
Source Rendered
@@ -60,78 +60,42 @@ The real difference, however, is that you can then define your controller method
60 60
 
61 61
 ## Form Field Types
62 62
 
63  
-There are many classes extending `[api:FormField]`. Some examples:
64  
-
65  
-*  `[api:TextField]`
66  
-*  `[api:EmailField]`
67  
-*  `[api:NumericField]`
68  
-*  `[api:DateField]`
69  
-*  `[api:CheckboxField]`
70  
-*  `[api:DropdownField]`
71  
-*  `[api:OptionsetField]`
72  
-*  `[api:CheckboxSetField]`
73  
-
74  
-Full overview at [form-field-types](/reference/form-field-types)
  63
+There are many classes extending `[api:FormField]`,
  64
+there's a full overview at [form-field-types](/reference/form-field-types)
75 65
 
76 66
 
77 67
 ### Using Form Fields
78 68
 
79  
-To get these fields automatically rendered into a form element, all you need to do is create a new instance of the
  69
+To get these fields automatically rendered into a form element, 
  70
+all you need to do is create a new instance of the
80 71
 class, and add it to the fieldlist of the form. 
81 72
 
82 73
 	:::php
83 74
 	$form = new Form(
84  
-	        $controller = $this,
85  
-	        $name = "SignupForm",
86  
-	        $fields = new FieldList(
87  
-	            new TextField(
88  
-	                $name = "FirstName", 
89  
-	                $title = "First name"
90  
-	            ),
91  
-	            new TextField("Surname"),
92  
-	            new EmailField("Email", "Email address"),
93  
-	         ), 
94  
-	        $actions = new FieldList(
95  
-	            // List the action buttons here
96  
-	            new FormAction("signup", "Sign up")
97  
-	        ), 
98  
-	        $requiredFields = new RequiredFields(
99  
-	            // List the required fields here: "Email", "FirstName"
100  
-	        )
101  
-	);
102  
-
103  
-
104  
-You'll note some of the fields are optional.
105  
-
106  
-Implementing the more complex fields requires extra arguments.
107  
-
108  
-	:::php
109  
-	$form = new Form(
110  
-	        $controller = $this,
111  
-	        $name = "SignupForm",
112  
-	        $fields = new FieldList(
113  
-	            // List the your fields here
114  
-	            new TextField(
115  
-	                $name = "FirstName", 
116  
-	                $title = "First name"
117  
-	            ),
118  
-	            new TextField("Surname"),
119  
-	            new EmailField("Email", "Email address")
120  
-	            new DropdownField(
121  
-	                 $name = "Country",
122  
-	                 $title = "Country (if outside nz)",
123  
-	                 $source = Geoip::getCountryDropDown(),
124  
-	                 $value = Geoip::visitor_country()
125  
-	            )
126  
-	        ), new FieldList(
127  
-	            // List the action buttons here
128  
-	            new FormAction("signup", "Sign up")
129  
-	 
130  
-	        ), new RequiredFields(
131  
-	            // List the required fields here: "Email", "FirstName"
132  
-	        )
  75
+		$this, // controller
  76
+		"SignupForm", // form name
  77
+		new FieldList( // fields
  78
+			TextField::create("FirstName")
  79
+				->setTitle('First name')
  80
+			TextField::create("Surname")
  81
+				->setTitle('Last name')
  82
+				->setMaxLength(50),
  83
+			EmailField::create("Email")
  84
+				->setTitle("Email address")
  85
+				->setAttribute('type', 'email')
  86
+		), 
  87
+		new FieldList( // actions
  88
+			FormAction::create("signup")->setTitle("Sign up")
  89
+		), 
  90
+		new RequiredFields( // validation
  91
+			"Email", "FirstName"
  92
+		)
133 93
 	);
134 94
 
  95
+You'll notice that we've used a new notation for creating form fields,
  96
+using `create()` instead of the `new` operator. These are functionally equivalent,
  97
+but allows PHP to chain operations like `setTitle()` without assigning
  98
+the field instance to a temporary variable.
135 99
 
136 100
 ##  Readonly
137 101
 
@@ -144,7 +108,7 @@ Readonly on a Form
144 108
 Readonly on a FieldList
145 109
 
146 110
 	:::php
147  
-	$myFieldSet->makeReadonly();
  111
+	$myFieldList->makeReadonly();
148 112
 
149 113
 
150 114
 Readonly on a FormField
@@ -167,29 +131,29 @@ First of all, you need to create your form on it's own class, that way you can d
167 131
 	:::php
168 132
 	class MyForm extends Form {
169 133
 	
170  
-	   public function __construct($controller, $name) {
171  
-	      $fields = new FieldList(
172  
-	         new TextField('FirstName', 'First name'),
173  
-	         new EmailField('Email', 'Email address')
174  
-	      );
  134
+		 public function __construct($controller, $name) {
  135
+				$fields = new FieldList(
  136
+					 new TextField('FirstName', 'First name'),
  137
+					 new EmailField('Email', 'Email address')
  138
+				);
175 139
 	
176  
-	      $actions = new FieldList(
177  
-	         new FormAction('submit', 'Submit')
178  
-	      );
  140
+				$actions = new FieldList(
  141
+					 new FormAction('submit', 'Submit')
  142
+				);
179 143
 	
180  
-	      parent::__construct($controller, $name, $fields, $actions);
181  
-	   }
  144
+				parent::__construct($controller, $name, $fields, $actions);
  145
+		 }
182 146
 	
183  
-	   public function forTemplate() {
184  
-	      return $this->renderWith(array(
185  
-	         $this->class,
186  
-	         'Form'
187  
-	      ));
188  
-	   }
  147
+		 public function forTemplate() {
  148
+				return $this->renderWith(array(
  149
+					 $this->class,
  150
+					 'Form'
  151
+				));
  152
+		 }
189 153
 	
190  
-	   public function submit($data, $form) {
191  
-	      // do stuff here
192  
-	   }
  154
+		 public function submit($data, $form) {
  155
+				// do stuff here
  156
+		 }
193 157
 	
194 158
 	}
195 159
 
@@ -201,31 +165,31 @@ basic customisation:
201 165
 
202 166
 	:::ss
203 167
 	<form $FormAttributes>
204  
-	   <% if Message %>
205  
-	      <p id="{$FormName}_error" class="message $MessageType">$Message</p>
206  
-	   <% else %>
207  
-	      <p id="{$FormName}_error" class="message $MessageType" style="display: none"></p>
208  
-	   <% end_if %>
209  
-	   
210  
-	   <fieldset>
211  
-	      <div id="FirstName" class="field text">
212  
-	         <label class="left" for="{$FormName}_FirstName">First name</label>
213  
-	         $dataFieldByName(FirstName)
214  
-	      </div>
  168
+		 <% if Message %>
  169
+				<p id="{$FormName}_error" class="message $MessageType">$Message</p>
  170
+		 <% else %>
  171
+				<p id="{$FormName}_error" class="message $MessageType" style="display: none"></p>
  172
+		 <% end_if %>
  173
+		 
  174
+		 <fieldset>
  175
+				<div id="FirstName" class="field text">
  176
+					 <label class="left" for="{$FormName}_FirstName">First name</label>
  177
+					 $dataFieldByName(FirstName)
  178
+				</div>
215 179
 	
216  
-	      <div id="Email" class="field email">
217  
-	         <label class="left" for="{$FormName}_Email">Email</label>
218  
-	         $dataFieldByName(Email)
219  
-	      </div>
  180
+				<div id="Email" class="field email">
  181
+					 <label class="left" for="{$FormName}_Email">Email</label>
  182
+					 $dataFieldByName(Email)
  183
+				</div>
220 184
 	
221  
-	      $dataFieldByName(SecurityID)
222  
-	   </fieldset>
  185
+				$dataFieldByName(SecurityID)
  186
+		 </fieldset>
223 187
 	
224  
-	   <% if Actions %>
225  
-	      <div class="Actions">
226  
-	         <% loop Actions %>$Field<% end_loop %>
227  
-	      </div>
228  
-	   <% end_if %>
  188
+		 <% if Actions %>
  189
+				<div class="Actions">
  190
+					 <% loop Actions %>$Field<% end_loop %>
  191
+				</div>
  192
+		 <% end_if %>
229 193
 	</form>
230 194
 
231 195
  `$dataFieldByName(FirstName)` will return the form control contents of `Field()` for the particular field object, in
2  docs/en/topics/grid-field.md
Source Rendered
@@ -14,7 +14,7 @@ This example might come from a Controller designed to manage the members of a gr
14 14
 	 */
15 15
 	public function MemberForm() {
16 16
 		$field = new GridField("Members", "Members of this group", $this->group->Members());
17  
-		return new Form("MemberForm", $this, new FieldSet($field), new FieldSet());
  17
+		return new Form("MemberForm", $this, new FieldList($field), new FieldList());
18 18
 	}
19 19
 
20 20
 Note that the only way to specify the data that is listed in a grid field is with `SS_List` argument.  If you want to customise the data displayed, you can do so by customising this object.
18  docs/en/topics/i18n.md
Source Rendered
@@ -70,9 +70,9 @@ to write your own logic for any frontend output.
70 70
 	i18n::set_date_format('dd.MM.YYYY');
71 71
 	i18n::set_time_format('HH:mm');
72 72
 
73  
-Most localization routines in SilverStripe use the [http://framework.zend.com/manual/en/zend.date.html](Zend_Date API).
  73
+Most localization routines in SilverStripe use the [Zend_Date API](http://framework.zend.com/manual/en/zend.date.html).
74 74
 This means all formats are defined in
75  
-[http://framework.zend.com/manual/en/zend.date.constants.html#zend.date.constants.selfdefinedformats](ISO date format),
  75
+[ISO date format](http://framework.zend.com/manual/en/zend.date.constants.html#zend.date.constants.selfdefinedformats),
76 76
 not PHP's built-in [date()](http://nz.php.net/manual/en/function.date.php).
77 77
 
78 78
 ### i18n in URLs
@@ -227,22 +227,20 @@ which supports different translation adapters, dealing with different storage fo
227 227
 
228 228
 By default, SilverStripe 3.x uses a YAML format (through the [Zend_Translate_RailsYAML adapter](https://github.com/chillu/zend_translate_railsyaml)). 
229 229
 
230  
-Example: sapphire/lang/en.yml (extract)
  230
+Example: framework/lang/en.yml (extract)
231 231
 
232  
-	:::yml
233 232
 	en:
234 233
 	  ImageUploader:
235 234
 	    Attach: 'Attach %s'
236  
-	  FileIFrameField:
  235
+	  UploadField:
237 236
 	    NOTEADDFILES: 'You can add files once you have saved for the first time.'
238 237
 
239  
-Translation table: sapphire/lang/de.yml (extract)
  238
+Translation table: framework/lang/de.yml (extract)
240 239
 
241  
-	:::yml
242 240
 	de:
243 241
 	  ImageUploader:
244 242
 	    ATTACH: '%s anhängen'
245  
-	  FileIframeField:
  243
+	  UploadField:
246 244
 	    NOTEADDFILES: 'Sie können Dateien hinzufügen sobald Sie das erste mal gespeichert haben'
247 245
 
248 246
 Note that translations are cached across requests.
@@ -262,7 +260,7 @@ Example: framework/lang/en_US.php (extract)
262 260
 		'Attach %s',
263 261
 		'Attach image/file'
264 262
 	);
265  
-	$lang['en_US']['FileIFrameField']['NOTEADDFILES'] = 'You can add files once you have saved for the first time.';
  263
+	$lang['en_US']['UploadField']['NOTEADDFILES'] = 'You can add files once you have saved for the first time.';
266 264
 	// ...
267 265
 
268 266
 
@@ -270,7 +268,7 @@ Translation table: framework/lang/de_DE.php (extract)
270 268
 
271 269
 	:::php
272 270
 	$lang['de_DE']['ImageUploader']['ATTACH'] = '%s anhängen';
273  
-	$lang['de_DE']['FileIframeField']['NOTEADDFILES'] = 'Sie können Dateien hinzufügen sobald Sie das erste mal gespeichert haben';
  271
+	$lang['de_DE']['UploadField']['NOTEADDFILES'] = 'Sie können Dateien hinzufügen sobald Sie das erste mal gespeichert haben';
274 272
 
275 273
 In order to enable usage of PHP language definitions in 3.x, you need to register a legacy adapter
276 274
 in your `mysite/_config.php`:
6  docs/en/topics/page-types.md
Source Rendered
@@ -52,14 +52,11 @@ especially useful if you know how long your source data needs to be.
52 52
 
53 53
 	:::php
54 54
 	class StaffPage extends Page {
55  
-	
56 55
 	   static $db = array(
57 56
 	      'Author' => 'Varchar(50)'
58 57
 	   );
59  
-	
60 58
 	}
61 59
 	class StaffPage_Controller extends Page_Controller {
62  
-	
63 60
 	}
64 61
 
65 62
 
@@ -68,7 +65,8 @@ model works.
68 65
 
69 66
 ## Adding Form Fields and Tabs
70 67
 
71  
-See [form](/topics/forms) and [tutorial:2-extending-a-basic-site](/tutorials/2-extending-a-basic-site)
  68
+See [form](/topics/forms) and [tutorial:2-extending-a-basic-site](/tutorials/2-extending-a-basic-site).
  69
+Note: To modify fields in the "Settings" tab, you need to use `updateSettingsFields()` instead.
72 70
 
73 71
 ## Removing inherited form fields and tabs
74 72
 
2  docs/en/topics/rich-text-editing.md
Source Rendered
@@ -17,7 +17,7 @@ It is usually added through the `[api:DataObject->getCMSFields()]` method:
17 17
 		static $db = array('Content' => 'HTMLText');
18 18
 
19 19
 		public function getCMSFields() {
20  
-			return new FieldSet(new HTMLEditorField('Content'));
  20
+			return new FieldList(new HTMLEditorField('Content'));
21 21
 		}
22 22
 	}
23 23
 
4  docs/en/topics/search.md
Source Rendered
@@ -8,7 +8,7 @@ See [Tutorial: Site Search](/tutorials/4-site-search) for details.
8 8
 ## Searching for DataObjects
9 9
 
10 10
 The `[api:SearchContext]` class provides a good base implementation that you can hook into your own controllers. 
11  
-A working implementation of searchable DataObjects can be seen in the `[api:ModelAdmin]` class.
  11
+A working implementation of searchable DataObjects can be seen in the `[ModelAdmin](/reference/modeladmin)` class.
12 12
 
13 13
 [SearchContext](/reference/searchcontext) goes into more detail about setting up a default search form for `[api:DataObject]`s.
14 14
 
@@ -33,7 +33,7 @@ dedicated search service like the [sphinx module](http://silverstripe.org/sphinx
33 33
 
34 34
 ## Related
35 35
 
36  
-*  `[api:ModelAdmin]`
  36
+*  [ModelAdmin](/reference/modeladmin)
37 37
 *  [RestfulServer module](https://github.com/silverstripe/silverstripe-restfulserver)
38 38
 *  [Tutorial: Site Search](/tutorials/4-site-search)
39 39
 *  [SearchContext](/reference/searchcontext)
1  docs/en/topics/theme-development.md
Source Rendered
@@ -99,7 +99,6 @@ our theme in action. The code for mine is below.
99 99
 	<h1>$Title</h1>
100 100
 	$Content
101 101
 	$Form
102  
-	$PageComments
103 102
 
104 103
 
105 104
 All you have to do now is tell your site to use your new theme - This is defined in the **mysite/_config.php** file
119  docs/en/tutorials/3-forms.md
Source Rendered
@@ -207,7 +207,7 @@ that the *BrowserPollSubmission* table is created. Now we just need to define 'd
207 207
 			$submission = new BrowserPollSubmission();
208 208
 			$form->saveInto($submission);
209 209
 			$submission->write();
210  
-			Director::redirectBack();
  210
+			return $this->redirectBack();
211 211
 		}
212 212
 	}
213 213
 
@@ -218,7 +218,7 @@ A function that processes a form submission takes two arguments - the first is t
218 218
 In our function we create a new *BrowserPollSubmission* object. Since the name of our form fields and the name of the
219 219
 database fields are the same we can save the form directly into the data object.
220 220
 
221  
-We call the 'write' method to write our data to the database, and 'Director::redirectBack()' will redirect the user back
  221
+We call the 'write' method to write our data to the database, and 'redirectBack()' will redirect the user back
222 222
 to the home page.
223 223
 
224 224
 
@@ -237,11 +237,8 @@ Change the end of the 'BrowserPollForm' function so it looks like this:
237 237
 
238 238
 	:::php
239 239
 	public function BrowserPollForm() {
240  
-		...
241  
-	
242  
-		// Create validator
  240
+		// ...
243 241
 		$validator = new RequiredFields('Name', 'Browser');
244  
-		
245 242
 		return new Form($this, 'BrowserPollForm', $fields, $actions, $validator);
246 243
 	}
247 244
 
@@ -266,22 +263,16 @@ First modify the 'doBrowserPoll' to set the session variable 'BrowserPollVoted'
266 263
 *mysite/code/HomePage.php*
267 264
 
268 265
 	:::php
269  
-	...
270  
-	
271  
-	HomePage_Controller extends Page_Controller {
272  
-		...
273  
-		
  266
+	// ...
  267
+	class HomePage_Controller extends Page_Controller {
  268
+		// ...
274 269
 		public function doBrowserPoll($data, $form) {
275 270
 			$submission = new BrowserPollSubmission();
276 271
 			$form->saveInto($submission);
277 272
 			$submission->write();
278  
-			
279 273
 			Session::set('BrowserPollVoted', true);
280  
-			
281  
-			Director::redirectBack();
  274
+			return $this->redirectBack();
282 275
 		}
283  
-		
284  
-		...
285 276
 	}
286 277
 
287 278
 
@@ -293,59 +284,55 @@ it is.
293 284
 		if(Session::get('BrowserPollVoted')) {
294 285
 			return false;
295 286
 		}
296  
-		
297  
-		...
298  
-
  287
+		// ...
  288
+	}	
299 289
 
300  
-If you visit the home page now you will see you can only vote once per session; after that the form won't be shown. You
301  
-can start a new session by closing and reopening your browser (or if you're using Firefox and have installed the [Web
302  
-Developer](http://chrispederick.com/work/web-developer/) extension, you can use its Clear Session Cookies command).
303 290
 
304  
-Although the form is not shown, you'll still see the 'Browser Poll' heading. We'll leave this for now: after we've built
305  
-the bar graph of the results, we'll modify the template to show the graph instead of the form if the user has already
306  
-voted.
  291
+If you visit the home page now you will see you can only vote once per session; 
  292
+after that the form won't be shown. 
  293
+You can start a new session by closing and reopening your browser.
307 294
 
308  
-We now need some way of getting the data from the database into the template.
  295
+Now that we're collecting data, it would be nice to show the results
  296
+on the website as well. We could simply output every vote, but that's boring.
  297
+Let's group the results by browser, through the SilverStripe data model.
309 298
 
310  
-In the second tutorial we got the latest news articles for the home page by using the 'DataObject::get' function. We
311  
-can't use the 'DataObject::get' function here directly as we wish to count the total number of votes for each browser.
312  
-By looking at the documentation for 'DataObject::get', we can see that it returns a `[api:DataObjectSet]`
313  
-object. In fact, all data that can be iterated over in a template with a page control is contained in a DataObjectSet.
  299
+In the [second tutorial](/tutorials/2-extending-a-basic-site), 
  300
+we got a collection of news articles for the home page by 
  301
+using the 'ArticleHolder::get()' function, which returns a `[api:DataList]`.
  302
+We can get all submissions in the same fashion, through `BrowserPollSubmission::get()`.
  303
+This list will be the starting point for our result aggregation.
314 304
 
315  
-A `[api:DataObjectSet]` is a set of not just DataObjects, but of ViewableData, which the majority of
316  
-SilverStripe's classes (including DataObject) inherit from. We can create a DataObjectSet, fill it with our data, and
317  
-then create our graph using a page control in the template. Create the function 'BrowserPollResults' on the
318  
-*HomePage_Controller* class.
  305
+Create the function 'BrowserPollResults' on the *HomePage_Controller* class.
319 306
 
320 307
 ** mysite/code/HomePage.php **
321 308
 
322 309
 	:::php
323 310
 	public function BrowserPollResults() {
324  
-		$submissions = BrowserPollSubmission::get();
  311
+		$submissions = new GroupedList(BrowserPollSubmission::get());
325 312
 		$total = $submissions->Count();
326 313
 		
327  
-		$doSet = new DataObjectSet();
328  
-		foreach($submissions->groupBy('Browser') as $browser => $data) {
  314
+		$list = new ArrayList();
  315
+		foreach($submissions->groupBy('Browser') as $browserName => $browserSubmissions) {
329 316
 			$percentage = (int) ($data->Count() / $total * 100);
330  
-			$record = array(
331  
-				'Browser' => $browser,
  317
+			$list->push(new ArrayData(array(
  318
+				'Browser' => $browserName,
332 319
 				'Percentage' => $percentage
333  
-			);
334  
-			$doSet->push(new ArrayData($record));
  320
+			)));
335 321
 		}
336  
-		
337  
-		return $doSet;
  322
+		return $list;
338 323
 	}
339 324
 
340  
-
341  
-This introduces a few new concepts, so let's step through it.
  325
+This code introduces a few new concepts, so let's step through it.
342 326
 
343 327
 	:::php
344  
-	$submissions = BrowserPollSubmission::get();
  328
+	$submissions = new GroupedList(BrowserPollSubmission::get());
345 329
 
346 330
 
347  
-First we get all of the *BrowserPollSubmission*s from the database. This returns the submissions as a
348  
-DataObjectSet, which contains the submissions as *BrowserPollSubmission* objects.
  331
+First we get all of the `BrowserPollSubmission` records from the database. 
  332
+This returns the submissions as a `[api:DataList]`.
  333
+Then we wrap it inside a `[api:GroupedList]`, which adds the ability
  334
+to group those records. The resulting object will behave just like
  335
+the original `DataList`, though (with the addition of a `groupBy()` method).
349 336
 
350 337
 	:::php
351 338
 	$total = $submissions->Count();
@@ -354,29 +341,24 @@ DataObjectSet, which contains the submissions as *BrowserPollSubmission* objects