diff --git a/admin/javascript/LeftAndMain.Menu.js b/admin/javascript/LeftAndMain.Menu.js index 6a430beba..093f199d0 100644 --- a/admin/javascript/LeftAndMain.Menu.js +++ b/admin/javascript/LeftAndMain.Menu.js @@ -204,6 +204,7 @@ fromContainingPanel: { ontoggle: function(e){ this.toggleClass('collapsed', $(e.target).hasClass('collapsed')); + $(window).resize(); //Trigger jLayout } }, diff --git a/control/Director.php b/control/Director.php index 0236f8a68..46565d7b5 100644 --- a/control/Director.php +++ b/control/Director.php @@ -507,28 +507,28 @@ class Director implements TemplateGlobalProvider { */ public static function is_https() { $return = false; + + // See https://en.wikipedia.org/wiki/List_of_HTTP_header_fields + // See https://support.microsoft.com/?kbID=307347 + $headerOverride = false; + if(TRUSTED_PROXY) { + $headers = (defined('SS_TRUSTED_PROXY_PROTOCOL_HEADER')) ? array(SS_TRUSTED_PROXY_PROTOCOL_HEADER) : null; + if(!$headers) { + // Backwards compatible defaults + $headers = array('HTTP_X_FORWARDED_PROTO', 'HTTP_X_FORWARDED_PROTOCOL', 'HTTP_FRONT_END_HTTPS'); + } + foreach($headers as $header) { + $headerCompareVal = ($header === 'HTTP_FRONT_END_HTTPS' ? 'on' : 'https'); + if(!empty($_SERVER[$header]) && strtolower($_SERVER[$header]) == $headerCompareVal) { + $headerOverride = true; + break; + } + } + } + if ($protocol = Config::inst()->get('Director', 'alternate_protocol')) { $return = ($protocol == 'https'); - } else if( - TRUSTED_PROXY - && isset($_SERVER['HTTP_X_FORWARDED_PROTO']) - && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https' - ) { - // Convention for (non-standard) proxy signaling a HTTPS forward, - // see https://en.wikipedia.org/wiki/List_of_HTTP_header_fields - $return = true; - } else if( - TRUSTED_PROXY - && isset($_SERVER['HTTP_X_FORWARDED_PROTOCOL']) - && strtolower($_SERVER['HTTP_X_FORWARDED_PROTOCOL']) == 'https' - ) { - // Less conventional proxy header - $return = true; - } else if( - isset($_SERVER['HTTP_FRONT_END_HTTPS']) - && strtolower($_SERVER['HTTP_FRONT_END_HTTPS']) == 'on' - ) { - // Microsoft proxy convention: https://support.microsoft.com/?kbID=307347 + } else if($headerOverride) { $return = true; } else if((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off')) { $return = true; diff --git a/control/HTTPRequest.php b/control/HTTPRequest.php index 1aab8f26a..a75131371 100644 --- a/control/HTTPRequest.php +++ b/control/HTTPRequest.php @@ -655,14 +655,27 @@ class SS_HTTPRequest implements ArrayAccess { * @return string */ public function getIP() { - if (TRUSTED_PROXY && !empty($_SERVER['HTTP_CLIENT_IP'])) { - //check ip from share internet - return $_SERVER['HTTP_CLIENT_IP']; - } elseif (TRUSTED_PROXY && !empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { - //to check ip is pass from proxy - return $_SERVER['HTTP_X_FORWARDED_FOR']; + $headerOverrideIP = null; + if(TRUSTED_PROXY) { + $headers = (defined('SS_TRUSTED_PROXY_IP_HEADER')) ? array(SS_TRUSTED_PROXY_IP_HEADER) : null; + if(!$headers) { + // Backwards compatible defaults + $headers = array('HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR'); + } + foreach($headers as $header) { + if(!empty($_SERVER[$header])) { + $headerOverrideIP = $_SERVER[$header]; + break; + } + } + } + + if ($headerOverrideIP) { + return $headerOverrideIP; } elseif(isset($_SERVER['REMOTE_ADDR'])) { return $_SERVER['REMOTE_ADDR']; + } else { + return null; } } diff --git a/core/Constants.php b/core/Constants.php index a0d1ebe19..147d46a56 100644 --- a/core/Constants.php +++ b/core/Constants.php @@ -122,7 +122,7 @@ if(!defined('TRUSTED_PROXY')) { */ if(!isset($_SERVER['HTTP_HOST'])) { // HTTP_HOST, REQUEST_PORT, SCRIPT_NAME, and PHP_SELF - global $_FILE_TO_URL_MAPPING; + global $_FILE_TO_URL_MAPPING; if(isset($_FILE_TO_URL_MAPPING)) { $fullPath = $testPath = realpath($_SERVER['SCRIPT_FILENAME']); while($testPath && $testPath != '/' && !preg_match('/^[A-Z]:\\\\$/', $testPath)) { @@ -179,10 +179,13 @@ if(!isset($_SERVER['HTTP_HOST'])) { /** * Fix HTTP_HOST from reverse proxies */ - if (TRUSTED_PROXY && isset($_SERVER['HTTP_X_FORWARDED_HOST'])) { - + $trustedProxyHeader = (defined('SS_TRUSTED_PROXY_HOST_HEADER')) + ? SS_TRUSTED_PROXY_HOST_HEADER + : 'HTTP_X_FORWARDED_HOST'; + + if (TRUSTED_PROXY && !empty($_SERVER[$trustedProxyHeader])) { // Get the first host, in case there's multiple separated through commas - $_SERVER['HTTP_HOST'] = strtok($_SERVER['HTTP_X_FORWARDED_HOST'], ','); + $_SERVER['HTTP_HOST'] = strtok($_SERVER[SS_TRUSTED_PROXY_HOST_HEADER], ','); } } diff --git a/core/startup/ParameterConfirmationToken.php b/core/startup/ParameterConfirmationToken.php index 2798add9f..07468934e 100644 --- a/core/startup/ParameterConfirmationToken.php +++ b/core/startup/ParameterConfirmationToken.php @@ -163,26 +163,25 @@ class ParameterConfirmationToken { // Are we http or https? Replicates Director::is_https() without its dependencies/ $proto = 'http'; - if( - TRUSTED_PROXY - && isset($_SERVER['HTTP_X_FORWARDED_PROTO']) - && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https' - ) { - // Convention for (non-standard) proxy signaling a HTTPS forward, - // see https://en.wikipedia.org/wiki/List_of_HTTP_header_fields - $proto = 'https'; - } else if( - TRUSTED_PROXY - && isset($_SERVER['HTTP_X_FORWARDED_PROTOCOL']) - && strtolower($_SERVER['HTTP_X_FORWARDED_PROTOCOL']) == 'https' - ) { - // Less conventional proxy header - $proto = 'https'; - } else if( - isset($_SERVER['HTTP_FRONT_END_HTTPS']) - && strtolower($_SERVER['HTTP_FRONT_END_HTTPS']) == 'on' - ) { - // Microsoft proxy convention: https://support.microsoft.com/?kbID=307347 + // See https://en.wikipedia.org/wiki/List_of_HTTP_header_fields + // See https://support.microsoft.com/?kbID=307347 + $headerOverride = false; + if(TRUSTED_PROXY) { + $headers = (defined('SS_TRUSTED_PROXY_PROTOCOL_HEADER')) ? array(SS_TRUSTED_PROXY_PROTOCOL_HEADER) : null; + if(!$headers) { + // Backwards compatible defaults + $headers = array('HTTP_X_FORWARDED_PROTO', 'HTTP_X_FORWARDED_PROTOCOL', 'HTTP_FRONT_END_HTTPS'); + } + foreach($headers as $header) { + $headerCompareVal = ($header === 'HTTP_FRONT_END_HTTPS' ? 'on' : 'https'); + if(!empty($_SERVER[$header]) && strtolower($_SERVER[$header]) == $headerCompareVal) { + $headerOverride = true; + break; + } + } + } + + if($headerOverride) { $proto = 'https'; } else if((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off')) { $proto = 'https'; @@ -190,9 +189,6 @@ class ParameterConfirmationToken { $proto = 'https'; } - if((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off')) $proto = 'https'; - if(isset($_SERVER['SSL'])) $proto = 'https'; - $parts = array_filter(array( // What's our host $_SERVER['HTTP_HOST'], diff --git a/dev/DevelopmentAdmin.php b/dev/DevelopmentAdmin.php index c6963a7d2..55584cb20 100644 --- a/dev/DevelopmentAdmin.php +++ b/dev/DevelopmentAdmin.php @@ -2,7 +2,7 @@ /** * Base class for development tools. - * + * * Configured in framework/_config/dev.yml, with the config key registeredControllers being * used to generate the list of links for /dev. * @@ -21,9 +21,9 @@ class DevelopmentAdmin extends Controller { 'generatesecuretoken' => 'generatesecuretoken', '$Action' => 'runRegisteredController', ); - - private static $allowed_actions = array( - 'index', + + private static $allowed_actions = array( + 'index', 'buildDefaults', 'runRegisteredController', 'generatesecuretoken', @@ -33,7 +33,8 @@ class DevelopmentAdmin extends Controller { parent::init(); // Special case for dev/build: Defer permission checks to DatabaseAdmin->init() (see #4957) - $requestedDevBuild = (stripos($this->getRequest()->getURL(), 'dev/build') === 0 && !Security::database_is_ready()); + $requestedDevBuild = (stripos($this->getRequest()->getURL(), 'dev/build') === 0) + && (stripos($this->getRequest()->getURL(), 'dev/build/defaults') === false); // We allow access to this controller regardless of live-status or ADMIN permission only // if on CLI. Access to this controller is always allowed in "dev-mode", or of the user is ADMIN. @@ -109,17 +110,17 @@ class DevelopmentAdmin extends Controller { public function runRegisteredController(SS_HTTPRequest $request){ $controllerClass = null; - + $baseUrlPart = $request->param('Action'); $reg = Config::inst()->get(__CLASS__, 'registered_controllers'); if(isset($reg[$baseUrlPart])){ $controllerClass = $reg[$baseUrlPart]['controller']; } - + if($controllerClass && class_exists($controllerClass)){ return $controllerClass::create(); } - + $msg = 'Error: no controller registered in '.__CLASS__.' for: '.$request->param('Action'); if(Director::is_cli()){ // in CLI we cant use httpError because of a bug with stuff being in the output already, see DevAdminControllerTest @@ -129,9 +130,9 @@ class DevelopmentAdmin extends Controller { } } - - - + + + /* * Internal methods */ @@ -141,7 +142,7 @@ class DevelopmentAdmin extends Controller { */ protected static function get_links(){ $links = array(); - + $reg = Config::inst()->get(__CLASS__, 'registered_controllers'); foreach($reg as $registeredController){ foreach($registeredController['links'] as $url => $desc){ @@ -153,18 +154,18 @@ class DevelopmentAdmin extends Controller { protected function getRegisteredController($baseUrlPart){ $reg = Config::inst()->get(__CLASS__, 'registered_controllers'); - + if(isset($reg[$baseUrlPart])){ $controllerClass = $reg[$baseUrlPart]['controller']; return $controllerClass; } - + return null; } - - - - + + + + /* * Unregistered (hidden) actions */ diff --git a/dev/install/config-form.html b/dev/install/config-form.html index c8475d3b1..5973e46cc 100644 --- a/dev/install/config-form.html +++ b/dev/install/config-form.html @@ -19,7 +19,7 @@

CMS / Framework Installation Version $silverstripe_version"; ?>

Thanks for choosing to use SilverStripe! Please follow the instructions below and you should be up in running in no time.
- If you get stuck, head over to the installation forum, or check out our list of suggested web hosts known to work with SilverStripe. + If you get stuck, head over to the installation forum, or check out our page of suggested web hosting options known to work with SilverStripe.

@@ -32,7 +32,7 @@

You aren't currently able to install the software. Please see below for details.
- If you are having problems meeting the requirements, see the server requirements. + If you are having problems meeting the requirements, see the server requirements.

Your php.ini file is located at

@@ -46,7 +46,7 @@ hasWarnings()) { ?>

There are some issues that we recommend you look at before installing, however, you are still able to install the software. -
Please see below for details. If you are having problems meeting the requirements, see the server requirements.

+
Please see below for details. If you are having problems meeting the requirements, see the server requirements.

hasErrors() && !$adminReq->hasErrors()) { ?>

You're ready to install! Please confirm the configuration options below. Install SilverStripe

@@ -243,7 +243,7 @@

Theme selection Step 4 of 5

You can change the theme or download another from the SilverStripe website after installation.

Confirm Install Step 5 of 5

diff --git a/docs/en/00_Getting_Started/00_Server_Requirements.md b/docs/en/00_Getting_Started/00_Server_Requirements.md index e4dcdfd82..aa7ddeedc 100644 --- a/docs/en/00_Getting_Started/00_Server_Requirements.md +++ b/docs/en/00_Getting_Started/00_Server_Requirements.md @@ -23,7 +23,7 @@ Our web-based [PHP installer](installation/) can check if you meet the requireme * MySQL 5.0+ * PostgreSQL 8.3+ (requires ["postgresql" module](http://silverstripe.org/postgresql-module)) * SQL Server 2008+ (requires ["mssql" module](http://silverstripe.org/microsoft-sql-server-database/)) - * Support for [Oracle](http://www.silverstripe.org/oracle-database-module/) and [SQLite](http://silverstripe.org/sqlite-database/) is not commercially supported, but is under development by our open source community. + * Support for `[Oracle](http://www.silverstripe.org/oracle-database-module/)` and [SQLite](http://silverstripe.org/sqlite-database/) is not commercially supported, but is under development by our open source community. * One of the following web server products: * Apache 2.0+ with mod_rewrite and "AllowOverride All" set * IIS 7+ diff --git a/docs/en/00_Getting_Started/01_Installation/05_Common_Problems.md b/docs/en/00_Getting_Started/01_Installation/05_Common_Problems.md index d40229792..5e3315767 100644 --- a/docs/en/00_Getting_Started/01_Installation/05_Common_Problems.md +++ b/docs/en/00_Getting_Started/01_Installation/05_Common_Problems.md @@ -71,7 +71,7 @@ every page on the site, if that's easier. ## I can see unparsed PHP output in my browser -Please make sure all code inside `*.php` files is wrapped in classes. Due to the way `[api:ManifestBuilder]` +Please make sure all code inside `*.php` files is wrapped in classes. Due to the way [api:ManifestBuilder] includes all files with this extension, any **procedural code will be executed on every call**. The most common error here is putting a test.php/phpinfo.php file in the document root. See [datamodel](/developer_guides/model/data_model_and_orm) and [controllers](/developer_guides/controllers) for ways how to structure your code. diff --git a/docs/en/01_Tutorials/02_Extending_A_Basic_Site.md b/docs/en/01_Tutorials/02_Extending_A_Basic_Site.md index 36b484a3c..f9c028784 100644 --- a/docs/en/01_Tutorials/02_Extending_A_Basic_Site.md +++ b/docs/en/01_Tutorials/02_Extending_A_Basic_Site.md @@ -36,11 +36,11 @@ final page. Lets look at each one individually: ### Model -All content on our site is stored in a database. Each class that is a child of the `[api:DataObject]` class will have its own table in our database. +All content on our site is stored in a database. Each class that is a child of the [api:DataObject] class will have its own table in our database. Every object of such a class will correspond to a row in that table - this is our "data object", the **"model"** of Model-View-Controller. A page type has a data object that represents all the data for our page. Rather than inheriting -directly from `[api:DataObject]`, it inherits from `[api:SiteTree]`. We generally create a "Page" data object, and subclass this for all other page types. This allows us to define behavior that is consistent across all pages in our site. +directly from [api:DataObject], it inherits from [api:SiteTree]. We generally create a "Page" data object, and subclass this for all other page types. This allows us to define behavior that is consistent across all pages in our site. ### View @@ -49,7 +49,7 @@ presentation of our website. ### Controller -Each page type also has a **"controller"**. The controller contains all the code used to manipulate our data before it is rendered. For example, suppose we were making an auction site, and we only wanted to display the auctions closing in the next ten minutes. We would implement this logic in the controller. The controller for a page should inherit from `[api:ContentController]`. Just as we create a "Page" data object and subclass it for the rest of the site, we also create a "Page_Controller" that is subclassed. +Each page type also has a **"controller"**. The controller contains all the code used to manipulate our data before it is rendered. For example, suppose we were making an auction site, and we only wanted to display the auctions closing in the next ten minutes. We would implement this logic in the controller. The controller for a page should inherit from [api:ContentController]. Just as we create a "Page" data object and subclass it for the rest of the site, we also create a "Page_Controller" that is subclassed. Creating a new page type requires creating each of these three elements. We will then have full control over presentation, the database, and editable CMS fields. @@ -95,7 +95,7 @@ Let's create the *ArticleHolder* page type. Here we have done something interesting: the *$allowed_children* field. This is one of a number of static fields we can define to change the properties of a page type. The *$allowed_children* field is an array of page types that are allowed to be children of the page in the site tree. As we only want **news articles** in the news section, we only want pages of the type *ArticlePage* as children. We can enforce this in the CMS by setting the *$allowed_children* field within this class. -We will be introduced to other fields like this as we progress; there is a full list in the documentation for `[api:SiteTree]`. +We will be introduced to other fields like this as we progress; there is a full list in the documentation for [api:SiteTree]. Now that we have created our page types, we need to let SilverStripe rebuild the database: [http://localhost/your_site_name/dev/build](http://localhost/your_site_name/dev/build). SilverStripe should detect that there are two new page types, and add them to the list of page types in the database. @@ -162,7 +162,7 @@ Let's walk through this method. Firstly, we get the fields from the parent class; we want to add fields, not replace them. The *$fields* variable -returned is a `[api:FieldList]` object. +returned is a [api:FieldList] object. :::php $fields->addFieldToTab('Root.Main', new TextField('Author'), 'Content'); @@ -170,14 +170,14 @@ returned is a `[api:FieldList]` object. We can then add our new fields with *addFieldToTab*. The first argument is the tab on which we want to add the field to: -"Root.Main" is the tab which the content editor is on. The second argument is the field to add; this is not a database field, but a `[api:FormField]` - see the documentation for more details. +"Root.Main" is the tab which the content editor is on. The second argument is the field to add; this is not a database field, but a [api:FormField] - see the documentation for more details.
Note: By default, the CMS only has one tab. Creating new tabs is much like adding to existing tabs. For instance: `$fields->addFieldToTab('Root.NewTab', new TextField('Author'));` would create a new tab called "New Tab", and a single "Author" textfield inside.
-We have added two fields: A simple `[api:TextField]` and a `[api:DateField]`. +We have added two fields: A simple [api:TextField]` and a [api:DateField]. There are many more fields available in the default installation, listed in ["form field types"](/developer_guides/forms/field_types/common_subclasses). :::php @@ -232,7 +232,7 @@ By enabling *showCalendar* you show a calendar overlay when clicking on the fiel :::php $dateField->setConfig('dateformat', 'dd/MM/YYYY'); -*dateFormat* allows you to specify how you wish the date to be entered and displayed in the CMS field. See the `[api:DateField]` documentation for more configuration options. +*dateFormat* allows you to specify how you wish the date to be entered and displayed in the CMS field. See the [api:DateField] documentation for more configuration options. :::php $fields->addFieldToTab('Root.Main', new TextField('Author', 'Author Name'), 'Content'); @@ -269,13 +269,13 @@ First, the template for displaying a single article: Most of the code is just like the regular Page.ss, we include an informational div with the date and the author of the Article. -To access the new fields, we use *$Date* and *$Author*. In fact, all template variables and page controls come from either the data object or the controller for the page being displayed. The *$Title* variable comes from the *Title* field of the `[api:SiteTree]` class. *$Date* and *$Author* come from the *ArticlePage* table through your custom Page. *$Content* comes from the *SiteTree* table through the same data object. The data for your page is +To access the new fields, we use *$Date* and *$Author*. In fact, all template variables and page controls come from either the data object or the controller for the page being displayed. The *$Title* variable comes from the *Title* field of the [api:SiteTree] class. *$Date* and *$Author* come from the *ArticlePage* table through your custom Page. *$Content* comes from the *SiteTree* table through the same data object. The data for your page is spread across several tables in the database matched by id - e.g. *Content* is in the *SiteTree* table, and *Date* and *Author* are in the *ArticlePage* table. SilverStripe matches this data, and collates it into a single data object. ![](../_images/tutorial2_data-collation.jpg) -Rather than using *$Date* directly, we use *$Date.Nice*. If we look in the `[api:Date]` documentation, we can see +Rather than using *$Date* directly, we use *$Date.Nice*. If we look in the [api:Date] documentation, we can see that the *Nice* function returns the date in *dd/mm/yyyy* format, rather than the *yyyy-mm-dd* format stored in the database. @@ -305,7 +305,7 @@ We'll now create a template for the article holder. We want our news section to
-Here we use the page control *Children*. As the name suggests, this control allows you to iterate over the children of a page. In this case, the children are our news articles. The *$Link* variable will give the address of the article which we can use to create a link, and the *FirstParagraph* function of the `[api:HTMLText]` field gives us a nice summary of the article. The function strips all tags from the paragraph extracted. +Here we use the page control *Children*. As the name suggests, this control allows you to iterate over the children of a page. In this case, the children are our news articles. The *$Link* variable will give the address of the article which we can use to create a link, and the *FirstParagraph* function of the [api:HTMLText] field gives us a nice summary of the article. The function strips all tags from the paragraph extracted. ![](../_images/tutorial2_articleholder.jpg) @@ -398,7 +398,7 @@ The controller for a page is only created when page is actually visited, while t ## Creating a RSS feed -An RSS feed is something that no news section should be without. SilverStripe makes it easy to create RSS feeds by providing an `[api:RSSFeed]` class to do all the hard work for us. Add the following in the *ArticleHolder_Controller* class: +An RSS feed is something that no news section should be without. SilverStripe makes it easy to create RSS feeds by providing an [api:RSSFeed] class to do all the hard work for us. Add the following in the *ArticleHolder_Controller* class: **mysite/code/ArticleHolder.php** @@ -418,7 +418,7 @@ Ensure that when you have input the code to implement an RSS feed; flush the web This function creates an RSS feed of all the news articles, and outputs it to the browser. If we go to [http://localhost/your_site_name/news/rss](http://localhost/your_site_name/news/rss) we should see our RSS feed. When there is more to a URL after a page's base URL, "rss" in this case, SilverStripe will call the function with that name on the controller if it exists. -Depending on your browser, you should see something like the picture below. If your browser doesn't support RSS, you will most likely see the XML output instead. For more on RSS, see `[api:RSSFeed]` +Depending on your browser, you should see something like the picture below. If your browser doesn't support RSS, you will most likely see the XML output instead. For more on RSS, see [api:RSSFeed] ![](../_images/tutorial2_rss-feed.jpg) @@ -483,7 +483,7 @@ Nothing here should be new. The *StaffPage* page type is more interesting though Instead of adding our *Image* as a field in *$db*, we have used the *$has_one* array. This is because an *Image* is not a simple database field like all the fields we have seen so far, but has its own database table. By using the *$has_one* array, we create a relationship between the *StaffPage* table and the *Image* table by storing the id of the respective *Image* in the *StaffPage* table. -We then add an `[api:UploadField]` in the *getCMSFields* function to the tab "Root.Images". Since this tab doesn't exist, +We then add an [api:UploadField] in the *getCMSFields* function to the tab "Root.Images". Since this tab doesn't exist, the *addFieldToTab* function will create it for us. The *UploadField* allows us to select an image or upload a new one in the CMS. @@ -497,7 +497,7 @@ a new *StaffHolder* called "Staff", and create some *StaffPage*s in it. ### Creating the staff section templates -The staff section templates aren't too difficult to create, thanks to the utility methods provided by the `[api:Image]` class. +The staff section templates aren't too difficult to create, thanks to the utility methods provided by the [api:Image] class. **themes/simple/templates/Layout/StaffHolder.ss** @@ -521,7 +521,7 @@ The staff section templates aren't too difficult to create, thanks to the utilit -This template is very similar to the *ArticleHolder* template. The *ScaleWidth* method of the `[api:Image]` class +This template is very similar to the *ArticleHolder* template. The *ScaleWidth* method of the [api:Image] class will resize the image before sending it to the browser. The resized image is cached, so the server doesn't have to resize the image every time the page is viewed. diff --git a/docs/en/01_Tutorials/03_Forms.md b/docs/en/01_Tutorials/03_Forms.md index b88756e69..168829262 100644 --- a/docs/en/01_Tutorials/03_Forms.md +++ b/docs/en/01_Tutorials/03_Forms.md @@ -74,11 +74,11 @@ Let's step through this code. ``` First we create our form fields. -We do this by creating a `[api:FieldList]` and passing our fields as arguments. -The first field is a `[api:TextField]` with the name 'Name'. +We do this by creating a [api:FieldList] and passing our fields as arguments. +The first field is a [api:TextField] with the name 'Name'. There is a second argument when creating a field which specifies the text on the label of the field. If no second argument is passed, as in this case, it is assumed the label is the same as the name of the field. -The second field we create is an `[api:OptionsetField]`. This is a dropdown, and takes a third argument - an +The second field we create is an [api:OptionsetField]. This is a dropdown, and takes a third argument - an array mapping the values to the options listed in the dropdown. ```php @@ -90,14 +90,14 @@ array mapping the values to the options listed in the dropdown. After creating the fields, we create the form actions. Form actions appear as buttons at the bottom of the form. The first argument is the name of the function to call when the button is pressed, and the second is the label of the button. Here we create a 'Submit' button which calls the 'doBrowserPoll' method, which we will create later. -All the form actions (in this case only one) are collected into a `[api:FieldList]` object the same way we did with +All the form actions (in this case only one) are collected into a [api:FieldList] object the same way we did with the fields. ```php return new Form($this, 'BrowserPollForm', $fields, $actions); ``` -Finally we create the `[api:Form]` object and return it. +Finally we create the [api:Form] object and return it. The first argument is the controller that contains the form, in most cases '$this'. The second is the name of the method that returns the form, which is 'BrowserPollForm' in our case. The third and fourth arguments are the FieldLists containing the fields and form actions respectively. @@ -178,8 +178,8 @@ All going according to plan, if you visit [http://localhost/your_site_name/home/ Great! We now have a browser poll form, but it doesn't actually do anything. In order to make the form work, we have to implement the 'doBrowserPoll()' method that we told it about. -First, we need some way of saving the poll submissions to the database, so we can retrieve the results later. We can do this by creating a new object that extends from `[api:DataObject]`. -If you recall, in the [second tutorial](/tutorials/extending_a_basic_site) we said that all objects that inherit from DataObject and have their own fields are stored in tables the database. Also recall that all pages extend DataObject indirectly through `[api:SiteTree]`. Here instead of extending SiteTree (or `[api:Page]`) to create a page type, we will extend `[api:DataObject]` directly: +First, we need some way of saving the poll submissions to the database, so we can retrieve the results later. We can do this by creating a new object that extends from [api:DataObject]. +If you recall, in the [second tutorial](/tutorials/extending_a_basic_site) we said that all objects that inherit from DataObject and have their own fields are stored in tables the database. Also recall that all pages extend DataObject indirectly through [api:SiteTree]. Here instead of extending SiteTree (or [api:Page]) to create a page type, we will extend [api:DataObject] directly: **mysite/code/BrowserPollSubmission.php** @@ -208,7 +208,7 @@ If we then rebuild the database ([http://localhost/your_site_name/dev/build](htt } ``` -A function that processes a form submission takes two arguments - the first is the data in the form, the second is the `[api:Form]` object. +A function that processes a form submission takes two arguments - the first is the data in the form, the second is the [api:Form] object. In our function we create a new *BrowserPollSubmission* object. Since the name of our form fields, and the name of the database fields, are the same we can save the form directly into the data object. We call the 'write' method to write our data to the database, and '$this->redirectBack()' will redirect the user back to the home page. @@ -240,7 +240,7 @@ If we then open the homepage and attempt to submit the form without filling in t Now that we have a working form, we need some way of showing the results. The first thing to do is make it so a user can only vote once per session. If the user hasn't voted, show the form, otherwise show the results. -We can do this using a session variable. The `[api:Session]` class handles all session variables in SilverStripe. First modify the 'doBrowserPoll' to set the session variable 'BrowserPollVoted' when a user votes. +We can do this using a session variable. The [api:Session] class handles all session variables in SilverStripe. First modify the 'doBrowserPoll' to set the session variable 'BrowserPollVoted' when a user votes. **mysite/code/HomePage.php** @@ -279,7 +279,7 @@ Although the form is not shown, you'll still see the 'Browser Poll' heading. We' Now that we're collecting data, it would be nice to show the results on the website as well. We could simply output every vote, but that's boring. Let's group the results by browser, through the SilverStripe data model. -In the [second tutorial](/tutorials/extending_a_basic_site), we got a collection of news articles for the home page by using the 'ArticleHolder::get()' function, which returns a `[api:DataList]`. We can get all submissions in the same fashion, through `BrowserPollSubmission::get()`. This list will be the starting point for our result aggregation. +In the [second tutorial](/tutorials/extending_a_basic_site), we got a collection of news articles for the home page by using the 'ArticleHolder::get()' function, which returns a [api:DataList]. We can get all submissions in the same fashion, through `BrowserPollSubmission::get()`. This list will be the starting point for our result aggregation. Create the function 'BrowserPollResults' on the *HomePage_Controller* class. @@ -306,7 +306,7 @@ This code introduces a few new concepts, so let's step through it. ```php $submissions = new GroupedList(BrowserPollSubmission::get()); ``` -First we get all of the `BrowserPollSubmission` records from the database. This returns the submissions as a `[api:DataList]`.Then we wrap it inside a `[api:GroupedList]`, which adds the ability to group those records. The resulting object will behave just like the original `DataList`, though (with the addition of a `groupBy()` method). +First we get all of the `BrowserPollSubmission` records from the database. This returns the submissions as a [api:DataList]. Then we wrap it inside a [api:GroupedList], which adds the ability to group those records. The resulting object will behave just like the original `DataList`, though (with the addition of a `groupBy()` method). ```php $total = $submissions->Count(); @@ -324,9 +324,9 @@ We get the total number of submissions, which is needed to calculate the percent } ``` -Now we create an empty `[api:ArrayList]` to hold the data we'll pass to the template. Its similar to `[api:DataList]`, but can hold arbitrary objects rather than just DataObject` instances. Then we iterate over the 'Browser' submissions field. +Now we create an empty [api:ArrayList] to hold the data we'll pass to the template. Its similar to [api:DataList], but can hold arbitrary objects rather than just DataObject` instances. Then we iterate over the 'Browser' submissions field. -The `groupBy()` method splits our list by the 'Browser' field passed to it, creating new lists with submissions just for a specific browser. Each of those lists is keyed by the browser name. The aggregated result is then contained in an `[api:ArrayData]` object, which behaves much like a standard PHP array, but allows us to use it in SilverStripe templates. +The `groupBy()` method splits our list by the 'Browser' field passed to it, creating new lists with submissions just for a specific browser. Each of those lists is keyed by the browser name. The aggregated result is then contained in an [api:ArrayData] object, which behaves much like a standard PHP array, but allows us to use it in SilverStripe templates. The final step is to create the template to display our data. Change the 'BrowserPoll' div to the below. diff --git a/docs/en/01_Tutorials/04_Site_Search.md b/docs/en/01_Tutorials/04_Site_Search.md index 1d12c908e..a5db20590 100644 --- a/docs/en/01_Tutorials/04_Site_Search.md +++ b/docs/en/01_Tutorials/04_Site_Search.md @@ -92,7 +92,7 @@ function, and then attempt to render it with *Page_results.ss*, falling back to ## Creating the template Lastly we need the template for the search page. This template uses all the same techniques used in previous -tutorials. It also uses a number of pagination variables, which are provided by the `[api:PaginatedList]` +tutorials. It also uses a number of pagination variables, which are provided by the [api:PaginatedList] class. *themes/simple/templates/Layout/Page_results.ss* diff --git a/docs/en/01_Tutorials/05_Dataobject_Relationship_Management.md b/docs/en/01_Tutorials/05_Dataobject_Relationship_Management.md index 003a91856..c52e6f576 100644 --- a/docs/en/01_Tutorials/05_Dataobject_Relationship_Management.md +++ b/docs/en/01_Tutorials/05_Dataobject_Relationship_Management.md @@ -160,7 +160,7 @@ It's empty by default, but you can add new students as required, or relate them to the project by typing in the box above the table. In our case, we want to manage those records, edit their details, and add new ones. -To accomplish this, we have added a specific `[api:GridFieldConfig]`. +To accomplish this, we have added a specific [api:GridFieldConfig]. While we could've built the config from scratch, there's several preconfigured instances. The `GridFieldConfig_RecordEditor` default configures the field to edit records, rather than just viewing them. @@ -427,4 +427,4 @@ we suggest some excercises to make the solution more flexible: and avoid any duplication between the two subclasses. * Render mentor details in their own template * Change the `GridField` to list only five records per page (the default is 20). - This configuration is stored in the `[api:GridFieldPaginator]` component + This configuration is stored in the [api:GridFieldPaginator] component diff --git a/docs/en/02_Developer_Guides/00_Model/01_Data_Model_and_ORM.md b/docs/en/02_Developer_Guides/00_Model/01_Data_Model_and_ORM.md index 9b06821ce..105f19879 100644 --- a/docs/en/02_Developer_Guides/00_Model/01_Data_Model_and_ORM.md +++ b/docs/en/02_Developer_Guides/00_Model/01_Data_Model_and_ORM.md @@ -461,7 +461,7 @@ Occasionally, the system described above won't let you do exactly what you need methods that manipulate the SQL query at a lower level. When using these, please ensure that all table and field names are escaped with double quotes, otherwise some DB backends (e.g. PostgreSQL) won't work. -Under the hood, query generation is handled by the `[api:DataQuery]` class. This class does provide more direct access +Under the hood, query generation is handled by the [api:DataQuery] class. This class does provide more direct access to certain SQL features that `DataList` abstracts away from you. In general, we advise against using these methods unless it's absolutely necessary. If the ORM doesn't do quite what @@ -573,7 +573,7 @@ sub-classes of the base class (including the base class itself). example above, NewsSection didn't have its own data, so an extra table would be redundant. * In all the tables, ID is the primary key. A matching ID number is used for all parts of a particular record: -record #2 in Page refers to the same object as record #2 in `[api:SiteTree]`. +record #2 in Page refers to the same object as record #2 in [api:SiteTree]. To retrieve a news article, SilverStripe joins the [api:SiteTree], [api:Page] and NewsPage tables by their ID fields. diff --git a/docs/en/02_Developer_Guides/00_Model/02_Relations.md b/docs/en/02_Developer_Guides/00_Model/02_Relations.md index 2480448b8..b3cb2bd1c 100644 --- a/docs/en/02_Developer_Guides/00_Model/02_Relations.md +++ b/docs/en/02_Developer_Guides/00_Model/02_Relations.md @@ -295,7 +295,7 @@ and `remove()` method. You can use the ORM to get a filtered result list without writing any SQL. For example, this snippet gets you the "Players"-relation on a team, but only containing active players. -See `[api:DataObject::$has_many]` for more info on the described relations. +See [api:DataObject::$has_many] for more info on the described relations. :::php where(array('ID' => 3)); + $update = SQLUpdate::create('"SiteTree"')->addWhere(array('ID' => 3)); // assigning a list of items $update->addAssignments(array( @@ -279,4 +279,4 @@ An alternative approach would be a custom getter in the object definition. * [api:SQLSelect] * [api:DB] * [api:Query] -* [api:Database] \ No newline at end of file +* [api:Database] diff --git a/docs/en/02_Developer_Guides/00_Model/09_Validation.md b/docs/en/02_Developer_Guides/00_Model/09_Validation.md index 045fd0d6d..3c668f21b 100644 --- a/docs/en/02_Developer_Guides/00_Model/09_Validation.md +++ b/docs/en/02_Developer_Guides/00_Model/09_Validation.md @@ -9,7 +9,7 @@ While this is a useful approach, it can lead to data inconsistencies if the reco controller and form context. Most validation constraints are actually data constraints which belong on the model. SilverStripe provides the -[api:DataObject->validate] method for this purpose. +[api:DataObject::validate()] method for this purpose. By default, there is no validation - objects are always valid! However, you can overload this method in your DataObject sub-classes to specify custom validation, or use the `validate` hook through a [api:DataExtension]. diff --git a/docs/en/02_Developer_Guides/00_Model/10_Versioning.md b/docs/en/02_Developer_Guides/00_Model/10_Versioning.md index 176bfc5fb..587d820fd 100644 --- a/docs/en/02_Developer_Guides/00_Model/10_Versioning.md +++ b/docs/en/02_Developer_Guides/00_Model/10_Versioning.md @@ -10,7 +10,7 @@ It is most commonly applied to pages in the CMS (the `SiteTree` class). Draft co from published content shown to your website visitors. Versioning in SilverStripe is handled through the [api:Versioned] class. As a [api:DataExtension] it is possible to -be applied to any `[api:DataObject]` subclass. The extension class will automatically update read and write operations +be applied to any [api:DataObject]` subclass. The extension class will automatically update read and write operations done via the ORM via the `augmentSQL` database hook. Adding Versioned to your `DataObject` subclass works the same as any other extension. It accepts two or more arguments @@ -82,7 +82,7 @@ The record is retrieved as a `DataObject`, but saving back modifications via `wr rather than modifying the existing one. -In order to get a list of all versions for a specific record, we need to generate specialized `[api:Versioned_Version]` +In order to get a list of all versions for a specific record, we need to generate specialized [api:Versioned_Version] objects, which expose the same database information as a `DataObject`, but also include information about when and how a record was published. @@ -95,9 +95,9 @@ a record was published. The usual call to `DataObject->write()` will write to whatever stage is currently active, as defined by the `Versioned::current_stage()` global setting. Each call will automatically create a new version in the -`_versions` table. To avoid this, use `[writeWithoutVersion()](api:Versioned->writeWithoutVersion())` instead. +`_versions` table. To avoid this, use [writeWithoutVersion()](api:Versioned::writeWithoutVersion()) instead. -To move a saved version from one stage to another, call `[writeToStage()](api:Versioned->writeToStage())` on the +To move a saved version from one stage to another, call [writeToStage()](api:Versioned::writeToStage()) on the object. The process of moving a version to a different stage is also called "publishing", so we've created a shortcut for this: `publish(, )`. @@ -132,7 +132,7 @@ is initialized. But it can also be set and reset temporarily to force a specific ### Custom SQL We generally discourage writing `Versioned` queries from scratch, due to the complexities involved through joining -multiple tables across an inherited table scheme (see `[api:Versioned->augmentSQL()]`). If possible, try to stick to +multiple tables across an inherited table scheme (see [api:Versioned::augmentSQL()]). If possible, try to stick to smaller modifications of the generated `DataList` objects. Example: Get the first 10 live records, filtered by creation date: diff --git a/docs/en/02_Developer_Guides/00_Model/11_Scaffolding.md b/docs/en/02_Developer_Guides/00_Model/11_Scaffolding.md index f66d57d15..ef0f8ddf3 100644 --- a/docs/en/02_Developer_Guides/00_Model/11_Scaffolding.md +++ b/docs/en/02_Developer_Guides/00_Model/11_Scaffolding.md @@ -146,7 +146,7 @@ To include relations (`$has_one`, `$has_many` and `$many_many`) in your search, ### Summary Fields Summary fields can be used to show a quick overview of the data for a specific [api:DataObject] record. The most common use -is their display as table columns, e.g. in the search results of a `[api:ModelAdmin]` CMS interface. +is their display as table columns, e.g. in the search results of a [api:ModelAdmin] CMS interface. :::php populateDefaults()] method will need to be overloaded. +[api:DataObject::populateDefaults()] method will need to be overloaded. This method is called whenever a new record is instantiated, and you must be sure to call the method on the parent object! diff --git a/docs/en/02_Developer_Guides/00_Model/How_Tos/Grouping_DataObject_Sets.md b/docs/en/02_Developer_Guides/00_Model/How_Tos/Grouping_DataObject_Sets.md index 5effa4379..85b12ed40 100644 --- a/docs/en/02_Developer_Guides/00_Model/How_Tos/Grouping_DataObject_Sets.md +++ b/docs/en/02_Developer_Guides/00_Model/How_Tos/Grouping_DataObject_Sets.md @@ -6,8 +6,8 @@ These lists can get quite long, and hard to present on a single list. by splitting up the list into multiple pages. In this howto, we present an alternative to pagination: -Grouping a list by various criteria, through the `[api:GroupedList]` class. -This class is a `[api:SS_ListDecorator]`, which means it wraps around a list, +Grouping a list by various criteria, through the [api:GroupedList] class. +This class is a [api:SS_ListDecorator], which means it wraps around a list, adding new functionality. It provides a `groupBy()` method, which takes a field name, and breaks up the managed list @@ -88,7 +88,7 @@ In this case, the `getTitleFirstLetter()` method defined earlier is used to brea Grouping a set by month is a very similar process. The only difference would be to sort the records by month name, and then create a method on the DataObject that returns the month name, -and pass that to the [api:GroupedList->GroupedBy()] call. +and pass that to the [api:GroupedList::GroupedBy()] call. We're reusing our example `Module` object, but grouping by its built-in `Created` property instead, @@ -128,7 +128,7 @@ sorted by month name from January to December. This can be accomplshed by sortin } -The final step is the render this into the template using the [api:GroupedList->GroupedBy()] method. +The final step is the render this into the template using the [api:GroupedList::GroupedBy()] method. :::ss // Modules list grouped by the Month Posted diff --git a/docs/en/02_Developer_Guides/01_Templates/02_Common_Variables.md b/docs/en/02_Developer_Guides/01_Templates/02_Common_Variables.md index 6b93222ee..275d10b00 100644 --- a/docs/en/02_Developer_Guides/01_Templates/02_Common_Variables.md +++ b/docs/en/02_Developer_Guides/01_Templates/02_Common_Variables.md @@ -403,7 +403,7 @@ You can add your own forms by implementing new form instances (see the [Forms tu ## API Documentation - * `[api:ContentController]`: The main controller responsible for handling pages. - * `[api:Controller]`: Generic controller (not specific to pages.) - * `[api:DataObject]`: Underlying model class for page objects. - * `[api:ViewableData]`: Underlying object class for pretty much anything displayable. + * [api:ContentController]: The main controller responsible for handling pages. + * [api:Controller]: Generic controller (not specific to pages.) + * [api:DataObject]: Underlying model class for page objects. + * [api:ViewableData]: Underlying object class for pretty much anything displayable. diff --git a/docs/en/02_Developer_Guides/01_Templates/03_Requirements.md b/docs/en/02_Developer_Guides/01_Templates/03_Requirements.md index 5a792fa0d..fdb6085bd 100644 --- a/docs/en/02_Developer_Guides/01_Templates/03_Requirements.md +++ b/docs/en/02_Developer_Guides/01_Templates/03_Requirements.md @@ -23,7 +23,7 @@ Requiring assets from the template is restricted compared to the PHP API. ## PHP Requirements API It is common practice to include most Requirements either in the *init()*-method of your [controller](../controllers/), or -as close to rendering as possible (e.g. in `[api:FormField]`. +as close to rendering as possible (e.g. in [api:FormField]). :::php -In case you want to explicitly allow un-escaped HTML input, the property can be cast as `[api:HTMLText]`. The following +In case you want to explicitly allow un-escaped HTML input, the property can be cast as [api:HTMLText]. The following example takes the `Content` field in a `SiteTree` class, which is of this type. It forces the content into an explicitly escaped format. diff --git a/docs/en/02_Developer_Guides/01_Templates/How_Tos/02_Pagination.md b/docs/en/02_Developer_Guides/01_Templates/How_Tos/02_Pagination.md index 46872a793..b33407f00 100644 --- a/docs/en/02_Developer_Guides/01_Templates/How_Tos/02_Pagination.md +++ b/docs/en/02_Developer_Guides/01_Templates/How_Tos/02_Pagination.md @@ -25,8 +25,8 @@ Note that the concept of "pages" used in pagination does not necessarily mean th it's just a term to describe a sub-collection of the list. -There are two ways to generate pagination controls: [api:PaginatedList->Pages] and -[api:PaginatedList->PaginationSummary]. In this example we will use `PaginationSummary()`. +There are two ways to generate pagination controls: [api:PaginatedList::Pages()] and +[api:PaginatedList::PaginationSummary()]. In this example we will use `PaginationSummary()`. The first step is to simply list the objects in the template: @@ -72,7 +72,7 @@ If there is more than one page, this block will render a set of pagination contr In some situations where you are generating the list yourself, the underlying list will already contain only the items that you wish to display on the current page. In this situation the automatic limiting done by [api:PaginatedList] -will break the pagination. You can disable automatic limiting using the [api:PaginatedList->setLimitItems] method +will break the pagination. You can disable automatic limiting using the [api:PaginatedList::setLimitItems()] method when using custom lists. :::php diff --git a/docs/en/02_Developer_Guides/02_Controllers/01_Introduction.md b/docs/en/02_Developer_Guides/02_Controllers/01_Introduction.md index 6decd4373..91c717ce6 100644 --- a/docs/en/02_Developer_Guides/02_Controllers/01_Introduction.md +++ b/docs/en/02_Developer_Guides/02_Controllers/01_Introduction.md @@ -159,7 +159,7 @@ Each controller should define a `Link()` method. This should be used to avoid ha }
-The [api:Controller::join_links] is optional, but makes `Link()` more flexible by allowing an `$action` argument, and concatenates the path segments with slashes. The action should map to a method on your controller. +The [api:Controller::join_links()] is optional, but makes `Link()` more flexible by allowing an `$action` argument, and concatenates the path segments with slashes. The action should map to a method on your controller.
## Related Documentation diff --git a/docs/en/02_Developer_Guides/02_Controllers/02_Routing.md b/docs/en/02_Developer_Guides/02_Controllers/02_Routing.md index bfd30e446..67f3f71c3 100644 --- a/docs/en/02_Developer_Guides/02_Controllers/02_Routing.md +++ b/docs/en/02_Developer_Guides/02_Controllers/02_Routing.md @@ -95,7 +95,7 @@ You can also fetch one parameter at a time. ## URL Patterns -The `[api:RequestHandler]` class will parse all rules you specify against the following patterns. The most specific rule +The [api:RequestHandler] class will parse all rules you specify against the following patterns. The most specific rule will be the one followed for the response.
diff --git a/docs/en/02_Developer_Guides/03_Forms/01_Validation.md b/docs/en/02_Developer_Guides/03_Forms/01_Validation.md index c3ff0caee..82ad5abd0 100644 --- a/docs/en/02_Developer_Guides/03_Forms/01_Validation.md +++ b/docs/en/02_Developer_Guides/03_Forms/01_Validation.md @@ -5,7 +5,7 @@ summary: Validate form data through the server side validation API. SilverStripe provides server-side form validation out of the box through the [api:Validator] class and its' child class [api:RequiredFields]. A single `Validator` instance is set on each `Form`. Validators are implemented as an argument to -the `[api:Form]` constructor or through the function `setValidator`. +the [api:Form] constructor or through the function `setValidator`. :::php Each individual [api:FormField] instance is responsible for validating the submitted content through the -[api:FormField::validate] method. By default, this just checks the value exists. Fields like `EmailField` override +[api:FormField::validate()] method. By default, this just checks the value exists. Fields like `EmailField` override `validate` to check for a specific format.
@@ -192,7 +192,7 @@ classes added to each input. For Parsley we can structure the form like. ## Model Validation An alternative (or additional) approach to validation is to place it directly on the database model. SilverStripe -provides a `[api:DataObject->validate]` method to validate data at the model level. See +provides a [api:DataObject::validate()] method to validate data at the model level. See [Data Model Validation](../model/validation). ### Validation in the CMS diff --git a/docs/en/02_Developer_Guides/03_Forms/Field_types/01_Common_Subclasses.md b/docs/en/02_Developer_Guides/03_Forms/Field_types/01_Common_Subclasses.md index 951578f22..c47366379 100644 --- a/docs/en/02_Developer_Guides/03_Forms/Field_types/01_Common_Subclasses.md +++ b/docs/en/02_Developer_Guides/03_Forms/Field_types/01_Common_Subclasses.md @@ -3,73 +3,73 @@ summary: A table containing a list of the common FormField subclasses. # Common FormField type subclasses -This is a high level overview of available `[api:FormField]` subclasses. An automatically generated list is available +This is a high level overview of available [api:FormField] subclasses. An automatically generated list is available on the SilverStripe API documentation. ## Basic - * `[api:CheckboxField]`: Single checkbox field. - * `[api:DropdownField]`: A `` tag. Can optionally save into has-one relationships. + * [api:ReadonlyField]: Read-only field to display a non-editable value with a label. + * [api:TextareaField]: Multi-line text field. + * [api:TextField]: Single-line text field. + * [api:PasswordField]: Masked input field. ## Actions - * `[api:FormAction]`: Button element for forms, both for `` and `