Merge pull request #3733 from wilr/docsrewrite

DOCS Restructure of the docs
This commit is contained in:
Will Rossiter 2014-12-16 23:05:37 +13:00
commit 453e12d1a7
434 changed files with 12232 additions and 12878 deletions

View File

@ -1,8 +1,8 @@
# Requirements
SilverStripe CMS needs to be installed on a web server. Content authors and website administrators use their web browser to access a web-based GUI to
do their day-to-day work. Website designers and developers require access to the files on the server to update templates,
website logic, and perform upgrades or maintainance.
SilverStripe CMS needs to be installed on a web server. Content authors and website administrators use their web browser
to access a web-based GUI to do their day-to-day work. Website designers and developers require access to the files on
the server to update templates, website logic, and perform upgrades or maintenance.
Our web-based [PHP installer](/installation) can check if you meet the requirements listed below.
@ -36,14 +36,26 @@ Our web-based [PHP installer](/installation) can check if you meet the requireme
## Web server hardware requirements
Hardware requirements vary widely depending on the traffic to your website, the complexity of its logic (i.e., PHP), and its size (i.e., database.) By default, all pages are dynamic, and thus access both the database and execute PHP code to generate. SilverStripe can cache full pages and segments of templates to dramatically increase performance.
Hardware requirements vary widely depending on the traffic to your website, the complexity of its logic (i.e., PHP), and
its size (i.e., database.) By default, all pages are dynamic, and thus access both the database and execute PHP code to
generate. SilverStripe can cache full pages and segments of templates to dramatically increase performance.
A typical website page on a conservative single CPU machine (e.g., Intel 2Ghz) takes roughly 300ms to generate. This comfortably allows over a million page views per month. Caching and other optimisations can improve this by a factor of ten or even one hundred times. SilverStripe CMS can be used in multiple-server architectures to improve scalability and redundancy.
A typical website page on a conservative single CPU machine (e.g., Intel 2Ghz) takes roughly 300ms to generate. This
comfortably allows over a million page views per month. Caching and other optimisations can improve this by a factor of
ten or even one hundred times. SilverStripe CMS can be used in multiple-server architectures to improve scalability and
redundancy.
For more information on how to scale SilverStripe see the [Performance](../../developer_guides/performance/) Gluide.
## Client side (CMS) requirements
SilverStripe CMS is designed to work well with Google Chrome, Mozilla Firefox and Internet Explorer 8+. We aim to provide satisfactory experiences in Apple Safari. SilverStripe CMS works well across Windows, Linux, and Mac operating systems.
SilverStripe CMS is designed to work well with Google Chrome, Mozilla Firefox and Internet Explorer 8+. We aim to
provide satisfactory experiences in Apple Safari. SilverStripe CMS works well across Windows, Linux, and Mac operating
systems.
## End user requirements ##
## End user requirements
SilverStripe CMS is designed to make excellent, standards-compliant websites that are compatible with a wide range of industry standard browsers and operating systems. A competent developer is able to produce websites that meet W3C guidelines for HTML, CSS, JavaScript, and accessibility, in addition to meeting specific guildelines, such as e-government requirements.
SilverStripe CMS is designed to make excellent, standards-compliant websites that are compatible with a wide range of
industry standard browsers and operating systems. A competent developer is able to produce websites that meet W3C
guidelines for HTML, CSS, JavaScript, and accessibility, in addition to meeting specific guildelines, such as
e-government requirements.

View File

@ -0,0 +1,24 @@
# Installation on Linux, Unix and *nix like Operating Systems
SilverStripe should be able to be installed on any Linux, Unix or *nix like OS as long as the correct server software is installed and configured (referred to a *nix in this document from herein). It is common that web hosting that you may use for your production SilverStripe application will be *nix based, here you may also want to use *nix locally to ensure how you develop locally mimics closely your production environment.
Is important to ensure you check the [Server Requirements](/Getting_Started/Installation/Server_Requirements) list before acquiring and installing SilverStripe on your *nix server (locally or otherwise).
At a high level you will need a:
* Web server e.g. Apache, Nginx
* Database e.g. MariaDB, Postgres, MySQL
* PHP
##*nix installation guides on the web
There are a number of good step by step guides covering server setups and installing of SilverStripe on the various flavours of *nix systems.
Note: Many of the following guides simply download SilverStripe as a zipped file. We recommend the use of [Composer](/Getting_Started/Composer/) once you get to the point of installing SilverStripe (though the choice is up to you). Always ensure you get the latest version if you are starting a new project.
###Known (but not exhaustive) list
* [How To Install Silverstripe on Your VPS](https://www.digitalocean.com/community/tutorials/how-to-install-silverstripe-on-your-vps)
* [Running SilverStripe On Nginx (LEMP) On Debian Wheezy/Ubuntu 13.04](http://www.howtoforge.com/running-silverstripe-on-nginx-lemp-on-debian-wheezy-ubuntu-13.04)
* [Setting up nginx, PHP-FPM, and SilverStripe on Fedora 19](http://halkyon.net/blog/setting-up-nginx-php-fpm-and-silverstripe-installation-on-fedora-19/)
* [How to install SilverStripe CMS on a Linux Virtual Server](http://www.rosehosting.com/blog/how-to-install-silverstripe-cms-on-a-linux-virtual-server/)
_If you find further good *nix related installation articles please email these to community+docs@silverstripe.org._

View File

@ -154,7 +154,7 @@ Two other important folders to set the permissions on are `assets` and `silverst
You will need to give **Modify** permission to **IUSR** user. To do it right click the folder and choose **Properties**. Then open the security tab, press **Edit** and add the **IUSR** user to the list by clicking the **Add** button. Afterwards tick **Modify** under **Allow** for that user. Repeat these steps for each folder.
![](_images/iis7-iusr-permissions.jpg)
![](/_images/iis7-iusr-permissions.jpg)
## Install SilverStripe

View File

@ -40,11 +40,11 @@ a beta version of the software)
## Screenshots
![](_images/webpi-2-a-silverstripe-choice.jpg)
![](_images/webpi-2-b-dependencies.jpg)
![](_images/webpi-2-c-downloading-and-installaing.jpg)
![](_images/webpi-2-d-installer-questions-step1.jpg)
![](_images/webpi-2-e-installer-questions-step2.jpg)
![](_images/webpi-2-f-success-message.jpg)
![](_images/webpi-2-g-silverstripe-homepage.jpg)
![](_images/webpi-2-h-cms-interface-working.jpg)
![](/_images/webpi-2-a-silverstripe-choice.jpg)
![](/_images/webpi-2-b-dependencies.jpg)
![](/_images/webpi-2-c-downloading-and-installaing.jpg)
![](/_images/webpi-2-d-installer-questions-step1.jpg)
![](/_images/webpi-2-e-installer-questions-step2.jpg)
![](/_images/webpi-2-f-success-message.jpg)
![](/_images/webpi-2-g-silverstripe-homepage.jpg)
![](/_images/webpi-2-h-cms-interface-working.jpg)

View File

@ -1,4 +1,7 @@
# nginx and HHVM
title: Nginx and HHVM
summary: Setting up Nginx and HHVM on Debian/Ubuntu using packages.
# Nginx and HHVM
[HHVM](http://hhvm.com/) is a faster alternative to PHP, in that it runs in a virtual machine
and uses just-in-time (JIT) compilation to achieve better performance over standard PHP.

View File

@ -1,43 +1,27 @@
# Generic Webserver Installation
# Installation
These instructions show you how to install SilverStripe on any web server.
For additional information about installing SilverStripe on specific operation systems, refer to:
* [Installation on a Windows Server](windows-pi)
* [Installation on OSX](mac-osx)
These instructions show you how to install SilverStripe on any web server.
The best way to install from the source code is to use [Composer](composer).
Check out our operating system specific guides for [Linux](linux_unix),
[Windows Server](windows-pi) and [Mac OSX](mac-osx).
## Installation Steps
* [Download](http://silverstripe.org/download) the installer package
* Make sure the webserver has MySQL and PHP support. See [Server Requirements](server-requirements) for more
information.
* Make sure the webserver has MySQL and PHP support. See [Server Requirements](server-requirements) for more information.
* Unpack the installer somewhere into your web-root. Usually the www folder or similar. Most downloads from SilverStripe
are compressed tarballs. To extract these files you can either do them natively (Unix) or with 7-Zip (Windows)
* Visit your sites Domain or IP Address in your web browser.
* Visit your sites domain or IP address in your web browser.
* You will be presented with a form where you enter your MySQL login details and are asked to give your site a 'project
name' and the default login details. Follow the questions and select the *install* button at the bottom of the page.
* After a couple of minutes, your site will be set up. Visit your site and enjoy!
## Issues?
If the above steps don't work for any reason have a read of the [Common Problems](common-problems) section.
## Security notes
### Yaml
For the reasons explained in [security](/topics/security), Yaml files are blocked by default by the .htaccess file
provided by the SilverStripe installer module.
To allow serving yaml files from a specific directory, add code like this to an .htaccess file in that directory
<Files *.yml>
Order allow,deny
Allow from all
</Files>
<div class="notice" markdown="1">
SilverStripe ships with default rewriting rules specific to your web server. Apart from
routing requests to the framework, they also prevent access to sensitive files in the webroot,
for example YAML configuration files. Please refer to the [security](/topics/security) documentation for details.
</div>

View File

@ -1,9 +1,5 @@
# Installing and Upgrading with Composer
<div markdown='1' style="float: right; margin-left: 20px">
![](../_images/composer.png)
</div>
Composer is a package management tool for PHP that lets you install and upgrade SilverStripe and its modules. Although installing Composer is one extra step, it will give you much more flexibility than just downloading the file from silverstripe.org. This is our recommended way of downloading SilverStripe and managing your code.
For more information about Composer, visit [its website](http://getcomposer.org/).

View File

@ -50,7 +50,7 @@ Example Forum:
| `forum/code` | PHP code for model and controller (subdirectories are optional) |
| ... | ... |
![](_images/modules_folder.jpg)
![](/_images/modules_folder.jpg)
### Module documentation
@ -72,16 +72,19 @@ Example Forum Documentation:
| `forum/docs/_manifest_exclude` | Empty file to signify that SilverStripe does not need to load classes from this folder |
| `forum/docs/en/` | English documentation |
| `forum/docs/en/index.md` | Documentation homepage. Should provide an introduction and links to remaining docs |
| `forum/docs/en/installing.md` | |
| `forum/docs/en/_images/` | Folder to store any images or media |
| `forum/docs/en/sometopic/` | You can organize documentation into nested folders |
| `forum/docs/en/Getting_Started.md` | Documentation page. Naming convention is Uppercase and underscores. |
| `forum/docs/en//_images/` | Folder to store any images or media |
| `forum/docs/en/Some_Topic/` | You can organise documentation into nested folders. Naming convention is Uppercase and underscores. |
|`forum/docs/en/04_Some_Topic/00_Getting_Started.md`|Structure is created by use of numbered prefixes. This applies to nested folders and documentations pages, index.md should not have a prefix.|
## PHP Include Paths
## Autoloading
Due to the way `[api:ManifestBuilder]` recursively detects php-files and includes them through PHP5's
*__autoload()*-feature, you don't need to worry about include paths. Feel free to structure your php-code into
subdirectories inside the *code*-directory.
SilverStripe recursively detects classes in PHP files by building up a manifest used for autoloading,
as well as respecting Composer's built-in autoloading for libraries. This means
in most cases, you don't need to worry about include paths or `require()` calls
in your own code - after adding a new class, simply regenerate the manifest
by using a `flush=1` query parameter. See the ["Manifests" documentation](/developer_guides/execution_pipeline/manifests) for details.
## Best Practices

View File

@ -1,15 +1,8 @@
# Installing SilverStripe
title: Getting Started
introduction: SilverStripe is a web application. This means that you will need to have a webserver and database. We will take you through the setup of the server environment as well the application itself.
## Download
SilverStripe is a web application. This means that you will need to have a webserver and database meeting its
[requirements](server-requirements). We will take you through the setup of the server environment as well the application itself.
<div markdown='1' style="float: right; margin-left: 20px">
![](../_images/composer.png)
</div>
## Getting the code
## Installing SilverStripe
The best way to get SilverStripe is to [install with Composer](composer). Composer is a package management tool for PHP that
lets you install and upgrade SilverStripe and its modules. Although installing Composer is one extra step, it will give you much more flexibility than just downloading the file from silverstripe.org.

View File

@ -1,3 +1,6 @@
title: Building a basic site
summary: An overview of the SilverStripe installation and an introduction to creating a web page.
# Tutorial 1 - Building a Basic Site
## Overview
@ -15,9 +18,9 @@ templates - one for the home page, and one for the rest of the site.
## Installation
You need to [download the SilverStripe software](http://www.silverstripe.org/stable-download) and install it to your local machine or to a webserver.
You need to [download the SilverStripe software](http://www.silverstripe.org/software/download) and install it to your local machine or to a webserver.
For more information about installing and configuring a webserver read the [Installation instructions and videos](../installation).
For more information about installing and configuring a webserver read the [Installation instructions and videos](/getting_started/installation).
This tutorial uses the SilverStripe CMS default theme 'Simple' which you will find in the themes folder. We will investigate the existing template files that make up the theme as well as create some new files to build upon the theme.
@ -43,13 +46,13 @@ When designing your site you should only need to modify the *mysite*, *themes* a
### User Interface Basics
![](_images/tutorial1_cms-basic.jpg)
![](/_images/tutorial1_cms-basic.jpg)
The CMS is the area in which you can manage your site content. You can access the cms at http://localhost/your_site_name/admin (or http://yourdomain.com/admin if you are using your own domain name). You
will be presented with a login screen. Login using the details you provided at installation. After logging in you
should see the CMS interface with a list of the pages currently on your website (the site tree). Here you can add, delete and reorganize pages. If you need to delete, publish, or unpublish a page, first check "multi-selection" at the top. You will then be able to perform actions on any checked files using the "Actions" dropdown. Clicking on a page will open it in the page editing interface pictured below (we've entered some test content).
![](_images/tutorial1_cms-numbered.jpg)
![](/_images/tutorial1_cms-numbered.jpg)
1. This menu allows you to move between different sections of the CMS. There are four core sections - "Pages", "Files", "Users" and "Settings". If you have modules installed, they may have their own sections here. In this tutorial we will be focusing on the "Pages" section.
2. The breadcrumbs on the left will show you a direct path to the page you are currently looking at. You can use this path to navigate up through a page's hierarchy. On the left there are tabs you may use to flick between different aspects of a page. By default, you should be shown three tabs: "Content", "Settings", and "History".
@ -57,11 +60,11 @@ should see the CMS interface with a list of the pages currently on your website
* Settings - Here you set the type of page behavior, parent page, show in search, show in menu, and who can view or edit the page.
* History - This allows you to view previous version of your page, compare, change, and revert to previous version if need be.
3. Within the "Pages" section (provided you are in the "Content" or "Settings" tab) you can quickly move between pages in the CMS using the site tree. To collapse and expand this sidebar, click the arrow at the bottom. If you are in the history tab, you will notice the site tree has been replaced by a list of the alterations to the current page.
![](_images/tutorial1_cms-numbered-3.jpg)
![](/_images/tutorial1_cms-numbered-3.jpg)
4. This section allows you to edit the content for the currently selected page, as well as changing other properties of the page such as the page name and URL. The content editor has full [WYSIWYG](http://en.wikipedia.org/wiki/WYSIWYG) capabilities, allowing you to change formatting and insert links, images, and tables.
5. These buttons allow you to save your changes to the draft copy, publish your draft copy, unpublish from the live website, or remove a page from the draft website. The SilverStripe CMS workflow stores two copies of a page, a draft and a published copy. By having separate draft and published copies, we can preview draft changes on the site before publishing them to the live website. You can quickly preview your draft pages without leaving the CMS by clicking the "Preview" button.
![](_images/tutorial1_cms-numbered-5.jpg)
![](/_images/tutorial1_cms-numbered-5.jpg)
### Try it
@ -74,7 +77,7 @@ To create a new page, click the "Add New" button above the site tree.
When you create a new page, you are given the option of setting the structure of the page ("Top level" or "Under another page") and the page type.
The page type specifies the templates used to render the page, the fields that are able to be edited in the CMS, and page specific behavior. We will explain page types in more depth as we progress; for now, make all pages of the type "Page".
![](_images/tutorial1_addpage.jpg)
![](/_images/tutorial1_addpage.jpg)
**SilverStripe's friendly URLs**
@ -84,7 +87,7 @@ page in the database.
Note that if you have sub-pages, changing the Top level URL field for a page will affect the URL for all sub-pages. For example, if we changed the URL field "/about-us/" to "/about-silverstripe/" then the sub-pages URLs would now be "/about-silverstripe/URL-of-subpage/" rather than "/about-us/URL-of-subpage/".
![](_images/tutorial1_url.jpg)
![](/_images/tutorial1_url.jpg)
When you create a new page, SilverStripe automatically creates an appropriate URL for it. For example, *About Us* will
become *about-us*. You are able to change it yourself so that you can make long titles more usable or descriptive. For
@ -179,7 +182,7 @@ Then, using a loop over the page control *Menu(1)*, we add a link to the list fo
This creates the navigation at the top of the page:
![](_images/tutorial1_menu.jpg)
![](/_images/tutorial1_menu.jpg)
@ -216,7 +219,7 @@ You can also create the pages elsewhere on the site tree, and drag and drop the
Either way, your site tree should now look something like this:
![](_images/tutorial1_2nd_level-cut.jpg)
![](/_images/tutorial1_2nd_level-cut.jpg)
Great, we now have a hierarchical site structure! Let's look at how this is created and displayed in our template.
@ -227,7 +230,7 @@ Adding a second level menu is very similar to adding the first level menu. Open
<% loop $Menu(2) %>
<li class="$LinkingMode">
<a href="$Link" title="Go to the $Title.XML page">
<span class="arrow">&rarr;</span>
<span class="arrow"></span>
<span class="text">$MenuTitle.XML</span>
</a>
</li>
@ -250,7 +253,7 @@ like this:
<% loop $Menu(2) %>
<li class="$LinkingMode">
<a href="$Link" title="Go to the $Title.XML page">
<span class="arrow">&rarr;</span>
<span class="arrow"></span>
<span class="text">$MenuTitle.XML</span>
</a>
</li>
@ -286,7 +289,7 @@ to get the top level page title. In this case, we merely use it to check the exi
Both the top menu, and the sidebar menu should be updating and highlighting as you move from page to page. They will also mirror changes done in the SilverStripe CMS, such as renaming pages or moving them around.
![](_images/tutorial1_menu-two-level.jpg)
![](/_images/tutorial1_menu-two-level.jpg)
Feel free to experiment with the if and loop statements. For example, you could create a drop down style menu from the top navigation using a combination of if statements, loops, and some CSS to style it.
@ -302,7 +305,7 @@ The following example runs an if statement and a loop on *Children*, checking to
<% loop $Children %>
<li class="$LinkingMode">
<a href="$Link" title="Go to the $Title.XML page">
<span class="arrow">&rarr;</span>
<span class="arrow"></span>
<span class="text">$MenuTitle.XML</span>
</a>
</li>
@ -329,7 +332,7 @@ Earlier we stated that every page in a SilverStripe site has a **page type**, an
template, or template layout, corresponding to the page type. Therefore, the first step when switching the homepage template is to create a new page type.
Each page type is represented by two PHP classes: a *data object* and a *controller*. Don't worry about the details of page
types right now, we will go into much more detail in the [next tutorial](2-extending-a-basic-site).
types right now, we will go into much more detail in the [next tutorial](/tutorials/extending_a_basic_site).
Create a new file *HomePage.php* in *mysite/code*. Copy the following code into it:
@ -355,7 +358,7 @@ After building the database, we can change the page type of the homepage in the
In the CMS, navigate to the "Home" page and switch to the "Settings" tab. Change "Page type" to *Home Page*, and click "Save & Publish".
![](_images/tutorial1_homepage-type.jpg)
![](/_images/tutorial1_homepage-type.jpg)
Our homepage is now of the page type *HomePage*. Regardless, it is still
rendered with the *Page* template. SilverStripe does this as our homepage inherits its type from *Page*,
@ -387,27 +390,20 @@ We'll also replace the title text with an image. Find this line:
Your Home page should now look like this:
![](_images/tutorial1_home-template.jpg)
![](/_images/tutorial1_home-template.jpg)
SilverStripe first searches for a template in the *themes/simple/templates* folder. Since there is no *HomePage.ss*,
it will use the *Page.ss* for both *Page* and *HomePage* page types. When it comes across the *$Layout* tag, it will
then descend into the *themes/simple/templates/Layout* folder, and will use *Page.ss* for the *Page* page type, and
*HomePage.ss* for the *HomePage* page type. So while you could create a HomePage.ss in the *themes/simple/templates/* it is better to reuse the navigation and footer common to both our Home page and the rest of the pages on our website.
![](_images/tutorial1_subtemplates-diagram.jpg)
![](/_images/tutorial1_subtemplates-diagram.jpg)
## Summary
So far we have taken a look at the different areas and functionality within the pages area of the CMS. We have learnt about template variables, controls and if statements and used these to build a basic, but fully functional, website. We have also briefly covered page types, and looked at how they correspond to templates and sub-templates. Using this knowledge, we have customized our website's homepage design.
In the next tutorial, [Extending a Basic Site](2-extending-a-basic-site), we will explore page types on a deeper level, and look at customising our own page types to extend the functionality of SilverStripe.
In the next tutorial, [Extending a Basic Site](/tutorials/extending_a_basic_site), we will explore page types on a deeper level, and look at customising our own page types to extend the functionality of SilverStripe.
[Next tutorial >>](2-extending-a-basic-site)
## Books on SilverStripe
* [Official book on SilverStripe in English](http://www.silverstripe.org/silverstripe-book).
* [Official book on SilverStripe in German](http://www.silverstripe.org/das-silverstripe-buch).
![](_images/silverstripe-cms-book-front-cover-design-june2009preview.jpg)
[Next tutorial >>](/tutorials/extending_a_basic_site)

View File

@ -1,9 +1,12 @@
title: Extending a basic site
summary: Building on tutorial 1, a look at storing data in SilverStripe and creating a latest news feed.
# Tutorial 2 - Extending a basic site
## Overview
In the [first tutorial (Building a basic site)](1-building-a-basic-site) we learnt how to create a basic site using SilverStripe. This tutorial will build on that, and explore extending SilverStripe by creating our own page types. After doing this we should have a better understanding of how SilverStripe works.
In the [first tutorial (Building a basic site)](/tutorials/building_a_basic_site) we learnt how to create a basic site using SilverStripe. This tutorial will build on that, and explore extending SilverStripe by creating our own page types. After doing this we should have a better understanding of how SilverStripe works.
## What are we working towards?
@ -11,12 +14,12 @@ We are going to work on adding two new sections to the site we built in the firs
The first of these new sections will be *News*, with a recent news listing on the homepage and an RSS feed:
![](_images/tutorial2_newslist.jpg)
![](/_images/tutorial2_newslist.jpg)
The second will be a *Staff* section, to demonstrate more complex database structures (such as associating an image with each staff member):
![](_images/tutorial2_einstein.jpg)
![](/_images/tutorial2_einstein.jpg)
@ -50,7 +53,7 @@ Creating a new page type requires creating each of these three elements. We will
A more in-depth introduction of Model-View-Controller can be found
[here](http://www.slash7.com/articles/2005/02/22/mvc-the-most-vexing-conundrum).
![](_images/tutorial2_pagetype-inheritance.jpg)
![](/_images/tutorial2_pagetype-inheritance.jpg)
## Creating the news section page types
@ -116,7 +119,7 @@ it. Add a *$db* property definition in the *ArticlePage* class:
}
Every entry in the array is a *key => value* pair. The **key** is the name of the field, and the **value** is the type. See ["data types"](/topics/data-types) for a complete list of types.
Every entry in the array is a *key => value* pair. The **key** is the name of the field, and the **value** is the type. See ["data types and casting"](/developer_guides/model/data_types_and_casting) for a complete list of types.
<div class="hint" markdown="1">
The names chosen for the fields you add must not already be used. Be careful using field names such as Title,
@ -171,7 +174,7 @@ would create a new tab called "New Tab", and a single "Author" textfield inside.
</div>
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"](/reference/form-field-types).
There are many more fields available in the default installation, listed in ["form field types"](/developer_guides/forms/fields/common_subclasses).
:::php
return $fields;
@ -181,7 +184,7 @@ Finally, we return the fields to the CMS. If we flush the cache (by adding ?flus
Now that we have created our page types, let's add some content. Go into the CMS and create an *ArticleHolder* page named "News", then create a few *ArticlePage*'s within it.
![](_images/tutorial2_news-cms.jpg)
![](/_images/tutorial2_news-cms.jpg)
## Modifing the date field
@ -266,13 +269,13 @@ To access the new fields, we use *$Date* and *$Author*. In fact, all template va
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)
![](/_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
that the *Nice* function returns the date in *dd/mm/yyyy* format, rather than the *yyyy-mm-dd* format stored in the
database.
![](_images/tutorial2_news.jpg)
![](/_images/tutorial2_news.jpg)
###ArticleHolder Template
We'll now create a template for the article holder. We want our news section to show a list of news items, each with a summary and a link to the main article (our Article Page).
@ -300,12 +303,12 @@ 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.
![](_images/tutorial2_articleholder.jpg)
![](/_images/tutorial2_articleholder.jpg)
### Using include files in templates
We can make our templates more modular and easier to maintain by separating commonly-used components in to *include files*. We are already familiar with the `<% include Sidebar %>` line from looking at the menu in the [first tutorial (Building a basic site)](1-building-a-basic-site).
We can make our templates more modular and easier to maintain by separating commonly-used components in to *include files*. We are already familiar with the `<% include Sidebar %>` line from looking at the menu in the [first tutorial (Building a basic site)](/tutorials/building_a_basic_site).
We'll separate the display of linked articles as we want to reuse this code later on.
@ -348,7 +351,7 @@ And this one to the *HomePage* class:
This will change the icons for the pages in the CMS.
![](_images/tutorial2_icons2.jpg)
![](/_images/tutorial2_icons2.jpg)
<div class="hint" markdown="1">
Note: The `news-file` icon may not exist in a default SilverStripe installation. Try adding your own image or choosing a different one from the `treeicons` collection.
@ -368,7 +371,7 @@ It would be nice to greet page visitors with a summary of the latest news when t
}
This function simply runs a database query that gets the latest news articles from the database. By default, this is five, but you can change it by passing a number to the function. See the [Data Model](../topics/datamodel) documentation for details. We can reference this function as a page control in our *HomePage* template:
This function simply runs a database query that gets the latest news articles from the database. By default, this is five, but you can change it by passing a number to the function. See the [Data Model and ORM](/developer_guides/model/data_model_and_orm) documentation for details. We can reference this function as a page control in our *HomePage* template:
**themes/simple/templates/Layout/Homepage.ss**
@ -385,7 +388,7 @@ When SilverStripe comes across a variable or page control it doesn't recognize,
The controller for a page is only created when page is actually visited, while the data object is available when the page is referenced in other pages, e.g. by page controls. A good rule of thumb is to put all functions specific to the page currently being viewed in the controller; only if a function needs to be used in another page should you put it in the data object.
![](_images/tutorial2_homepage-news.jpg)
![](/_images/tutorial2_homepage-news.jpg)
@ -410,7 +413,7 @@ This function creates an RSS feed of all the news articles, and outputs it to th
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)
![](/_images/tutorial2_rss-feed.jpg)
Now all we need is to let the user know that our RSS feed exists. Add this function to *ArticleHolder_Controller*:
@ -477,12 +480,12 @@ We then add an `[api:UploadField]` in the *getCMSFields* function to the tab "Ro
the *addFieldToTab* function will create it for us. The *UploadField* allows us to select an image or upload a new one in
the CMS.
![](_images/tutorial2_photo.jpg)
![](/_images/tutorial2_photo.jpg)
Rebuild the database ([http://localhost/your_site_name/dev/build](http://localhost/your_site_name/dev/build)) and open the CMS. Create
a new *StaffHolder* called "Staff", and create some *StaffPage*s in it.
![](_images/tutorial2_create-staff.jpg)
![](/_images/tutorial2_create-staff.jpg)
### Creating the staff section templates
@ -515,7 +518,7 @@ This template is very similar to the *ArticleHolder* template. The *SetWidth* me
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.
![](_images/tutorial2_staff-section.jpg)
![](/_images/tutorial2_staff-section.jpg)
The *StaffPage* template is also very straight forward.
@ -536,10 +539,10 @@ The *StaffPage* template is also very straight forward.
Here we use the *SetWidth* method to get a different sized image from the same source image. You should now have
a complete staff section.
![](_images/tutorial2_einstein.jpg)
![](/_images/tutorial2_einstein.jpg)
## Summary
In this tutorial we have explored the concept of page types. In the process of creating and extending page types we have covered many of the concepts required to build a site with SilverStripe.
[Next Tutorial >>](3-forms)
[Next Tutorial >>](/tutorials/forms)

View File

@ -1,16 +1,19 @@
title: Forms
summary: Capture and store user information through web forms.
# Tutorial 3 - Forms
## Overview
This tutorial is intended to be a continuation of the first two tutorials ([first tutorial](1-building-a-basic-site), [second tutorial](2-extending-a-basic-site)). In this tutorial we will build on the site we developed in the earlier tutorials and explore forms in SilverStripe. We will look at custom coded forms: forms which need to be written in PHP.
This tutorial is intended to be a continuation of the first two tutorials ([first tutorial](/tutorials/building_a_basic_site), [second tutorial](/tutorials/extending_a_basic_site)). In this tutorial we will build on the site we developed in the earlier tutorials and explore forms in SilverStripe. We will look at custom coded forms: forms which need to be written in PHP.
Instead of using a custom coded form, we could use the [userforms module](http://silverstripe.org/user-forms-module). This module allows users to construct forms via the CMS. A form created this way is much quicker to implement, but also lacks the flexibility of a coded form.
Instead of using a custom coded form, we could use the [userforms module](http://addons.silverstripe.org/add-ons/silverstripe/userforms). This module allows users to construct forms via the CMS. A form created this way is much quicker to implement, but also lacks the flexibility of a coded form.
## What are we working towards?
We will create a poll on the home page that asks the user their favourite web browser, and displays a bar graph of the results.
![tutorial:tutorial3_pollresults.png](_images/tutorial3_pollresults.jpg)
![tutorial:tutorial3_pollresults.png](/_images/tutorial3_pollresults.jpg)
## Creating the form
@ -19,7 +22,7 @@ The poll we will be creating on our homepage will ask the user for their name an
**mysite/code/HomePage.php**
:::php
```php
class HomePage_Controller extends Page_Controller {
private static $allowed_actions = array('BrowserPollForm');
@ -51,11 +54,11 @@ The poll we will be creating on our homepage will ask the user for their name an
}
...
```
Let's step through this code.
:::php
```php
// Create fields
$fields = new FieldList(
new TextField('Name'),
@ -68,7 +71,7 @@ Let's step through this code.
'Lynx' => 'Lynx'
))
);
```
First we create our form fields.
We do this by creating a `[api:FieldList]` and passing our fields as arguments.
@ -78,11 +81,11 @@ argument is passed, as in this case, it is assumed the label is the same as the
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
```php
$actions = new FieldList(
new FormAction('doBrowserPoll', 'Submit');
);
```
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.
@ -90,9 +93,9 @@ Here we create a 'Submit' button which calls the 'doBrowserPoll' method, which w
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
```php
return new Form($this, 'BrowserPollForm', $fields, $actions);
```
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
@ -104,7 +107,7 @@ Add the following code to the top of your home page template, just before `<div
**themes/simple/templates/Layout/HomePage.ss**
:::ss
```ss
...
<div id="BrowserPoll">
<h2>Browser Poll</h2>
@ -112,6 +115,7 @@ Add the following code to the top of your home page template, just before `<div
</div>
<div class="Content">
...
```
In order to make the graphs render correctly,
we need to add some CSS styling.
@ -119,48 +123,55 @@ Add the following code to the existing `form.css` file:
**themes/simple/css/form.css**
:::css
```css
/* BROWSER POLL */
#BrowserPoll {
float: right;
margin: 20px 10px 0 0;
width: 20%;
}
form FieldList {
border:0;
}
#BrowserPoll .message {
float:left;
display: block;
color:red;
background:#efefef;
border:1px solid #ccc;
padding:5px;
margin:5px;
}
#BrowserPoll h2 {
font-size: 1.5em;
line-height:2em;
color: #0083C8;
}
#BrowserPoll .field {
padding:3px 0;
}
#BrowserPoll input.text {
padding: 0;
font-size:1em;
}
#BrowserPoll .Actions {
padding:5px 0;
}
#BrowserPoll .bar {
background-color: #015581;
}
form FieldList {
border:0;
}
#BrowserPoll .message {
float:left;
display: block;
color:red;
background:#efefef;
border:1px solid #ccc;
padding:5px;
margin:5px;
}
#BrowserPoll h2 {
font-size: 1.5em;
line-height:2em;
color: #0083C8;
}
#BrowserPoll .field {
padding:3px 0;
}
#BrowserPoll input.text {
padding: 0;
font-size:1em;
}
#BrowserPoll .Actions {
padding:5px 0;
}
#BrowserPoll .bar {
background-color: #015581;
}
```
All going according to plan, if you visit [http://localhost/your_site_name/home/?flush=1](http://localhost/your_site_name/home/?flush=1) it should look something like this:
![](_images/tutorial3_pollform.jpg)
![](/_images/tutorial3_pollform.jpg)
## Processing the form
@ -168,11 +179,11 @@ 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](2-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:
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**
:::php
```php
<?php
class BrowserPollSubmission extends DataObject {
private static $db = array(
@ -180,12 +191,12 @@ If you recall, in the [second tutorial](2-extending-a-basic-site) we said that a
'Browser' => 'Text'
);
}
```
If we then rebuild the database ([http://localhost/your_site_name/dev/build](http://localhost/your_site_name/dev/build)), we will see that the *BrowserPollSubmission* table is created. Now we just need to define 'doBrowserPoll' on *HomePage_Controller*:
**mysite/code/HomePage.php**
:::php
```php
class HomePage_Controller extends Page_Controller {
// ...
public function doBrowserPoll($data, $form) {
@ -195,7 +206,7 @@ If we then rebuild the database ([http://localhost/your_site_name/dev/build](htt
return $this->redirectBack();
}
}
```
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.
@ -211,17 +222,17 @@ Change the end of the 'BrowserPollForm' function so it looks like this:
**mysite/code/HomePage.php**
:::php
```php
public function BrowserPollForm() {
// ...
$validator = new RequiredFields('Name', 'Browser');
return new Form($this, 'BrowserPollForm', $fields, $actions, $validator);
}
```
If we then open the homepage and attempt to submit the form without filling in the required fields errors should appear.
![](_images/tutorial3_validation.jpg)
![](/_images/tutorial3_validation.jpg)
## Showing the poll results
@ -233,7 +244,7 @@ We can do this using a session variable. The `[api:Session]` class handles all s
**mysite/code/HomePage.php**
:::php
```php
// ...
class HomePage_Controller extends Page_Controller {
// ...
@ -245,12 +256,12 @@ We can do this using a session variable. The `[api:Session]` class handles all s
return $this->redirectBack();
}
}
```
Then we simply need to check if the session variable has been set in 'BrowserPollForm()', and to not return the form if
it is.
:::php
```php
// ...
class HomePage_Controller extends Page_Controller {
// ...
@ -259,7 +270,7 @@ it is.
// ...
}
}
```
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 can start a new session by closing and reopening your browser,
or clearing your browsing session through your browsers preferences.
@ -268,13 +279,13 @@ 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/2-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.
**mysite/code/HomePage.php**
:::php
```php
public function BrowserPollResults() {
$submissions = new GroupedList(BrowserPollSubmission::get());
$total = $submissions->Count();
@ -289,20 +300,20 @@ Create the function 'BrowserPollResults' on the *HomePage_Controller* class.
}
return $list;
}
```
This code introduces a few new concepts, so let's step through it.
:::php
```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).
:::php
```php
$total = $submissions->Count();
```
We get the total number of submissions, which is needed to calculate the percentages.
:::php
```php
$list = new ArrayList();
foreach($submissions->groupBy('Browser') as $browserName => $browserSubmissions) {
$percentage = (int) ($browserSubmissions->Count() / $total * 100);
@ -311,7 +322,7 @@ We get the total number of submissions, which is needed to calculate the percent
'Percentage' => $percentage
)));
}
```
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.
@ -322,7 +333,7 @@ The final step is to create the template to display our data. Change the 'Browse
**themes/simple/templates/Layout/HomePage.ss**
:::ss
```ss
<div id="BrowserPoll">
<h2>Browser Poll</h2>
<% if $BrowserPollForm %>
@ -338,17 +349,17 @@ The final step is to create the template to display our data. Change the 'Browse
</ul>
<% end_if %>
</div>
```
Here we first check if the *BrowserPollForm* is returned, and if it is display it. Otherwise the user has already voted,
and the poll results need to be displayed.
We use the normal tactic of putting the data into an unordered list and using CSS to style it, except here we use inline styles to display a bar that is sized proportionate to the number of votes the browser has received. You should now have a complete poll.
![](_images/tutorial3_pollresults.jpg)
![](/_images/tutorial3_pollresults.jpg)
## Summary
In this tutorial we have explored custom php forms, and displayed result sets through Grouped Lists. We have briefly covered the different approaches to creating and using forms. Whether you decide to use the [userforms module](http://silverstripe.org/user-forms-module) or create a form in PHP depends on the situation and flexibility required.
In this tutorial we have explored custom php forms, and displayed result sets through Grouped Lists. We have briefly covered the different approaches to creating and using forms. Whether you decide to use the [userforms module](http://addons.silverstripe.org/add-ons/silverstripe/userforms) or create a form in PHP depends on the situation and flexibility required.
[Next Tutorial >>](4-site-search)
[Next Tutorial >>](/tutorials/site_search)

View File

@ -1,14 +1,17 @@
title: Site Search
summary: Enable website search. How to handle paged result sets and the SearchForm class.
# Tutorial 4 - Site Search
## Overview
This is a short tutorial demonstrating how to add search functionality to a SilverStripe site. It is recommended that you have completed the earlier tutorials ([Building a basic site](1-building-a-basic-site), [Extending a basic site](2-extending-a-basic-site), [Forms](3-forms)), especially the tutorial on forms, before attempting this tutorial. While this tutorial will add search functionality to the site built in the previous tutorials, it should be straight forward to follow this tutorial on any site of your own.
This is a short tutorial demonstrating how to add search functionality to a SilverStripe site. It is recommended that you have completed the earlier tutorials ([Building a basic site](/tutorials/building_a_basic_site), [Extending a basic site](/tutorials/extending_a_basic_site), [Forms](/tutorials/forms)), especially the tutorial on forms, before attempting this tutorial. While this tutorial will add search functionality to the site built in the previous tutorials, it should be straight forward to follow this tutorial on any site of your own.
## What are we working towards?
We are going to add a search box on the top of the page. When a user types something in the box, they are taken to a results page.
![](_images/tutorial4_search.jpg)
![](/_images/tutorial4_search.jpg)
## Creating the search form
@ -43,7 +46,7 @@ To add the search form, we can add `$SearchForm` anywhere in our templates. In t
This displays as:
![](_images/tutorial4_searchbox.jpg)
![](/_images/tutorial4_searchbox.jpg)
## Showing the results
@ -149,10 +152,10 @@ class.
Then finally add ?flush=1 to the URL and you should see the new template.
![](_images/tutorial4_search.jpg)
![](/_images/tutorial4_search.jpg)
## Summary
This tutorial has demonstrated how easy it is to have full text searching on your site. To add search to a SilverStripe site add a search form, a results page, and you're done!
[Next Tutorial >>](5-dataobject-relationship-management)
[Next Tutorial >>](/tutorials/dataobject_relationship_management)

View File

@ -1,8 +1,11 @@
title: DataObject Relationship Management
summary: Learn how to create custom DataObjects and how to build interfaces for managing that data.
# Tutorial 5 - Dataobject Relationship Management
## Overview
This tutorial explores the relationship and management of [DataObjects](/topics/datamodel#relations). It builds on the [second tutorial](2-extending-a-basic-site) where we learnt how to define
This tutorial explores the relationship and management of [DataObjects](/developer_guides/model/dataobject). It builds on the [second tutorial](/tutorials/extending_a_basic_site) where we learnt how to define
additional fields on our models, and attach images to them.
## What are we working towards?
@ -58,8 +61,8 @@ Let's create the `Student` and `Project` objects.
The relationships are defined through the `$has_one`
and `$has_many` properties on the objects.
The array keys declares the name of the relationship,
the array values contain the class name (see the ["database structure"](/reference/database-structure)
and ["datamodel"](/topics/datamodel) topics for more information).
the array values contain the class name (see the ["database structure"](/developer_guides/model/database_structure)
and ["datamodel"](/developer_guides/model/data_model_and_orm) topics for more information).
As you can see, only the `Project` model extends `Page`,
while `Student` is a plain `DataObject` subclass.
@ -103,7 +106,7 @@ as part of the normal page hierarchy in the CMS.
Now that we have created our `ProjectsHolder` and `Project` page types, we'll add some content.
Go into the CMS and create a `ProjectsHolder` page named **Projects**.
Then create one `Project` page for each project listed [above](#what-are-we-working-towards).
Then create one `Project` page for each project listed [above](/tutorials/dataobject_relationship_management#what-are-we-working-towards).
### Data Management Interface: GridField
@ -167,17 +170,17 @@ We call `setDisplayFields()` directly on the component responsible for their ren
using the `[ModelAdmin](reference/modeladmin)` interface instead.
</div>
![tutorial:tutorial5_project_creation.jpg](_images/tutorial5_project_creation.jpg)
![tutorial:tutorial5_project_creation.jpg](/_images/tutorial5_project_creation.jpg)
Select each `Project` page you have created before,
go in the tab panel called "Students", and add all students as required,
by clicking on the link **Add Student** of your *GridField* table.
![tutorial:tutorial5_addNew.jpg](_images/tutorial5_addNew.jpg)
![tutorial:tutorial5_addNew.jpg](/_images/tutorial5_addNew.jpg)
Once you have added all the students, and selected their projects, it should look a little like this:
![tutorial:tutorial5_students.jpg](_images/tutorial5_students.jpg)
![tutorial:tutorial5_students.jpg](/_images/tutorial5_students.jpg)
### Many-many relationships: Mentor
@ -247,18 +250,18 @@ In the CMS, open one of your `Project` pages and select the "Mentors" tab.
Add all the mentors listed [above](#what-are-we-working-towards)
by clicking on the **Add Mentor** button.
![tutorial:tutorial5_module_creation.jpg](_images/tutorial5_module_creation.jpg)
![tutorial:tutorial5_module_creation.jpg](/_images/tutorial5_module_creation.jpg)
To associate the mentor with a project, select one of the mentors, and click on the projects tab. Add all the projects a mentor is associated with (see the [list](#What_are_we_working_towards?)), by typing the name in "Find Projects by Page name" and clicking the "Link Existing" button.
To associate the mentor with a project, select one of the mentors, and click on the projects tab. Add all the projects a mentor is associated with (see the [list](/tutorials/dataobject_relationship_management#What_are_we_working_towards?)), by typing the name in "Find Projects by Page name" and clicking the "Link Existing" button.
You will notice that you are able to select the same `Project` for multiple mentors.
This is the definition of a **many-to-many** relation.
![tutorial:tutorial5_module_selection.jpg](_images/tutorial5_module_selection.jpg)
![tutorial:tutorial5_module_selection.jpg](/_images/tutorial5_module_selection.jpg)
## Website Display
Now that we have created all the *Page* and *DataObject* classes necessary and the relational tables to manage the [relations](/topics/datamodel#relations) between them, we would like to see these relations on the website. We will see in this section how to display all these relations,
Now that we have created all the *Page* and *DataObject* classes necessary and the relational tables to manage the [relations](/developer_guides/model/data_model_and_orm#relations) between them, we would like to see these relations on the website. We will see in this section how to display all these relations,
but also how to create a template for a *DataObject*.
For every kind of *Page* or *DataObject*, you can access to their relations thanks to the **control** loop.
@ -273,7 +276,7 @@ accessing a "has-many" and "many-many" relationship
in the template loops: to the template, it's just
a named list of object.
![tutorial:tutorial5_projects_table.jpg](_images/tutorial5_projects_table.jpg)
![tutorial:tutorial5_projects_table.jpg](/_images/tutorial5_projects_table.jpg)
**themes/simple/templates/Layout/ProjectsHolder.ss**
@ -331,7 +334,7 @@ Creating the detail view for each `Project` page works in a very similar way.
Given that we're in the context of a single project,
we can access the "Students" and "Mentors" relationships directly in the template.
![tutorial:tutorial5_project.jpg](_images/tutorial5_project.jpg)
![tutorial:tutorial5_project.jpg](/_images/tutorial5_project.jpg)
**themes/simple/templates/Layout/Project.ss**

View File

@ -0,0 +1,16 @@
title: Tutorials
introduction: The tutorials below take a step by step look at how to build a SilverStripe application.
## Written Tutorials
[CHILDREN]
## Video tutorials
* [How to set up a local development environment in SilverStripe](https://vimeo.com/108861537)
## Help: If you get stuck
* [Common Problems](/getting_started/installation/common_problems): Review some existing solutions to common problems.
* [SilverStripe Community Forums](http://www.silverstripe.org/community/forums/): Head over to the forums and ask the community
for help.

View File

@ -0,0 +1,588 @@
title: Introduction to the Data Model and ORM
summary: Introduction to creating and querying a database records through the ORM (object-relational model)
# Introduction to the Data Model and ORM
SilverStripe uses an [object-relational model](http://en.wikipedia.org/wiki/Object-relational_model) to represent its
information.
* Each database table maps to a PHP class.
* Each database row maps to a PHP object.
* Each database column maps to a property on a PHP object.
All data tables in SilverStripe are defined as subclasses of [api:DataObject]. The [api:DataObject] class represents a
single row in a database table, following the ["Active Record"](http://en.wikipedia.org/wiki/Active_record_pattern)
design pattern. Database Columns are is defined as [Data Types](data_types_and_casting) in the static `$db` variable
along with any [relationships](../relations) defined as `$has_one`, `$has_many`, `$many_many` properties on the class.
Let's look at a simple example:
**mysite/code/Player.php**
:::php
<?php
class Player extends DataObject {
private static $db = array(
'PlayerNumber' => 'Int',
'FirstName' => 'Varchar(255)',
'LastName' => 'Text',
'Birthday' => 'Date'
);
}
This `Player` class definition will create a database table `Player` with columns for `PlayerNumber`, `FirstName` and
so on. After writing this class, we need to regenerate the database schema.
## Generating the Database Schema
After adding, modifying or removing `DataObject` subclasses, make sure to rebuild your SilverStripe database. The
database schema is generated automatically by visiting the URL http://www.yoursite.com/dev/build while authenticated as an administrator.
This script will analyze the existing schema, compare it to what's required by your data classes, and alter the schema
as required.
It will perform the following changes:
* Create any missing tables
* Create any missing fields
* Create any missing indexes
* Alter the field type of any existing fields
* Rename any obsolete tables that it previously created to _obsolete_(tablename)
It **won't** do any of the following
* Delete tables
* Delete fields
* Rename any tables that it doesn't recognize. This allows other applications to coexist in the same database, as long as
their table names don't match a SilverStripe data class.
<div class="notice" markdown='1'>
You need to be logged in as an administrator to perform this command, unless your site is in [dev mode](../debugging),
or the command is run through [CLI](../cli).
</div>
When rebuilding the database schema through the [api:SS_ClassLoader] the following additional properties are
automatically set on the `DataObject`.
* ID: Primary Key. When a new record is created, SilverStripe does not use the database's built-in auto-numbering
system. Instead, it will generate a new `ID` by adding 1 to the current maximum ID.
* ClassName: An enumeration listing this data-class and all of its subclasses.
* Created: A date/time field set to the creation date of this record
* LastEdited: A date/time field set to the date this record was last edited through `write()`
**mysite/code/Player.php**
:::php
<?php
class Player extends DataObject {
private static $db = array(
'PlayerNumber' => 'Int',
'FirstName' => 'Varchar(255)',
'LastName' => 'Text',
'Birthday' => 'Date'
);
}
Generates the following `SQL`.
CREATE TABLE `Player` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`ClassName` enum('Player') DEFAULT 'Player',
`LastEdited` datetime DEFAULT NULL,
`Created` datetime DEFAULT NULL,
`PlayerNumber` int(11) NOT NULL DEFAULT '0',
`FirstName` varchar(255) DEFAULT NULL,
`LastName` mediumtext,
`Birthday` datetime DEFAULT NULL,
PRIMARY KEY (`ID`),
KEY `ClassName` (`ClassName`)
);
## Creating Data Records
A new instance of a [api:DataObject] can be created using the `new` syntax.
:::php
$player = new Player();
Or, a better way is to use the `create` method.
:::php
$player = Player::create();
<div class="notice" markdown='1'>
Using the `create()` method provides chainability, which can create add elegance and brevity to your code, e.g. `Player::create()->write()`. More importantly, however, it will look up the class in the [Injector](../extending/injector) so that can the class can be overriden by [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection).
</div>
Database columns and properties can be set as class properties on the object. The SilverStripe ORM handles the saving
of the values through a custom `__set()` method.
:::php
$player->FirstName = "Sam";
$player->PlayerNumber = 07;
To save the `DataObject` to the database, use the `write()` method. The first time `write()` is called, an `ID` will be
set.
:::php
$player->write();
For convenience, the `write()` method returns the record's ID. This is particularly useful when creating new records.
:::php
$player = Player::create();
$id = $player->write();
## Querying Data
With the `Player` class defined we can query our data using the `ORM` or Object-Relational Model. The `ORM` provides
shortcuts and methods for fetching, sorting and filtering data from our database.
:::php
$players = Player::get();
// returns a `DataList` containing all the `Player` objects.
$player = Player::get()->byId(2);
// returns a single `Player` object instance that has the ID of 2.
echo $player->ID;
// returns the players 'ID' column value
echo $player->dbObject('LastEdited')->Ago();
// calls the `Ago` method on the `LastEdited` property.
The `ORM` uses a "fluent" syntax, where you specify a query by chaining together different methods. Two common methods
are `filter()` and `sort()`:
:::php
$members = Player::get()->filter(array(
'FirstName' => 'Sam'
))->sort('Surname');
// returns a `DataList` containing all the `Player` records that have the `FirstName` of 'Sam'
<div class="info" markdown="1">
Provided `filter` values are automatically escaped and do not require any escaping.
</div>
## Lazy Loading
The `ORM` doesn't actually execute the [api:SQLQuery] until you iterate on the result with a `foreach()` or `<% loop %>`.
It's smart enough to generate a single efficient query at the last moment in time without needing to post-process the
result set in PHP. In `MySQL` the query generated by the ORM may look something like this
:::php
$players = Player::get()->filter(array(
'FirstName' => 'Sam'
));
$players = $players->sort('Surname');
// executes the following single query
// SELECT * FROM Player WHERE FirstName = 'Sam' ORDER BY Surname
This also means that getting the count of a list of objects will be done with a single, efficient query.
:::php
$players = Player::get()->filter(array(
'FirstName' => 'Sam'
))->sort('Surname');
// This will create an single SELECT COUNT query
// SELECT COUNT(*) FROM Player WHERE FirstName = 'Sam'
echo $players->Count();
## Looping over a list of objects
`get()` returns a `DataList` instance. You can loop over `DataList` instances in both PHP and templates.
:::php
$players = Player::get();
foreach($players as $player) {
echo $player->FirstName;
}
Notice that we can step into the loop safely without having to check if `$players` exists. The `get()` call is robust, and will at worst return an empty `DataList` object. If you do want to check if the query returned any records, you can use the `exists()` method, e.g.
:::php
$players = Player::get();
if($players->exists()) {
// do something here
}
See the [Lists](../lists) documentation for more information on dealing with [api:SS_List] instances.
## Returning a single DataObject
There are a couple of ways of getting a single DataObject from the ORM. If you know the ID number of the object, you
can use `byID($id)`:
:::php
$player = Player::get()->byID(5);
`get()` returns a [api:DataList] instance. You can use operations on that to get back a single record.
:::php
$players = Player::get();
$first = $players->first();
$last = $players->last();
## Sorting
If would like to sort the list by `FirstName` in a ascending way (from A to Z).
:::php
// Sort can either be Ascending (ASC) or Descending (DESC)
$players = Player::get()->sort('FirstName', 'ASC');
// Ascending is implied
$players = Player::get()->sort('FirstName');
To reverse the sort
:::php
$players = Player::get()->sort('FirstName', 'DESC');
// or..
$players = Player::get()->sort('FirstName', 'ASC')->reverse();
However you might have several entries with the same `FirstName` and would like to sort them by `FirstName` and
`LastName`
:::php
$players = Players::get()->sort(array(
'FirstName' => 'ASC',
'LastName'=>'ASC'
));
You can also sort randomly.
:::php
$players = Player::get()->sort('RAND()')
## Filtering Results
The `filter()` method filters the list of objects that gets returned.
:::php
$players = Player::get()->filter(array(
'FirstName' => 'Sam'
));
Each element of the array specifies a filter. You can specify as many filters as you like, and they **all** must be
true for the record to be included in the result.
The key in the filter corresponds to the field that you want to filter and the value in the filter corresponds to the
value that you want to filter to.
So, this would return only those players called "Sam Minnée".
:::php
$players = Player::get()->filter(array(
'FirstName' => 'Sam',
'LastName' => 'Minnée',
));
// SELECT * FROM Player WHERE FirstName = 'Sam' AND LastName = 'Minnée'
There is also a shorthand way of getting Players with the FirstName of Sam.
:::php
$players = Player::get()->filter('FirstName', 'Sam');
Or if you want to find both Sam and Sig.
:::php
$players = Player::get()->filter(
'FirstName', array('Sam', 'Sig')
);
// SELECT * FROM Player WHERE FirstName IN ('Sam', 'Sig')
You can use [SearchFilters](searchfilters) to add additional behavior to your `filter` command rather than an
exact match.
:::php
$players = Player::get()->filter(array(
'FirstName:StartsWith' => 'S'
'PlayerNumber:GreaterThan' => '10'
));
### filterAny
Use the `filterAny()` method to match multiple criteria non-exclusively (with an "OR" disjunctive),
:::php
$players = Player::get()->filterAny(array(
'FirstName' => 'Sam',
'Age' => 17,
));
// SELECT * FROM Player WHERE ("FirstName" = 'Sam' OR "Age" = '17')
You can combine both conjunctive ("AND") and disjunctive ("OR") statements.
:::php
$players = Player::get()
->filter(array(
'LastName' => 'Minnée'
))
->filterAny(array(
'FirstName' => 'Sam',
'Age' => 17,
));
// SELECT * FROM Player WHERE ("LastName" = 'Minnée' AND ("FirstName" = 'Sam' OR "Age" = '17'))
You can use [SearchFilters](searchfilters) to add additional behavior to your `filterAny` command.
:::php
$players = Player::get()->filterAny(array(
'FirstName:StartsWith' => 'S'
'PlayerNumber:GreaterThan' => '10'
));
### filterByCallback
It is also possible to filter by a PHP callback, this will force the data model to fetch all records and loop them in
PHP, thus `filter()` or `filterAny()` are to be preferred over `filterByCallback()`.
<div class="notice" markdown="1">
Because `filterByCallback()` has to run in PHP, it has a significant performance tradeoff, and should not be used on large recordsets.
`filterByCallback()` will always return an `ArrayList`.
</div>
The first parameter to the callback is the item, the second parameter is the list itself. The callback will run once
for each record, if the callback returns true, this record will be added to the list of returned items.
The below example will get all `Players` aged over 10.
:::php
$players = Player::get()->filterByCallback(function($item, $list) {
return ($item->Age() > 10);
});
### Exclude
The `exclude()` method is the opposite to the filter in that it removes entries from a list.
:::php
$players = Player::get()->exclude('FirstName', 'Sam');
// SELECT * FROM Player WHERE FirstName != 'Sam'
Remove both Sam and Sig..
:::php
$players = Player::get()->exclude(
'FirstName', array('Sam','Sig')
);
`Exclude` follows the same pattern as filter, so for removing only Sam Minnée from the list:
:::php
$players = Player::get()->exclude(array(
'FirstName' => 'Sam',
'Surname' => 'Minnée',
));
And removing Sig and Sam with that are either age 17 or 74.
:::php
$players = Player::get()->exclude(array(
'FirstName' => array('Sam', 'Sig'),
'Age' => array(17, 43)
));
// SELECT * FROM Player WHERE ("FirstName" NOT IN ('Sam','Sig) OR "Age" NOT IN ('17', '74));
You can use [SearchFilters](searchfilters) to add additional behavior to your `exclude` command.
:::php
$players = Player::get()->exclude(array(
'FirstName:EndsWith' => 'S'
'PlayerNumber:LessThanOrEqual' => '10'
));
### Subtract
You can subtract entries from a [api:DataList] by passing in another DataList to `subtract()`
:::php
$sam = Player::get()->filter('FirstName', 'Sam');
$players = Player::get();
$noSams = $players->subtract($sam);
Though for the above example it would probably be easier to use `filter()` and `exclude()`. A better use case could be
when you want to find all the members that does not exist in a Group.
:::php
// ... Finding all members that does not belong to $group.
$otherMembers = Member::get()->subtract($group->Members());
### Limit
You can limit the amount of records returned in a DataList by using the `limit()` method.
:::php
$members = Member::get()->limit(5);
`limit()` accepts two arguments, the first being the amount of results you want returned, with an optional second
parameter to specify the offset, which allows you to tell the system where to start getting the results from. The
offset, if not provided as an argument, will default to 0.
:::php
// Return 10 members with an offset of 4 (starting from the 5th result).
$members = Member::get()->sort('Surname')->limit(10, 4);
<div class="alert">
Note that the `limit` argument order is different from a MySQL LIMIT clause.
</div>
### Raw SQL
Occasionally, the system described above won't let you do exactly what you need to do. In these situations, we have
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
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
you need it to, you may also consider extending the ORM with new data types or filter modifiers
#### Where clauses
You can specify a WHERE clause fragment (that will be combined with other filters using AND) with the `where()` method:
:::php
$members = Member::get()->where("\"FirstName\" = 'Sam'")
#### Joining Tables
You can specify a join with the `innerJoin` and `leftJoin` methods. Both of these methods have the same arguments:
* The name of the table to join to.
* The filter clause for the join.
* An optional alias.
:::php
// Without an alias
$members = Member::get()
->leftJoin("Group_Members", "\"Group_Members\".\"MemberID\" = \"Member\".\"ID\"");
$members = Member::get()
->innerJoin("Group_Members", "\"Rel\".\"MemberID\" = \"Member\".\"ID\"", "Rel");
<div class="alert" markdown="1">
Passing a *$join* statement to will filter results further by the JOINs performed against the foreign table. It will
**not** return the additionally joined data.
</div>
### Default Values
Define the default values for all the `$db` fields. This example sets the "Status"-column on Player to "Active"
whenever a new object is created.
:::php
<?php
class Player extends DataObject {
private static $defaults = array(
"Status" => 'Active',
);
}
<div class="notice" markdown='1'>
Note: Alternatively you can set defaults directly in the database-schema (rather than the object-model). See
[Data Types and Casting](data-types) for details.
</div>
## Subclasses
Inheritance is supported in the data model: separate tables will be linked together, the data spread across these
tables. The mapping and saving logic is handled by SilverStripe, you don't need to worry about writing SQL most of the
time.
For example, suppose we have the following set of classes:
:::php
<?php
class Page extends SiteTree {
}
class NewsPage extends Page {
private static $db = array(
'Summary' => 'Text'
);
}
The data for the following classes would be stored across the following tables:
:::yml
SiteTree:
- ID: Int
- ClassName: Enum('SiteTree', 'Page', 'NewsPage')
- Created: Datetime
- LastEdited: Datetime
- Title: Varchar
- Content: Text
NewsArticle:
- ID: Int
- Summary: Text
Accessing the data is transparent to the developer.
:::php
$news = NewsPage::get();
foreach($news as $article) {
echo $news->Title;
}
The way the ORM stores the data is this:
* "Base classes" are direct sub-classes of [api:DataObject]. They are always given a table, whether or not they have
special fields. This is called the "base table". In our case, `SiteTree` is the base table.
* The base table's ClassName field is set to class of the given record. It's an enumeration of all possible
sub-classes of the base class (including the base class itself).
* Each sub-class of the base object will also be given its own table, *as long as it has custom fields*. In the
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]`.
To retrieve a news article, SilverStripe joins the [api:SiteTree], [api:Page] and NewsArticle tables by their ID fields.
## Related Documentation
* [Data Types and Casting](../data_types_and_casting)
## API Documentation
* [api:DataObject]
* [api:DataList]
* [api:DataQuery]

View File

@ -0,0 +1,277 @@
title: Relations between Records
summary: Relate models together using the ORM using has_one, has_many, and many_many.
# Relations between Records
In most situations you will likely see more than one [api:DataObject] and several classes in your data model may relate
to one another. An example of this is a `Player` object may have a relationship to one or more `Team` or `Coach` classes
and could take part in many `Games`. Relations are a key part of designing and building a good data model.
Relations are built through static array definitions on a class, in the format `<relationship-name> => <classname>`.
SilverStripe supports a number of relationship types and each relationship type can have any number of relations.
## has_one
A 1-to-1 relation creates a database-column called "`<relationship-name>`ID", in the example below this would be
"TeamID" on the "Player"-table.
:::php
<?php
class Team extends DataObject {
private static $db = array(
'Title' => 'Varchar'
);
private static $has_many = array(
'Players' => 'Player'
);
}
class Player extends DataObject {
private static $has_one = array(
"Team" => "Team",
);
}
This defines a relationship called `Team` which links to a `Team` class. The `ORM` handles navigating the relationship
and provides a short syntax for accessing the related object.
At the database level, the `has_one` creates a `TeamID` field on `Player`. A `has_many` field does not impose any database changes. It merely injects a new method into the class to access the related records (in this case, `Players()`)
:::php
$player = Player::get()->byId(1);
$team = $player->Team();
// returns a 'Team' instance.
echo $player->Team()->Title;
// returns the 'Title' column on the 'Team' or `getTitle` if it exists.
The relationship can also be navigated in [templates](../templates).
:::ss
<% with $Player %>
<% if $Team %>
Plays for $Team.Title
<% end_if %>
<% end_with %>
## has_many
Defines 1-to-many joins. As you can see from the previous example, `$has_many` goes hand in hand with `$has_one`.
<div class="alert" markdown='1'>
Please specify a $has_one-relationship on the related child-class as well, in order to have the necessary accessors
available on both ends.
</div>
:::php
<?php
class Team extends DataObject {
private static $db = array(
'Title' => 'Varchar'
);
private static $has_many = array(
'Players' => 'Player'
);
}
class Player extends DataObject {
private static $has_one = array(
"Team" => "Team",
);
}
Much like the `has_one` relationship, `has_many` can be navigated through the `ORM` as well. The only difference being
you will get an instance of [api:HasManyList] rather than the object.
:::php
$team = Team::get()->first();
echo $team->Players();
// [HasManyList]
echo $team->Players()->Count();
// returns '14';
foreach($team->Players() as $player) {
echo $player->FirstName;
}
To specify multiple $has_manys to the same object you can use dot notation to distinguish them like below:
:::php
<?php
class Person extends DataObject {
private static $has_many = array(
"Managing" => "Company.Manager",
"Cleaning" => "Company.Cleaner",
);
}
class Company extends DataObject {
private static $has_one = array(
"Manager" => "Person",
"Cleaner" => "Person"
);
}
Multiple `$has_one` relationships are okay if they aren't linking to the same object type. Otherwise, they have to be
named.
## belongs_to
Defines a 1-to-1 relationship with another object, which declares the other end of the relationship with a
corresponding $has_one. A single database column named `<relationship-name>ID` will be created in the object with the
`$has_one`, but the $belongs_to by itself will not create a database field. This field will hold the ID of the object
declaring the `$belongs_to`.
Similarly with $has_many, dot notation can be used to explicitly specify the `$has_one` which refers to this relation.
This is not mandatory unless the relationship would be otherwise ambiguous.
:::php
<?php
class Team extends DataObject {
private static $has_one = array(
'Coach' => 'Coach'
);
}
class Coach extends DataObject {
private static $belongs_to = array(
'Team' => 'Team.Coach'
);
}
## many_many
Defines many-to-many joins. A new table, (this-class)_(relationship-name), will be created with a pair of ID fields.
<div class="warning" markdown='1'>
Please specify a $belongs_many_many-relationship on the related class as well, in order to have the necessary accessors
available on both ends.
</div>
:::php
<?php
class Team extends DataObject {
private static $many_many = array(
"Supporters" => "Supporter",
);
}
class Supporter extends DataObject {
private static $belongs_many_many = array(
"Supports" => "Team",
);
}
Much like the `has_one` relationship, `many_many` can be navigated through the `ORM` as well. The only difference being
you will get an instance of [api:ManyManyList] rather than the object.
:::php
$team = Team::get()->byId(1);
$supporters = $team->Supporters();
// returns a 'ManyManyList' instance.
The relationship can also be navigated in [templates](../templates).
:::ss
<% with $Supporter %>
<% loop $Supports %>
Supports $Title
<% end_if %>
<% end_with %>
## many_many or belongs_many_many?
If you're unsure about whether an object should take on `many_many` or `belongs_many_many`, the best way to think about it is that the object where the relationship will be edited (i.e. via checkboxes) should contain the `many_many`. For instance, in a `many_many` of Product => Categories, the `Product` should contain the `many_many`, because it is much more likely that the user will select Categories for a Product than vice-versa.
## Adding relations
Adding new items to a relations works the same, regardless if you're editing a **has_many** or a **many_many**. They are
encapsulated by [api:HasManyList] and [api:ManyManyList], both of which provide very similar APIs, e.g. an `add()`
and `remove()` method.
:::php
$team = Team::get()->byId(1);
// create a new supporter
$supporter = new Supporter();
$supporter->Name = "Foo";
$supporter->write();
// add the supporter.
$team->Supporters()->add($supporter);
## Custom Relations
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.
:::php
<?php
class Team extends DataObject {
private static $has_many = array(
"Players" => "Player"
);
public function ActivePlayers() {
return $this->Players()->filter('Status', 'Active');
}
}
<div class="notice" markdown="1">
Adding new records to a filtered `RelationList` like in the example above doesn't automatically set the filtered
criteria on the added record.
</div>
## Relations on Unsaved Objects
You can also set *has_many* and *many_many* relations before the `DataObject` is saved. This behavior uses the
[api:UnsavedRelationList] and converts it into the correct `RelationList` when saving the `DataObject` for the first
time.
This unsaved lists will also recursively save any unsaved objects that they contain.
As these lists are not backed by the database, most of the filtering methods on `DataList` cannot be used on a list of
this type. As such, an `UnsavedRelationList` should only be used for setting a relation before saving an object, not
for displaying the objects contained in the relation.
## Related Documentation
* [Introduction to the Data Model and ORM](data_model_and_orm)
* [Lists](lists)
## API Documentation
* [api:HasManyList]
* [api:ManyManyList]
* [api:DataObject]

View File

@ -0,0 +1,94 @@
title: Managing Lists
summary: The SS_List interface allows you to iterate through and manipulate a list of objects.
# Managing Lists
Whenever using the ORM to fetch records or navigate relationships you will receive an [api:SS_List] instance commonly as
either [api:DataList] or [api:RelationList]. This object gives you the ability to iterate over each of the results or
modify.
## Iterating over the list.
[api:SS_List] implements `IteratorAggregate`, allowing you to loop over the instance.
:::php
$members = Member::get();
foreach($members as $member) {
echo $member->Name;
}
Or in the template engine:
:::ss
<% loop $Members %>
<!-- -->
<% end_loop %>
## Finding an item by value.
:::php
// $list->find($key, $value);
//
$members = Member::get();
echo $members->find('ID', 4)->FirstName;
// returns 'Sam'
## Maps
A map is an array where the array indexes contain data as well as the values. You can build a map from any list
:::php
$members = Member::get()->map('ID', 'FirstName');
// $members = array(
// 1 => 'Sam'
// 2 => 'Sig'
// 3 => 'Will'
// );
This functionality is provided by the [api:SS_Map] class, which can be used to build a map around any `SS_List`.
:::php
$members = Member::get();
$map = new SS_Map($members, 'ID', 'FirstName');
## Column
:::php
$members = Member::get();
echo $members->column('Email');
// returns array(
// 'sam@silverstripe.com',
// 'sig@silverstripe.com',
// 'will@silverstripe.com'
// );
## ArrayList
[api:ArrayList] exists to wrap a standard PHP array in the same API as a database backed list.
:::php
$sam = Member::get()->byId(5);
$sig = Member::get()->byId(6);
$list = new ArrayList();
$list->push($sam);
$list->push($sig);
echo $list->Count();
// returns '2'
## API Documentation
* [api:SS_List]
* [api:RelationList]
* [api:DataList]
* [api:ArrayList]
* [api:SS_Map]

View File

@ -0,0 +1,182 @@
title: Data Types, Overloading and Casting
summary: Learn how how data is stored going in and coming out of the ORM and how to modify it.
# Data Types and Casting
Each model in a SilverStripe [api:DataObject] will handle data at some point. This includes database columns such as
the ones defined in a `$db` array or simply a method that returns data for the template.
A Data Type is represented in SilverStripe by a [api:DBField] subclass. The class is responsible for telling the ORM
about how to store its data in the database and how to format the information coming out of the database, i.e. on a template.
In the `Player` example, we have four database columns each with a different data type (Int, Varchar).
**mysite/code/Player.php**
:::php
<?php
class Player extends DataObject {
private static $db = array(
'PlayerNumber' => 'Int',
'FirstName' => 'Varchar(255)',
'LastName' => 'Text',
'Birthday' => 'Date'
);
}
## Available Types
* [api:Boolean]: A boolean field.
* [api:Currency]: A number with 2 decimal points of precision, designed to store currency values.
* [api:Date]: A date field
* [api:Decimal]: A decimal number.
* [api:Enum]: An enumeration of a set of strings
* [api:HTMLText]: A variable-length string of up to 2MB, designed to store HTML
* [api:HTMLVarchar]: A variable-length string of up to 255 characters, designed to store HTML
* [api:Int]: An integer field.
* [api:Percentage]: A decimal number between 0 and 1 that represents a percentage.
* [api:SS_Datetime]: A date / time field
* [api:Text]: A variable-length string of up to 2MB, designed to store raw text
* [api:Time]: A time field
* [api:Varchar]: A variable-length string of up to 255 characters, designed to store raw text.
You can define your own [api:DBField] instances if required as well. See the API documentation for a list of all the
available subclasses.
## Formatting Output
The Data Type does more than setup the correct database schema. They can also define methods and formatting helpers for
output. You can manually create instances of a Data Type and pass it through to the template.
If this case, we'll create a new method for our `Player` that returns the full name. By wrapping this in a [api:Varchar]
object we can control the formatting and it allows us to call methods defined from `Varchar` as `LimitCharacters`.
**mysite/code/Player.php**
:::php
<?php
class Player extends DataObject {
..
public function getName() {
return DBField::create_field('Varchar', $this->FirstName . ' '. $this->LastName);
}
}
Then we can refer to a new `Name` column on our `Player` instances. In templates we don't need to use the `get` prefix.
:::php
$player = Player::get()->byId(1);
echo $player->Name;
// returns "Sam Minnée"
echo $player->getName();
// returns "Sam Minnée";
echo $player->getName()->LimitCharacters(2);
// returns "Sa.."
### Casting
Rather than manually returning objects from your custom functions. You can use the `$casting` property.
:::php
<?php
class Player extends DataObject {
private static $casting = array(
"Name" => 'Varchar',
);
public function getName() {
return $this->FirstName . ' '. $this->LastName;
}
}
The properties on any SilverStripe object can be type casted automatically, by transforming its scalar value into an
instance of the [api:DBField] class, providing additional helpers. For example, a string can be cast as a [api:Text]
type, which has a `FirstSentence()` method to retrieve the first sentence in a longer piece of text.
On the most basic level, the class can be used as simple conversion class from one value to another, e.g. to round a
number.
:::php
DBField::create_field('Double', 1.23456)->Round(2); // results in 1.23
Of course that's much more verbose than the equivalent PHP call. The power of [api:DBField] comes with its more
sophisticated helpers, like showing the time difference to the current date:
:::php
DBField::create_field('Date', '1982-01-01')->TimeDiff(); // shows "30 years ago"
## Casting ViewableData
Most objects in SilverStripe extend from [api:ViewableData], which means they know how to present themselves in a view
context. Through a `$casting` array, arbitrary properties and getters can be casted:
:::php
<?php
class MyObject extends ViewableData {
private static $casting = array(
'MyDate' => 'Date'
);
public function getMyDate() {
return '1982-01-01';
}
}
$obj = new MyObject;
$obj->getMyDate(); // returns string
$obj->MyDate; // returns string
$obj->obj('MyDate'); // returns object
$obj->obj('MyDate')->InPast(); // returns boolean
## Casting HTML Text
The database field types [api:HTMLVarchar]/[api:HTMLText] and [api:Varchar]/[api:Text] are exactly the same in
the database. However, the template engine knows to escape fields without the `HTML` prefix automatically in templates,
to prevent them from rendering HTML interpreted by browsers. This escaping prevents attacks like CSRF or XSS (see
"[security](../security)"), which is important if these fields store user-provided data.
<div class="hint" markdown="1">
You can disable this auto-escaping by using the `$MyField.RAW` escaping hints, or explicitly request escaping of HTML
content via `$MyHtmlField.XML`.
</div>
## Overloading
"Getters" and "Setters" are functions that help us save fields to our [api:DataObject] instances. By default, the
methods `getField()` and `setField()` are used to set column data. They save to the protected array, `$obj->record`.
We can overload the default behavior by making a function called "get`<fieldname>`" or "set`<fieldname>`".
The following example will use the result of `getStatus` instead of the 'Status' database column. We can refer to the
database column using `dbObject`.
:::php
<?php
class Player extends DataObject {
private static $db = array(
"Status" => "Enum(array('Active', 'Injured', 'Retired'))"
);
public function getStatus() {
return (!$this->obj("Birthday")->InPast()) ? "Unborn" : $this->dbObject('Status')->Value();
}
## API Documentation
* [api:DataObject]
* [api:DBField]

View File

@ -0,0 +1,84 @@
title: Extending DataObjects
summary: Modify the data model without using subclasses.
# Extending DataObjects
You can add properties and methods to existing [api:DataObjects]s like [api:Member] without hacking core code or sub
classing by using [api:DataExtension]. See the [Extending SilverStripe](../extending) guide for more information on
[api:DataExtension].
The following documentation outlines some common hooks that the [api:Extension] API provides specifically for managing
data records.
## onBeforeWrite
You can customize saving-behavior for each DataObject, e.g. for adding workflow or data customization. The function is
triggered when calling *write()* to save the object to the database. This includes saving a page in the CMS or altering
a `ModelAdmin` record.
Example: Disallow creation of new players if the currently logged-in player is not a team-manager.
:::php
<?php
class Player extends DataObject {
private static $has_many = array(
"Teams"=>"Team"
);
public function onBeforeWrite() {
// check on first write action, aka "database row creation" (ID-property is not set)
if(!$this->isInDb()) {
$currentPlayer = Member::currentUser();
if(!$currentPlayer->IsTeamManager()) {
user_error('Player-creation not allowed', E_USER_ERROR);
exit();
}
}
// check on every write action
if(!$this->record['TeamID']) {
user_error('Cannot save player without a valid team', E_USER_ERROR);
exit();
}
// CAUTION: You are required to call the parent-function, otherwise
// SilverStripe will not execute the request.
parent::onBeforeWrite();
}
}
## onBeforeDelete
Triggered before executing *delete()* on an existing object.
Example: Checking for a specific [permission](/reference/permission) to delete this type of object. It checks if a
member is logged in who belongs to a group containing the permission "PLAYER_DELETE".
:::php
<?php
class Player extends DataObject {
private static $has_many = array(
"Teams" => "Team"
);
public function onBeforeDelete() {
if(!Permission::check('PLAYER_DELETE')) {
Security::permissionFailure($this);
exit();
}
parent::onBeforeDelete();
}
}
<div class="notice" markdown='1'>
Note: There are no separate methods for *onBeforeCreate* and *onBeforeUpdate*. Please check `$this->isInDb()` to toggle
these two modes, as shown in the example above.
</div>

View File

@ -0,0 +1,53 @@
title: SearchFilter Modifiers
summary: Use suffixes on your ORM queries.
# SearchFilter Modifiers
The `filter` and `exclude` operations specify exact matches by default. However, there are a number of suffixes that
you can put on field names to change this behavior. These are represented as `SearchFilter` subclasses and include.
* [api:StartsWithFilter]
* [api:EndsWithFilter]
* [api:PartialMatchFilter]
* [api:GreaterThanFilter]
* [api:GreaterThanOrEqualFilter]
* [api:LessThanFilter]
* [api:LessThanOrEqualFilter]
An example of a `SearchFilter` in use:
:::php
// fetch any player that starts with a S
$players = Player::get()->filter(array(
'FirstName:StartsWith' => 'S'
'PlayerNumber:GreaterThan' => '10'
));
// to fetch any player that's name contains the letter 'z'
$players = Player::get()->filterAny(array(
'FirstName:PartialMatch' => 'z'
'LastName:PartialMatch' => 'z'
));
Developers can define their own [api:SearchFilter] if needing to extend the ORM filter and exclude behaviors.
These suffixes can also take modifiers themselves. The modifiers currently supported are `":not"`, `":nocase"` and
`":case"`. These negate the filter, make it case-insensitive and make it case-sensitive, respectively. The default
comparison uses the database's default. For MySQL and MSSQL, this is case-insensitive. For PostgreSQL, this is
case-sensitive.
The following is a query which will return everyone whose first name starts with "S", either lowercase or uppercase:
:::php
$players = Player::get()->filter(array(
'FirstName:StartsWith:nocase' => 'S'
));
// use :not to perform a converse operation to filter anything but a 'W'
$players = Player::get()->filter(array(
'FirstName:StartsWith:not' => 'W'
));
## API Documentation
* [api:SearchFilter]

View File

@ -0,0 +1,50 @@
title: Model-Level Permissions
summary: Reduce risk by securing models.
# Model-Level Permissions
Models can be modified in a variety of controllers and user interfaces, all of which can implement their own security
checks. Often it makes sense to centralize those checks on the model, regardless of the used controller.
The API provides four methods for this purpose: `canEdit()`, `canCreate()`, `canView()` and `canDelete()`.
Since they're PHP methods, they can contain arbitrary logic matching your own requirements. They can optionally receive
a `$member` argument, and default to the currently logged in member (through `Member::currentUser()`).
<div class="notice" markdown="1">
By default, all `DataObject` subclasses can only be edited, created and viewed by users with the 'ADMIN' permission
code.
</div>
:::php
<?php
class MyDataObject extends DataObject {
public function canView($member = null) {
return Permission::check('CMS_ACCESS_CMSMain', 'any', $member);
}
public function canEdit($member = null) {
return Permission::check('CMS_ACCESS_CMSMain', 'any', $member);
}
public function canDelete($member = null) {
return Permission::check('CMS_ACCESS_CMSMain', 'any', $member);
}
public function canCreate($member = null) {
return Permission::check('CMS_ACCESS_CMSMain', 'any', $member);
}
}
<div class="alert" markdown="1">
These checks are not enforced on low-level ORM operations such as `write()` or `delete()`, but rather rely on being
checked in the invoking code. The CMS default sections as well as custom interfaces like [api:ModelAdmin] or
[api:GridField] already enforce these permissions.
</div>
## API Documentation
* [api:DataObject]
* [api:Permission]

View File

@ -0,0 +1,114 @@
title: SQLQuery
summary: Write and modify direct database queries through SQLQuery.
# SQLQuery
A [api:SQLQuery] object represents a SQL query, which can be serialized into a SQL statement. Dealing with low-level
SQL such as `mysql_query()` is not encouraged, since the ORM provides powerful abstraction API's.
For example, if you want to run a simple `COUNT` SQL statement, the following three statements are functionally
equivalent:
:::php
// Through raw SQL.
$count = DB::query('SELECT COUNT(*) FROM "Member"')->value();
// Through SQLQuery abstraction layer.
$query = new SQLQuery();
$count = $query->setFrom('Member')->setSelect('COUNT(*)')->value();
// Through the ORM.
$count = Member::get()->count();
<div class="info">
The SQLQuery object is used by the SilverStripe ORM internally. By understanding SQLQuery, you can modify the SQL that
the ORM creates.
</div>
## Usage
### Select
:::php
$sqlQuery = new SQLQuery();
$sqlQuery->setFrom('Player');
$sqlQuery->selectField('FieldName', 'Name');
$sqlQuery->selectField('YEAR("Birthday")', 'Birthyear');
$sqlQuery->addLeftJoin('Team','"Player"."TeamID" = "Team"."ID"');
$sqlQuery->addWhere('YEAR("Birthday") = 1982');
// $sqlQuery->setOrderBy(...);
// $sqlQuery->setGroupBy(...);
// $sqlQuery->setHaving(...);
// $sqlQuery->setLimit(...);
// $sqlQuery->setDistinct(true);
// Get the raw SQL (optional)
$rawSQL = $sqlQuery->sql();
// Execute and return a Query object
$result = $sqlQuery->execute();
// Iterate over results
foreach($result as $row) {
echo $row['BirthYear'];
}
The `$result` is an array lightly wrapped in a database-specific subclass of `[api:Query]`. This class implements the
*Iterator*-interface, and provides convenience-methods for accessing the data.
### Delete
:::php
$sqlQuery->setDelete(true);
### Insert / Update
<div class="alert" markdown="1">
Currently not supported through the `SQLQuery` class, please use raw `DB::query()` calls instead.
</div>
:::php
DB::query('UPDATE "Player" SET "Status"=\'Active\'');
### Joins
:::php
$sqlQuery = new SQLQuery();
$sqlQuery->setFrom('Player');
$sqlQuery->addSelect('COUNT("Player"."ID")');
$sqlQuery->addWhere('"Team"."ID" = 99');
$sqlQuery->addLeftJoin('Team', '"Team"."ID" = "Player"."TeamID"');
$count = $sqlQuery->execute()->value();
### Mapping
Creates a map based on the first two columns of the query result.
:::php
$sqlQuery = new SQLQuery();
$sqlQuery->setFrom('Player');
$sqlQuery->setSelect('ID');
$sqlQuery->selectField('CONCAT("Name", ' - ', YEAR("Birthdate")', 'NameWithBirthyear');
$map = $sqlQuery->execute()->map();
echo $map;
// returns array(
// 1 => "Foo - 1920",
// 2 => "Bar - 1936"
// );
## Related Documentation
* [Introduction to the Data Model and ORM](../data_model_and_orm)
## API Documentation
* [api:DataObject]
* [api:SQLQuery]
* [api:DB]
* [api:Query]
* [api:Database]

View File

@ -0,0 +1,48 @@
title: Model Validation and Constraints
summary: Validate your data at the model level
# Validation and Constraints
Traditionally, validation in SilverStripe has been mostly handled on the controller through [form validation](../forms).
While this is a useful approach, it can lead to data inconsistencies if the record is modified outside of the
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.
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].
Invalid objects won't be able to be written - a [api:ValidationException] will be thrown and no write will occur.
It is expected that you call `validate()` in your own application to test that an object is valid before attempting a
write, and respond appropriately if it isn't.
The return value of `validate()` is a [api:ValidationResult] object.
:::php
<?php
class MyObject extends DataObject {
private static $db = array(
'Country' => 'Varchar',
'Postcode' => 'Varchar'
);
public function validate() {
$result = parent::validate();
if($this->Country == 'DE' && $this->Postcode && strlen($this->Postcode) != 5) {
$result->error('Need five digits for German postcodes');
}
return $result;
}
}
## API Documentation
* [api:DataObject]
* [api:ValidationResult];

View File

@ -0,0 +1,177 @@
title: Versioning
summary: Add versioning to your database content through the Versioned extension.
# Versioning
Database content in SilverStripe can be "staged" before its publication, as well as track all changes through the
lifetime of a database record.
It is most commonly applied to pages in the CMS (the `SiteTree` class). Draft content edited in the CMS can be different
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
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
denoting the different "stages", which map to different database tables.
**mysite/_config/app.yml**
:::yml
MyRecord:
extensions:
- Versioned("Stage","Live")
<div class="notice" markdown="1">
The extension is automatically applied to `SiteTree` class. For more information on extensions see
[Extending](../extending) and the [Configuration](../configuration) documentation.
</div>
## Database Structure
Depending on how many stages you configured, two or more new tables will be created for your records. In the above, this
will create a new `MyClass_Live` table once you've rebuilt the database.
<div class="notice" markdown="1">
Note that the "Stage" naming has a special meaning here, it will leave the original table name unchanged, rather than
adding a suffix.
</div>
* `MyRecord` table: Contains staged data
* `MyRecord_Live` table: Contains live data
* `MyRecord_versions` table: Contains a version history (new record created on each save)
Similarly, any subclass you create on top of a versioned base will trigger the creation of additional tables, which are
automatically joined as required:
* `MyRecordSubclass` table: Contains only staged data for subclass columns
* `MyRecordSubclass_Live` table: Contains only live data for subclass columns
* `MyRecordSubclass_versions` table: Contains only version history for subclass columns
## Usage
### Reading Versions
By default, all records are retrieved from the "Draft" stage (so the `MyRecord` table in our example). You can
explicitly request a certain stage through various getters on the `Versioned` class.
:::php
// Fetching multiple records
$stageRecords = Versioned::get_by_stage('MyRecord', 'Stage');
$liveRecords = Versioned::get_by_stage('MyRecord', 'Live');
// Fetching a single record
$stageRecord = Versioned::get_by_stage('MyRecord', 'Stage')->byID(99);
$liveRecord = Versioned::get_by_stage('MyRecord', 'Live')->byID(99);
### Historical Versions
The above commands will just retrieve the latest version of its respective stage for you, but not older versions stored
in the `<class>_versions` tables.
:::php
$historicalRecord = Versioned::get_version('MyRecord', <record-id>, <version-id>);
<div class="alert" markdown="1">
The record is retrieved as a `DataObject`, but saving back modifications via `write()` will create a new version,
rather than modifying the existing one.
</div>
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.
:::php
$record = MyRecord::get()->byID(99); // stage doesn't matter here
$versions = $record->allVersions();
echo $versions->First()->Version; // instance of Versioned_Version
### Writing Versions and Changing Stages
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
`<class>_versions` table. To avoid this, use `[writeWithoutVersion()](api:Versioned->writeWithoutVersion())` instead.
To move a saved version from one stage to another, call `[writeToStage(<stage>)](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(<from-stage>, <to-stage>)`.
:::php
$record = Versioned::get_by_stage('MyRecord', 'Stage')->byID(99);
$record->MyField = 'changed';
// will update `MyRecord` table (assuming Versioned::current_stage() == 'Stage'),
// and write a row to `MyRecord_versions`.
$record->write();
// will copy the saved record information to the `MyRecord_Live` table
$record->publish('Stage', 'Live');
Similarly, an "unpublish" operation does the reverse, and removes a record from a specific stage.
:::php
$record = MyRecord::get()->byID(99); // stage doesn't matter here
// will remove the row from the `MyRecord_Live` table
$record->deleteFromStage('Live');
### Forcing the Current Stage
The current stage is stored as global state on the object. It is usually modified by controllers, e.g. when a preview
is initialized. But it can also be set and reset temporarily to force a specific operation to run on a certain stage.
:::php
$origMode = Versioned::get_reading_mode(); // save current mode
$obj = MyRecord::getComplexObjectRetrieval(); // returns 'Live' records
Versioned::set_reading_mode('Stage'); // temporarily overwrite mode
$obj = MyRecord::getComplexObjectRetrieval(); // returns 'Stage' records
Versioned::set_reading_mode($origMode); // reset current mode
### 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
smaller modifications of the generated `DataList` objects.
Example: Get the first 10 live records, filtered by creation date:
:::php
$records = Versioned::get_by_stage('MyRecord', 'Live')->limit(10)->sort('Created', 'ASC');
### Permissions
The `Versioned` extension doesn't provide any permissions on its own, but you can have a look at the `SiteTree` class
for implementation samples, specifically `canPublish()` and `canDeleteFromStage()`.
### Page Specific Operations
Since the `Versioned` extension is primarily used for page objects, the underlying `SiteTree` class has some additional
helpers.
### Templates Variables
In templates, you don't need to worry about this distinction. The `$Content` variable contain the published content by
default, and only preview draft content if explicitly requested (e.g. by the "preview" feature in the CMS, or by adding ?stage=Stage to the URL). If you want
to force a specific stage, we recommend the `Controller->init()` method for this purpose, for example:
**mysite/code/MyController.php**
:::php
public function init() {
parent::init();
Versioned::set_reading_mode('Stage.Stage');
}
### Controllers
The current stage for each request is determined by `VersionedRequestFilter` before any controllers initialize, through
`Versioned::choose_site_stage()`. It checks for a `Stage` GET parameter, so you can force a draft stage by appending
`?stage=Stage` to your request. The setting is "sticky" in the PHP session, so any subsequent requests will also be in
draft stage.
<div class="alert" markdown="1">
The `choose_site_stage()` call only deals with setting the default stage, and doesn't check if the user is
authenticated to view it. As with any other controller logic, please use `DataObject->canView()` to determine
permissions, and avoid exposing unpublished content to your users.
</div>
## API Documentation
* [api:Versioned]

View File

@ -0,0 +1,228 @@
title: Building Model and Search Interfaces around Scaffolding
summary: A Model-driven approach to defining your application UI.
# Scaffolding
The ORM already has a lot of information about the data represented by a `DataObject` through its `$db` property, so
SilverStripe will use that information to provide scaffold some interfaces. This is done though [api:FormScaffolder]
to provide reasonable defaults based on the property type (e.g. a checkbox field for booleans). You can then further
customize those fields as required.
## Form Fields
An example is `DataObject`, SilverStripe will automatically create your CMS interface so you can modify what you need.
:::php
<?php
class MyDataObject extends DataObject {
private static $db = array(
'IsActive' => 'Boolean',
'Title' => 'Varchar',
'Content' => 'Text'
);
public function getCMSFields() {
// parent::getCMSFields() does all the hard work and creates the fields for Title, IsActive and Content.
$fields = parent::getCMSFields();
$fields->fieldByName('IsActive')->setTitle('Is active?');
return $fields;
}
}
To fully customize your form fields, start with an empty FieldList.
:::php
<?php
public function getCMSFields() {
$fields = FieldList::create(
TabSet::create("Root",
CheckboxSetField::create('IsActive','Is active?'),
TextField::create('Title'),
TextareaField::create('Content')
->setRows(5)
)
);
return $fields;
}
You can also alter the fields of built-in and module `DataObject` classes through your own
[DataExtension](../extensions), and a call to `DataExtension->updateCMSFields`.
## Searchable Fields
The `$searchable_fields` property uses a mixed array format that can be used to further customize your generated admin
system. The default is a set of array values listing the fields.
:::php
<?php
class MyDataObject extends DataObject {
private static $searchable_fields = array(
'Name',
'ProductCode'
);
}
Searchable fields will be appear in the search interface with a default form field (usually a [api:TextField]) and a
default search filter assigned (usually an [api:ExactMatchFilter]). To override these defaults, you can specify
additional information on `$searchable_fields`:
:::php
<?php
class MyDataObject extends DataObject {
private static $searchable_fields = array(
'Name' => 'PartialMatchFilter',
'ProductCode' => 'NumericField'
);
}
If you assign a single string value, you can set it to be either a [api:FormField] or [api:SearchFilter]. To specify
both, you can assign an array:
:::php
<?php
class MyDataObject extends DataObject {
private static $searchable_fields = array(
'Name' => array(
'field' => 'TextField',
'filter' => 'PartialMatchFilter',
),
'ProductCode' => array(
'title' => 'Product code #',
'field' => 'NumericField',
'filter' => 'PartialMatchFilter',
),
);
}
To include relations (`$has_one`, `$has_many` and `$many_many`) in your search, you can use a dot-notation.
:::php
<?php
class Team extends DataObject {
private static $db = array(
'Title' => 'Varchar'
);
private static $many_many = array(
'Players' => 'Player'
);
private static $searchable_fields = array(
'Title',
'Players.Name',
);
}
class Player extends DataObject {
private static $db = array(
'Name' => 'Varchar',
'Birthday' => 'Date'
);
private static $belongs_many_many = array(
'Teams' => 'Team'
);
}
### 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.
:::php
<?php
class MyDataObject extends DataObject {
private static $db = array(
'Name' => 'Text',
'OtherProperty' => 'Text',
'ProductCode' => 'Int',
);
private static $summary_fields = array(
'Name',
'ProductCode'
);
}
To include relations or field manipulations in your summaries, you can use a dot-notation.
:::php
<?php
class OtherObject extends DataObject {
private static $db = array(
'Title' => 'Varchar'
);
}
class MyDataObject extends DataObject {
private static $db = array(
'Name' => 'Text',
'Description' => 'HTMLText'
);
private static $has_one = array(
'OtherObject' => 'OtherObject'
);
private static $summary_fields = array(
'Name' => 'Name',
'Description.Summary' => 'Description (summary)',
'OtherObject.Title' => 'Other Object Title'
);
}
Non-textual elements (such as images and their manipulations) can also be used in summaries.
:::php
<?php
class MyDataObject extends DataObject {
private static $db = array(
'Name' => 'Text'
);
private static $has_one = array(
'HeroImage' => 'Image'
);
private static $summary_fields = array(
'Name' => 'Name',
'HeroImage.CMSThumbnail' => 'Hero Image'
);
}
## Related Documenation
* [SearchFilters](searchfilters)
## API Documentation
* [api:FormScaffolder]
* [api:DataObject]

View File

@ -0,0 +1,55 @@
title: Indexes
summary: Add Indexes to your Data Model to optimize database queries.
# Indexes
It is sometimes desirable to add indexes to your data model, whether to optimize queries or add a uniqueness constraint
to a field. This is done through the `DataObject::$indexes` map, which maps index names to descriptor arrays that
represent each index. There's several supported notations:
:::php
<?php
class MyObject extends DataObject {
private static $indexes = array(
'<column-name>' => true,
'<index-name>' => array('type' => '<type>', 'value' => '"<column-name>"'),
'<index-name>' => 'unique("<column-name>")'
);
}
The `<index-name>` can be an an arbitrary identifier in order to allow for more than one index on a specific database
column. The "advanced" notation supports more `<type>` notations. These vary between database drivers, but all of them
support the following:
* `index`: Standard index
* `unique`: Index plus uniqueness constraint on the value
* `fulltext`: Fulltext content index
In order to use more database specific or complex index notations, we also support raw SQL for as a value in the
`$indexes` definition. Keep in mind this will likely make your code less portable between databases.
**mysite/code/MyTestObject.php**
:::php
<?php
class MyTestObject extends DataObject {
private static $db = array(
'MyField' => 'Varchar',
'MyOtherField' => 'Varchar',
);
private static $indexes = array(
'MyIndexName' => array(
'type' => 'index',
'value' => '"MyField","MyOtherField"'
)
);
}
## API Documentation
* [api:DataObject]

View File

@ -0,0 +1,13 @@
title: Model and Databases
summary: Learn how SilverStripe manages database tables, ways to query your database and how to publish data.
introduction: This guide will cover how to create and manipulate data within SilverStripe and how to use the ORM (Object Relational Model) to query data.
In SilverStripe, application data will be represented by a [api:DataObject] class. A `DataObject` subclass defines the
data columns, relationships and properties of a particular data record. For example, [api:Member] is a `DataObject`
which stores information about a person, CMS user or mail subscriber.
[CHILDREN Exclude="How_tos"]
## How to's
[CHILDREN Folder="How_Tos"]

View File

@ -0,0 +1,493 @@
title: Template Syntax
summary: A look at the operations, variables and language controls you can use within templates.
# Template Syntax
SilverStripe templates are plain text files that have `.ss` extension and located within the `templates` directory of
a module, theme, or your `mysite` folder. A template can contain any markup language (e.g HTML, CSV, JSON..) and before
being rendered to the user, they're processed through [api:SSViewer]. This process replaces placeholders such as `$Var`
with real content from your [model](../model) and allows you to define logic controls like `<% if $Var %>`.
An example of a SilverStripe template is below:
**mysite/templates/Page.ss**
:::ss
<html>
<head>
<% base_tag %>
<title>$Title</title>
<% require themedCSS("screen") %>
</head>
<body>
<header>
<h1>Bob's Chicken Shack</h1>
</header>
<% with $CurrentMember %>
<p>Welcome $FirstName $Surname.</p>
<% end_with %>
<% if $Dishes %>
<ul>
<% loop $Dishes %>
<li>$Title ($Price.Nice)</li>
<% end_loop %>
</ul>
<% end_if %>
<% include Footer %>
</body>
</html>
<div class="note">
Templates can be used for more than HTML output. You can use them to output your data as JSON, XML, CSV or any other
text-based format.
</div>
## Variables
Variables are placeholders that will be replaced with data from the [DataModel](../model/) or the current
[Controller](../controllers). Variables are prefixed with a `$` character. Variable names must start with an
alphabetic character or underscore, with subsequent characters being alphanumeric or underscore:
:::ss
$Title
This inserts the value of the Title database field of the page being displayed in place of `$Title`.
Variables can be chained together, and include arguments.
:::ss
$Foo
$Foo(param)
$Foo.Bar
These variables will call a method / field on the object and insert the returned value as a string into the template.
* `$Foo` will call `$obj->Foo()` (or the field `$obj->Foo`)
* `$Foo(param)` will call `$obj->Foo("param")`
* `$Foo.Bar` will call `$obj->Foo()->Bar()`
If a variable returns a string, that string will be inserted into the template. If the variable returns an object, then
the system will attempt to render the object through its' `forTemplate()` method. If the `forTemplate()` method has not
been defined, the system will return an error.
<div class="note" markdown="1">
For more detail around how variables are inserted and formatted into a template see
[Formating, Modifying and Casting Variables](casting)
</div>
Variables can come from your database fields, or custom methods you define on your objects.
**mysite/code/Page.php**
:::php
public function UsersIpAddress() {
return $this->request->getIP();
}
**mysite/code/Page.ss**
:::html
<p>You are coming from $UsersIpAddress.</p>
<div class="node" markdown="1">
Method names that begin with `get` will automatically be resolved when their prefix is excluded. For example, the above method call `$UsersIpAddress` would also invoke a method named `getUsersIpAddress()`.
</div>
The variable's that can be used in a template vary based on the object currently in [scope](#scope). Scope defines what
object the methods get called on. For the standard `Page.ss` template the scope is the current [api:Page_Controller]
class. This object gives you access to all the database fields on [api:Page_Controller], its corresponding [api:Page]
record and any subclasses of those two.
**mysite/code/Layout/Page.ss**
:::ss
$Title
// returns the page `Title` property
$Content
// returns the page `Content` property
## Conditional Logic
The simplest conditional block is to check for the presence of a value (does not equal 0, null, false).
:::ss
<% if $CurrentMember %>
<p>You are logged in as $CurrentMember.FirstName $CurrentMember.Surname.</p>
<% end_if %>
A conditional can also check for a value other than falsy.
:::ss
<% if $MyDinner == "kipper" %>
Yummy, kipper for tea.
<% end_if %>
<div class="notice" markdown="1">
When inside template tags variables should have a '$' prefix, and literals should have quotes.
</div>
Conditionals can also provide the `else` case.
:::ss
<% if $MyDinner == "kipper" %>
Yummy, kipper for tea
<% else %>
I wish I could have kipper :-(
<% end_if %>
`else_if` commands can be used to handle multiple `if` statements.
:::ss
<% if $MyDinner == "quiche" %>
Real men don't eat quiche
<% else_if $MyDinner == $YourDinner %>
We both have good taste
<% else %>
Can I have some of your chips?
<% end_if %>
### Negation
The inverse of `<% if %>` is `<% if not %>`.
:::ss
<% if not $DinnerInOven %>
I'm going out for dinner tonight.
<% end_if %>
### Boolean Logic
Multiple checks can be done using `||`, `or`, `&&` or `and`.
If *either* of the conditions is true.
:::ss
<% if $MyDinner == "kipper" || $MyDinner == "salmon" %>
yummy, fish for tea
<% end_if %>
If *both* of the conditions are true.
:::ss
<% if $MyDinner == "quiche" && $YourDinner == "kipper" %>
Lets swap dinners
<% end_if %>
### Inequalities
You can use inequalities like `<`, `<=`, `>`, `>=` to compare numbers.
:::ss
<% if $Number >= "5" && $Number <= "10" %>
Number between 5 and 10
<% end_if %>
## Includes
Within SilverStripe templates we have the ability to include other templates from the `template/Includes` directory
using the `<% include %>` tag.
:::ss
<% include SideBar %>
The `include` tag can be particularly helpful for nested functionality and breaking large templates up. In this example,
the include only happens if the user is logged in.
:::ss
<% if $CurrentMember %>
<% include MembersOnlyInclude %>
<% end_if %>
Includes can't directly access the parent scope when the include is included. However you can pass arguments to the
include.
:::ss
<% with $CurrentMember %>
<% include MemberDetails Top=$Top, Name=$Name %>
<% end_with %>
## Looping Over Lists
The `<% loop %>` tag is used to iterate or loop over a collection of items such as [api:DataList] or a [api:ArrayList]
collection.
:::ss
<h1>Children of $Title</h1>
<ul>
<% loop $Children %>
<li>$Title</li>
<% end_loop %>
</ul>
This snippet loops over the children of a page, and generates an unordered list showing the `Title` property from each
page.
<div class="notice" markdown="1">
$Title inside the loop refers to the Title property on each object that is looped over, not the current page like
the reference of `$Title` outside the loop.
This demonstrates the concept of [Scope](#scope). When inside a <% loop %> the scope of the template has changed to the
object that is being looped over.
</div>
### Altering the list
`<% loop %>` statements iterate over a [api:DataList] instance. As the template has access to the list object,
templates can call [api:DataList] methods.
Sort the list by a given field.
:::ss
<ul>
<% loop $Children.Sort(Title, ASC) %>
<li>$Title</li>
<% end_loop %>
</ul>
Limiting the number of items displayed.
:::ss
<ul>
<% loop $Children.Limit(10) %>
<li>$Title</li>
<% end_loop %>
</ul>
Reversing the loop.
:::ss
<ul>
<% loop $Children.Reverse %>
<li>$Title</li>
<% end_loop %>
</ul>
Filtering the loop
:::ss
<ul>
<% loop $Children.Filter('School', 'College') %>
<li>$Title</li>
<% end_loop %>
</ul>
Methods can also be chained
:::ss
<ul>
<% loop $Children.Filter('School', 'College').Sort(Score, DESC) %>
<li>$Title</li>
<% end_loop %>
</ul>
### Position Indicators
Inside the loop scope, there are many variables at your disposal to determine the current position in the list and
iteration.
* `$Even`, `$Odd`: Returns boolean, handy for zebra striping
* `$EvenOdd`: Returns a string, either 'even' or 'odd'. Useful for CSS classes.
* `$First`, `$Last`, `$Middle`: Booleans about the position in the list
* `$FirstLast`: Returns a string, "first", "last", or "". Useful for CSS classes.
* `$Pos`: The current position in the list (integer). Will start at 1.
* `$TotalItems`: Number of items in the list (integer)
:::ss
<ul>
<% loop $Children.Reverse %>
<% if First %>
<li>My Favourite</li>
<% end_if %>
<li class="$EvenOdd">Child $Pos of $TotalItems - $Title</li>
<% end_loop %>
</ul>
<div class="info" markdown="1">
A common task is to paginate your lists. See the [Pagination](how_tos/pagination) how to for a tutorial on adding
pagination.
</div>
### Modulus and MultipleOf
$Modulus and $MultipleOf can help to build column and grid layouts.
:::ss
// returns an int
$Modulus(value, offset)
// returns a boolean.
$MultipleOf(factor, offset)
<% loop $Children %>
<div class="column-{$Modulus(4)}">
...
</div>
<% end_loop %>
// returns <div class="column-3">, <div class="column-2">,
<div class="hint" markdown="1">
`$Modulus` is useful for floated grid CSS layouts. If you want 3 rows across, put $Modulus(3) as a class and add a
`clear: both` to `.column-1`.
</div>
$MultipleOf(value, offset) can also be utilized to build column and grid layouts. In this case we want to add a `<br>`
after every 3th item.
:::ss
<% loop $Children %>
<% if $MultipleOf(3) %>
<br>
<% end_if %>
<% end_loop %>
### Escaping
Sometimes you will have template tags which need to roll into one another. Use `{}` to contain variables.
:::ss
$Foopx // will returns "" (as it looks for a `Foopx` value)
{$Foo}px // returns "3px" (CORRECT)
Or when having a `$` sign in front of the variable such as displaying money.
:::ss
$$Foo // returns ""
${$Foo} // returns "$3"
You can also use a backslash to escape the name of the variable, such as:
:::ss
$Foo // returns "3"
\$Foo // returns "$Foo"
<div class="hint" markdown="1">
For more information on formatting and casting variables see [Formating, Modifying and Casting Variables](casting)
</div>
## Scope
In the `<% loop %>` section, we saw an example of two **scopes**. Outside the `<% loop %>...<% end_loop %>`, we were in
the scope of the top level `Page`. But inside the loop, we were in the scope of an item in the list (i.e the `Child`)
The scope determines where the value comes from when you refer to a variable. Typically the outer scope of a `Page.ss`
layout template is the [api:Page_Controller] that is currently being rendered.
When the scope is a `Page_Controller` it will automatically also look up any methods in the corresponding `Page` data
record. In the case of `$Title` the flow looks like
$Title --> [Looks up: Current Page_Controller and parent classes] --> [Looks up: Current Page and parent classes].
The list of variables you could use in your template is the total of all the methods in the current scope object, parent
classes of the current scope object, and any [api:Extension] instances you have.
### Navigating Scope
#### Up
When in a particular scope, `$Up` takes the scope back to the previous level.
:::ss
<h1>Children of '$Title'</h1>
<% loop $Children %>
<p>Page '$Title' is a child of '$Up.Title'</p>
<% loop $Children %>
<p>Page '$Title' is a grandchild of '$Up.Up.Title'</p>
<% end_loop %>
<% end_loop %>
Given the following structure, it will output the text.
My Page
|
+-+ Child 1
| |
| +- Grandchild 1
|
+-+ Child 2
Children of 'My Page'
Page 'Child 1' is a child of 'My Page'
Page 'Grandchild 1' is a grandchild of 'My Page'
Page 'Child 2' is a child of 'MyPage'
#### Top
While `$Up` provides us a way to go up one level of scope, `$Top` is a shortcut to jump to the top most scope of the
page. The previous example could be rewritten to use the following syntax.
:::ss
<h1>Children of '$Title'</h1>
<% loop $Children %>
<p>Page '$Title' is a child of '$Top.Title'</p>
<% loop $Children %>
<p>Page '$Title' is a grandchild of '$Top.Title'</p>
<% end_loop %>
<% end_loop %>
### With
The `<% with %>` tag lets you change into a new scope. Consider the following example:
:::ss
<% with $CurrentMember %>
Hello, $FirstName, welcome back. Your current balance is $Balance.
<% end_with %>
This is functionalty the same as the following:
:::ss
Hello, $CurrentMember.FirstName, welcome back. Yout current balance is $CurrentMember.Balance
Notice that the first example is much tidier, as it removes the repeated use of the `$CurrentMember` accessor.
Outside the `<% with %>.`, we are in the page scope. Inside it, we are in the scope of `$CurrentMember` object. We can
refer directly to properties and methods of the [api:Member] object. `$FirstName` inside the scope is equivalent to
`$CurrentMember.FirstName`.
## Comments
Using standard HTML comments is supported. These comments will be included in the published site.
:::ss
$EditForm <!-- Some public comment about the form -->
However you can also use special SilverStripe comments which will be stripped out of the published site. This is useful
for adding notes for other developers but for things you don't want published in the public html.
:::ss
$EditForm <%-- Some hidden comment about the form --%>
## Related
[CHILDREN]
## How to's
[CHILDREN How_Tos]
## API Documentation
* [api:SSViewer]
* [api:SS_TemplateManifest]

View File

@ -0,0 +1,409 @@
title: Common Variables
summary: Some of the common variables and methods your templates can use, including Menu, SiteConfig, and more.
# Common Variables
The page below describes a few of common variables and methods you'll see in a SilverStripe template. This is not an
exhaustive list. From your template you can call any method, database field, or relation on the object which is
currently in scope as well as its' subclasses or extensions.
Knowing what methods you can call can be tricky, but the first step is to understand the scope you're in. Scope is
explained in more detail on the [syntax](syntax#scope) page.
<div class="notice" markdown="1">
Want a quick way of knowing what scope you're in? Try putting `$ClassName` in your template. You should see a string
such as `Page` of the object that's in scope. The methods you can call on that object then are any functions, database
properties or relations on the `Page` class, `Page_Controller` class as well as anything from their subclasses **or**
extensions.
</div>
Outputting these variables is only the start, if you want to format or manipulate them before adding them to the template
have a read of the [Formating, Modifying and Casting Variables](casting) documentation.
<div class="alert" markdown="1">
Some of the following only apply when you have the `CMS` module installed. If you're using the `Framework` alone, this
functionality may not be included.
</div>
## Base Tag
:::ss
<head>
<% base_tag %>
..
</head>
The `<% base_tag %>` placeholder is replaced with the HTML base element. Relative links within a document (such as <img
src="someimage.jpg" />) will become relative to the URI specified in the base tag. This ensures the browser knows where
to locate your sites images and css files.
It renders in the template as `<base href="http://www.yoursite.com" /><!--[if lte IE 6]></base><![endif]-->`
<div class="alert" markdown="1">
A `<% base_tag %>` is nearly always required or assumed by SilverStripe to exist.
</div>
## CurrentMember
Returns the currently logged in [api:Member] instance, if there is one logged in.
:::ss
<% if $CurrentMember %>
Welcome Back, $CurrentMember.FirstName
<% end_if %>
## Title and Menu Title
:::ss
$Title
$MenuTitle
Most objects within SilverStripe will respond to `$Title` (i.e they should have a `Title` database field or at least a
`getTitle()` method).
The CMS module in particular provides two fields to label a page: `Title` and `MenuTitle`. `Title` is the title
displayed on the web page, while `MenuTitle` can be a shorter version suitable for size-constrained menus.
<div class="notice" markdown="1">
If `MenuTitle` is left blank by the CMS author, it'll just default to the value in `Title`.
</div>
## Page Content
:::ss
$Content
It returns the database content of the `Content` property. With the CMS Module, this is the value of the WYSIWYG editor
but it is also the standard for any object that has a body of content to output.
<div class="info" markdown="1">
Please note that this database content can be `versioned`, meaning that draft content edited in the CMS can be different
from published content shown to your website visitors. In templates, you don't need to worry about this distinction.
The `$Content` variable contains the published content by default,and only preview draft content if explicitly
requested (e.g. by the "preview" feature in the CMS) (see the [versioning documentation](/../model/versioning) for
more details).
</div>
### SiteConfig: Global settings
<div class="notice" markdown="1">
`SiteConfig` is a module that is bundled with the `CMS`. If you wish to include `SiteConfig` in your framework only
web pages. You'll need to install it via `composer`.
</div>
:::ss
$SiteConfig.Title
The [SiteConfig](../configuration/siteconfig) object allows content authors to modify global data in the CMS, rather
than PHP code. By default, this includes a Website title and a Tagline.
`SiteConfig` can be extended to hold other data, for example a logo image which can be uploaded through the CMS or
global content such as your footer content.
## Meta Tags
The `$MetaTags` placeholder in a template returns a segment of HTML appropriate for putting into the `<head>` tag. It
will set up title, keywords and description meta-tags, based on the CMS content and is editable in the 'Meta-data' tab
on a per-page basis.
<div class="notice" markdown="1">
If you dont want to include the title tag use `$MetaTags(false)`.
</div>
By default `$MetaTags` renders:
:::ss
<title>Title of the Page</title>
<meta name="generator" http-equiv="generator" content="SilverStripe 3.0" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
`$MetaTags(false)` will render
:::ss
<meta name="generator" http-equiv="generator" content="SilverStripe 3.0" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
If using `$MetaTags(false)` we can provide a more custom `title`.
:::ss
$MetaTags(false)
<title>$Title - Bob's Fantasy Football</title>
## Links
:::ss
<a href="$Link">..</a>
All objects that could be accessible in SilverStripe should define a `Link` method and an `AbsoluteLink` method. Link
returns the relative URL for the object and `AbsoluteLink` outputs your full website address along with the relative
link.
:::ss
$Link
<!-- returns /about-us/offices/ -->
$AbsoluteLink
<!-- returns http://yoursite.com/about-us/offices/ -->
### Linking Modes
:::ss
$LinkingMode
When looping over a list of `SiteTree` instances through a `<% loop $Menu %>` or `<% loop $Children %>`, `$LinkingMode`
will return context about the page relative to the currently viewed page. It can have the following values:
* `link`: You are neither on this page nor in this section.
* `current`: You are currently on this page.
* `section`: The current page is a child of this menu item, so the current "section"
For instance, to only show the menu item linked if it's the current one:
:::ss
<% if $LinkingMode = current %>
$Title
<% else %>
<a href="$Link">$Title</a>
<% end_if %>
`$LinkingMode` is reused for several other variables and utility functions.
* `$LinkOrCurrent`: Determines if the item is the current page. Returns "link" or "current" strings.
* `$LinkOrSection`: Determines if the item is in the current section, so in the path towards the current page. Useful
for menus which you only want to show a second level menu when you are on that page or a child of it. Returns "link"
or "section" strings.
* `$InSection(page-url)`: This if block will pass if we're currently on the page-url page or one of its children.
:::ss
<% if $InSection(about-us) %>
<p>You are viewing the about us section</p>
<% end_if %>
### URLSegment
This returns the part of the URL of the page you're currently on. For example on the `/about-us/offices/` web page the
`URLSegment` will be `offices`. `URLSegment` cannot be used to generate a link since it does not output the full path.
It can be used within templates to generate anchors or other CSS classes.
:::ss
<div id="section-$URLSegment">
</div>
<!-- returns <div id="section-offices"> -->
## ClassName
Returns the class of the current object in [scope](syntax#scope) such as `Page` or `HomePage`. The `$ClassName` can be
handy for a number of uses. A common use case is to add to your `<body>` tag to influence CSS styles and JavaScript
behavior based on the page type used:
:::ss
<body class="$ClassName">
<!-- returns <body class="HomePage">, <body class="BlogPage"> -->
## Children Loops
:::ss
<% loop $Children %>
<% end_loop %>
Will loop over all Children records of the current object context. Children are pages that sit under the current page in
the `CMS` or a custom list of data. This originates in the `Versioned` extension's `getChildren` method.
<div class="alert" markdown="1">
For doing your website navigation most likely you'll want to use `$Menu` since its independent of the page
context.
</div>
### ChildrenOf
:::ss
<% loop $ChildrenOf(<my-page-url>) %>
<% end_loop %>
Will create a list of the children of the given page, as identified by its `URLSegment` value. This can come in handy
because it's not dependent on the context of the current page. For example, it would allow you to list all staff member
pages underneath a "staff" holder on any page, regardless if its on the top level or elsewhere.
### AllChildren
Content authors have the ability to hide pages from menus by un-selecting the `ShowInMenus` checkbox within the CMS.
This option will be honored by `<% loop $Children %>` and `<% loop $Menu %>` however if you want to ignore the user
preference, `AllChildren` does not filter by `ShowInMenus`.
:::ss
<% loop $AllChildren %>
...
<% end_loop %>
### Menu Loops
:::ss
<% loop $Menu(1) %>
...
<% end_loop %>
`$Menu(1)` returns the top-level menu of the website. You can also create a sub-menu using `$Menu(2)`, and so forth.
<div class="notice" markdown="1">
Pages with the `ShowInMenus` property set to `false` will be filtered out.
</div>
## Access to a specific Page
:::ss
<% with $Page(my-page) %>
$Title
<% end_with %>
Page will return a single page from site, looking it up by URL.
## Access to Parent and Level Pages
### Level
:::ss
<% with $Level(1) %>
$Title
<% end_with %>
Will return a page in the current path, at the level specified by the numbers. It is based on the current page context,
looking back through its parent pages. `Level(1)` being the top most level.
For example, imagine you're on the "bob marley" page, which is three levels in: "about us > staff > bob marley".
* `$Level(1).Title` would return "about us"
* `$Level(2).Title` would return "staff"
* `$Level(3).Title` would return "bob marley"
### Parent
:::ss
<!-- given we're on 'Bob Marley' in "about us > staff > bob marley" -->
$Parent.Title
<!-- returns 'staff' -->
$Parent.Parent.Title
<!-- returns 'about us' -->
## Navigating Scope
### Me
`$Me` outputs the current object in scope. This will call the `forTemplate` of the object.
:::ss
$Me
### Up
When in a particular scope, `$Up` takes the scope back to the previous level.
:::ss
<h1>Children of '$Title'</h1>
<% loop $Children %>
<p>Page '$Title' is a child of '$Up.Title'</p>
<% loop $Children %>
<p>Page '$Title' is a grandchild of '$Up.Up.Title'</p>
<% end_loop %>
<% end_loop %>
Given the following structure, it will output the text.
My Page
|
+-+ Child 1
| |
| +- Grandchild 1
|
+-+ Child 2
Children of 'My Page'
Page 'Child 1' is a child of 'My Page'
Page 'Grandchild 1' is a grandchild of 'My Page'
Page 'Child 2' is a child of 'MyPage'
### Top
While `$Up` provides us a way to go up one level of scope, `$Top` is a shortcut to jump to the top most scope of the
page. The previous example could be rewritten to use the following syntax.
:::ss
<h1>Children of '$Title'</h1>
<% loop $Children %>
<p>Page '$Title' is a child of '$Top.Title'</p>
<% loop $Children %>
<p>Page '$Title' is a grandchild of '$Top.Title'</p>
<% end_loop %>
<% end_loop %>
## Breadcrumbs
Breadcrumbs are the path of pages which need to be taken to reach the current page, and can be a great navigation aid
for website users.
While you can achieve breadcrumbs through the `$Level(<level>)` control manually, there's a nicer shortcut: The
`$Breadcrumbs` variable.
:::ss
$Breadcrumbs
By default, it uses the template defined in `cms/templates/BreadcrumbsTemplate.ss`
:::ss
<% if $Pages %>
<% loop $Pages %>
<% if $Last %>$Title.XML<% else %><a href="$Link">$MenuTitle.XML</a> &raquo;<% end_if %>
<% end_loop %>
<% end_if %>
<div class="info" markdown="1">
To customize the markup that the `$Breadcrumbs` generates, copy `cms/templates/BreadcrumbsTemplate.ss` to
`mysite/templates/BreadcrumbsTemplate.ss`, modify the newly copied template and flush your SilverStripe cache.
</div>
## Forms
:::ss
$Form
A page will normally contain some content and potentially a form of some kind. For example, the log-in page has a the
SilverStripe log-in form. If you are on such a page, the `$Form` variable will contain the HTML content of the form.
Placing it just below `$Content` is a good default.
You can add your own forms by implementing new form instances (see the [Forms tutorial](../tutorials/forms)).
## Related
* [Casting and Formating Variables](casting)
* [Template Inheritance](template_inheritance)
## 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.

View File

@ -0,0 +1,192 @@
title: Requirements
summary: How to include and require other assets in your templates such as javascript and CSS files.
# Requirements
The requirements class takes care of including CSS and JavaScript into your applications. This is preferred to hard
coding any references in the `<head>` tag of your template, as it enables a more flexible handling through the
[api:Requirements] class.
## Template Requirements API
**mysite/templates/Page.ss**
:::ss
<% require css("cms/css/TreeSelector.css") %>
<% require themedCSS("TreeSelector") %>
<% require javascript("cms/javascript/LeftAndMain.js") %>
<div class="alert" markdown="1">
Requiring assets from the template is restricted compared to the PHP API.
</div>
## PHP Requirements API
It is common practice to include most Requirements either in the *init()*-method of your [controller](../controller), or
as close to rendering as possible (e.g. in `[api:FormField]`.
:::php
<?php
class MyCustomController extends Controller {
public function init() {
parent::init();
Requirements::javascript("cms/javascript/LeftAndMain.js");
Requirements::css("cms/css/TreeSelector.css");
}
}
### CSS Files
:::php
Requirements::css($path, $media);
If you're using the CSS method a second argument can be used. This argument defines the 'media' attribute of the
`<link>` element, so you can define 'screen' or 'print' for example.
:::php
Requirements::css("cms/css/TreeSelector.css", "screen,projection");
### Javascript Files
:::php
Requirements::javascript($path);
A variant on the inclusion of custom javascript is the inclusion of *templated* javascript. Here, you keep your
JavaScript in a separate file and instead load, via search and replace, several PHP-generated variables into that code.
:::php
$vars = array(
"EditorCSS" => "cms/css/editor.css",
);
Requirements::javascriptTemplate("cms/javascript/editor.template.js", $vars);
In this example, `editor.template.js` is expected to contain a replaceable variable expressed as `$EditorCSS`.
### Custom Inline CSS or Javascript
You can also quote custom script directly. This may seem a bit ugly, but is useful when you need to transfer some kind
of 'configuration' from the database in a raw format. You'll need to use the `heredoc` syntax to quote JS and CSS,
this is generally speaking the best way to do these things - it clearly marks the copy as belonging to a different
language.
:::php
Requirements::customScript(<<<JS
alert("hi there");
JS
);
Requirements::customCSS(<<<CSS
.tree li.$className {
background-image: url($icon);
}
CSS
);
## Combining Files
You can concatenate several CSS or javascript files into a single dynamically generated file. This increases performance
by reducing HTTP requests.
:::php
Requirements::combine_files(
'foobar.js',
array(
'mysite/javascript/foo.js',
'mysite/javascript/bar.js',
)
);
<div class="alert" markdown='1'>
To make debugging easier in your local environment, combined files is disabled when running your application in `dev`
mode.
</div>
By default it stores the generated file in the assets/ folder, but you can configure this by pointing the
`Requirements.combined_files_folder` configuration setting to a specific folder.
**mysite/_config/app.yml**
:::yml
Requirements:
combined_files_folder: '_combined'
<div class="info" markdown='1'>
If SilverStripe doesn't have permissions on your server to write these files it will default back to including them
individually. SilverStripe **will not** rewrite your paths within the file.
</div>
You can also combine CSS files into a media-specific stylesheets as you would with the `Requirements::css` call - use
the third paramter of the `combine_files` function:
:::php
$printStylesheets = array(
"$themeDir/css/print_HomePage.css",
"$themeDir/css/print_Page.css",
);
Requirements::combine_files('print.css', $printStylesheets, 'print');
## Clearing assets
:::php
Requirements::clear();
Clears all defined requirements. You can also clear specific requirements.
:::php
Requirements::clear(THIRDPARTY_DIR.'/prototype.js');
<div class="alert" markdown="1">
Depending on where you call this command, a Requirement might be *re-included* afterwards.
</div>
## Blocking
Requirements can also be explicitly blocked from inclusion, which is useful to avoid conflicting JavaScript logic or
CSS rules. These blocking rules are independent of where the `block()` call is made. It applies both for already
included requirements, and ones included after the `block()` call.
One common example is to block the core `jquery.js` added by various form fields and core controllers, and use a newer
version in a custom location. This assumes you have tested your application with the newer version.
:::php
Requirements::block(THIRDPARTY_DIR . '/jquery/jquery.js');
<div class="alert" markdown="1">
The CMS also uses the `Requirements` system, and its operation can be affected by `block()` calls. Avoid this by
limiting the scope of your blocking operations, e.g. in `init()` of your controller.
</div>
## Inclusion Order
Requirements acts like a stack, where everything is rendered sequentially in the order it was included. There is no way
to change inclusion-order, other than using *Requirements::clear* and rebuilding the whole set of requirements.
<div class="alert" markdown="1">
Inclusion order is both relevant for CSS and Javascript files in terms of dependencies, inheritance and overlays - be
careful when messing with the order of requirements.
</div>
## Javascript placement
By default, SilverStripe includes all Javascript files at the bottom of the page body, unless there's another script
already loaded, then, it's inserted before the first `<script>` tag. If this causes problems, it can be configured.
**mysite/_config/app.yml**
:::yml
Requirements:
write_js_to_body: true
force_js_to_bottom: true
`Requirements.force_js_to_bottom`, will force SilverStripe to write the Javascript to the bottom of the page body, even
if there is an earlier script tag.
## API Documentation
* [api:Requirements]

View File

@ -0,0 +1,89 @@
title: Rendering data to a template
summary: Call and render SilverStripe templates manually.
# Rendering data to a template
Templates do nothing on their own. Rather, they are used to render a particular object. All of the `<% if %>`,
`<% loop %>` and other variables are methods or parameters that are called on the current object in
[scope](syntax#scope). All that is necessary is that the object is an instance of [api:ViewableData] (or one of its
subclasses).
The following will render the given data into a template. Given the template:
**mysite/templates/Coach_Message.ss**
:::ss
<strong>$Name</strong> is the $Role on our team.
Our application code can render into that view using `renderWith`. This method is called on the [api:ViewableData]
instance with a template name or an array of templates to render.
**mysite/code/Page.php**
:::php
$arrayData = new ArrayData(array(
'Name' => 'John',
'Role' => 'Head Coach'
));
echo $arrayData->renderWith('Coach_Message');
// returns "<strong>John</strong> is the Head Coach on our team."
<div class="info" markdown="1">
Most classes in SilverStripe you want in your template extend `ViewableData` and allow you to call `renderWith`. This
includes [api:Controller], [api:FormField] and [api:DataObject] instances.
</div>
:::php
$controller->renderWith(array("MyController", "MyBaseController"));
Member::currentUser()->renderWith('Member_Profile');
`renderWith` can be used to override the default template process. For instance, to provide an ajax version of a
template.
:::php
<?php
class Page_Controller extends ContentController {
private static $allowed_actions = array('iwantmyajax');
public function iwantmyajax() {
if(Director::is_ajax()) {
return $this->renderWith("AjaxTemplate");
} else {
return $this->httpError(404);
}
}
}
Any data you want to render into the template that does not extend `ViewableData` should be wrapped in an object that
does, such as `ArrayData` or `ArrayList`.
:::php
<?php
class Page_Controller extends ContentController {
..
public function iwantmyajax() {
if(Director::is_ajax()) {
$experience = new ArrayList();
$experience->push(new ArrayData(array(
'Title' => 'First Job'
)));
return $this->customize(new ArrayData(array(
'Name' => 'John',
'Role' => 'Head Coach',
'Experience' => $experience
))->renderWith("AjaxTemplate");
} else {
return $this->httpError(404);
}
}
}

View File

@ -0,0 +1,102 @@
title: Template Inheritance
summary: Override and extend module and core markup templates from your application code.
# Template Inheritance
Bundled within SilverStripe are default templates for any markup the framework outputs for things like Form templates,
Emails or RSS Feeds. These templates are provided to make getting your site up and running quick with sensible defaults
but it's easy to replace and customize SilverStripe (and add-on's) by providing custom templates in your own
`mysite/templates` folder or in your `themes/your_theme/templates` folder.
Take for instance the `GenericEmail` template in SilverStripe. This is the HTML default template that any email created
in SilverStripe is rendered with. It's bundled in the core framework at `framework/templates/email/GenericEmail.ss`.
Instead of editing that file to provide a custom template for your application, simply define a template of the same
name in the `mysite/templates/email` folder or in the `themes/your_theme/templates/email` folder if you're using themes.
**mysite/templates/email/GenericEmail.ss**
:::ss
$Body
<p>Thanks from Bob's Fantasy Football League.</p>
All emails going out of our application will have the footer `Thanks from Bob's Fantasy Football Leaguee` added.
<div class="alert" markdown="1">
As we've added a new file, make sure you flush your SilverStripe cache by visiting `http://yoursite.com/?flush=1`
</div>
Template inheritance works on more than email templates. All files within the `templates` directory including `includes`,
`layout` or anything else from core (or add-on's) template directory can be overridden by being located inside your
`mysite/templates` directory. SilverStripe keeps an eye on what templates have been overridden and the location of the
correct template through a [api:SS_TemplateManifest].
## Template Manifest
The location of each template and the hierarchy of what template to use is stored within a [api:SS_TemplateManifest]
instance. This is a serialized object containing a map of template names, paths and other meta data for each template
and is cached in your applications `TEMP_FOLDER` for performance. For SilverStripe to find the `GenericEmail` template
it does not check all your `template` folders on the fly, it simply asks the `manifest`.
The manifest is created whenever you flush your SilverStripe cache by appending `?flush=1` to any SilverStripe URL. For
example by visiting `http://yoursite.com/?flush=1`. When your include the `flush=1` flag, the manifest class will search
your entire project for the appropriate `.ss` files located in `template` directory and save that information for later.
It will each and prioritize templates in the following priority:
1. mysite (or other name given to site folder)
2. themes
3. modules
4. framework.
<div class="warning">
Whenever you add or remove template files, rebuild the manifest by visiting `http://yoursite.com/?flush=1`. You can
flush the cache from any page, (.com/home?flush=1, .com/admin?flush=1, etc.). Flushing the cache can be slow, so you
only need to do it when you're developing new templates.
</div>
## Nested Layouts through `$Layout`
SilverStripe has basic support for nested layouts through a fixed template variable named `$Layout`. It's used for
storing top level template information separate to individual page layouts.
When `$Layout` is found within a root template file (one in `templates`), SilverStripe will attempt to fetch a child
template from the `templates/Layout` directory. It will do a full sweep of your modules, core and custom code as it
would if it was looking for a new root template.
This is better illustrated with an example. Take for instance our website that has two page types `Page` and `HomePage`.
Our site looks mostly the same across both templates with just the main content in the middle changing. The header,
footer and navigation will remain the same and we don't want to replicate this work across more than one spot. The
`$Layout` function allows us to define the child template area which can be overridden.
**mysite/templates/Page.ss**
<html>
<head>
..
</head>
<body>
<% include Header %>
<% include Navigation %>
$Layout
<% include Footer %>
</body>
**mysite/templates/Layout/Page.ss**
<p>You are on a $Title page</p>
$Content
**mysite/templates/Layout/HomePage.ss**
<h1>This is the homepage!</h1>
<blink>Hi!</blink>

View File

@ -0,0 +1,67 @@
title: Themes
summary: What makes up a SilverStripe Theme. How to install one or write your own theme.
# Themes
Themes can be used to kick start your SilverStripe projects, can be stored outside of your application code and your
application can provide multiple unique themes (i.e a mobile theme).
## Downloading
Head to the [ Themes ](http://www.silverstripe.org/themes) area of the website to check out the range of themes the
community has built. Each theme has a page with links you can use to preview and download it. The theme is provided
as a .tar.gz file.
## Installation
### Manually
Unpack the contents of the zip file you download into the `themes` directory in your SilverStripe installation. The
theme should be accessible at `themes/theme_name`.
### Via Composer
If a theme has `composer` support you can require it directly through `composer`.
:::bash
composer require "author/theme_name" "dev/master"
<div class="alert" markdown="1">
As you've added new files to your SilverStripe installation, make sure you clear the SilverStripe cache by appending
`?flush=1` to your website URL (e.g http://yoursite.com/?flush=1).
</div>
After installing the files through either method, update the current theme in SilverStripe. This can be done by
either altering the `SSViewer.theme` setting in a [config.yml](../configuration) or by changing the current theme in
the Site Configuration panel (http://yoursite.com/admin/settings)
**mysite/_config/app.yml**
:::yml
SSViewer:
theme: theme_name
## Developing your own theme
A `theme` within SilverStripe is simply a collection of templates and other front end assets such as javascript and css.
located within the `themes` directory.
![themes:basicfiles.gif](//_images/basicfiles.gif)
## Submitting your theme to SilverStripe
If you want to submit your theme to the SilverStripe directory then check
* You should ensure your templates are well structured, modular and commented so it's easy for other people to customize
* Templates should not contain text inside images and all images provided must be open source and not break any
copyright or license laws. This includes any icons your template uses.
* A theme does not include any PHP files. Only CSS, HTML, Images and javascript.
Your theme file must be in a .tar.gz format. A useful tool for this is - [7 Zip](http://www.7-zip.org/). Using 7Zip you
must select the your_theme folder and Add to archive, select TAR and create. Then after you have the TAR file right
click it -> Add to Archive (again) -> Then use the archive format GZIP.
## Links
* [Themes Listing on silverstripe.org](http://silverstripe.org/themes)
* [Themes Forum on silverstripe.org](http://www.silverstripe.org/themes-2/)
* [Themes repository on github.com](http://github.com/silverstripe-themes)

View File

@ -0,0 +1,39 @@
title: Caching
summary: Reduce rending time with cached templates and understand the limitations of the ViewableData object caching.
# Caching
## Object caching
All functions that provide data to templates must have no side effects, as the value is cached after first access. For
example, this controller method will not behave as you might imagine.
:::php
private $counter = 0;
public function Counter() {
$this->counter += 1;
return $this->counter;
}
:::ss
$Counter, $Counter, $Counter
// returns 1, 1, 1
When we render `$Counter` to the template we would expect the value to increase and output `1, 2, 3`. However, as
`$Counter` is cached at the first access, the value of `1` is saved.
## Partial caching
Partial caching is a feature that allows the caching of just a portion of a page. Instead of fetching the required data
from the database to display, the contents of the area are fetched from the `TEMP_FOLDER` file-system pre-rendered and
ready to go. More information about Partial caching is in the [Performance](../performance) guide.
:::ss
<% cached 'MyCachedContent', LastEdited %>
$Title
<% end_cached %>

View File

@ -0,0 +1,30 @@
title: Translations
summary: Definition of the syntax for writing i18n compatible templates.
# Translations
Translations are easy to use with a template, and give access to SilverStripe's translation facilities. Here is an
example:
<%t Foo.BAR 'Bar' %>
<%t Member.WELCOME 'Welcome {name} to {site}' name=$Member.Name site="Foobar.com" %>
`Member.WELCOME` is an identifier in the translation system, for which different translations may be available. This
string may include named placeholders, in braces.
`'Welcome {name} to {site}'` is the default string used, if there is no translation for Member.WELCOME in the current
locale. This contains named placeholders.
`name=$Member.Name` assigns a value to the named placeholder `name`. This value is substituted into the translation
string wherever `{name}` appears in that string. In this case, it is assigning a value from a property `Member.Name`
`site="Foobar.com"` assigns a literal value to another named placeholder, `site`.
## Related
* [i18n](../i18n)
## API Documentation
* [api:i18n]

View File

@ -0,0 +1,107 @@
title: Formating, Modifying and Casting Variables
summary: Information on casting, security, modifying data before it's displayed to the user and how to format data within the template.
# Formatting and Casting
All objects that are being rendered in a template should be a [api:ViewableData] instance such as `DataObject`,
`DBField` or `Controller`. From these objects, the template can include any method from the object in
[scope](syntax#scope).
For instance, if we provide a [api:HtmlText] instance to the template we can call the `FirstParagraph` method. This will
output the result of the [api:HtmlText::FirstParagraph] method to the template.
**mysite/code/Page.ss**
:::ss
$Content.FirstParagraph
<!-- returns the result of HtmlText::FirstParagragh() -->
$LastEdited.Format("d/m/Y")
<!-- returns the result of SS_Datetime::Format("d/m/Y") -->
Any public method from the object in scope can be called within the template. If that method returns another
`ViewableData` instance, you can chain the method calls.
:::ss
$Content.FirstParagraph.NoHTML
<!-- "First Paragraph" -->
<p>Copyright {$Now.Year}</p>
<!-- "Copyright 2014" -->
<div class="$URLSegment.LowerCase">
<!-- <div class="about-us"> -->
<div class="notice" markdown="1">
See the API documentation for [api:HtmlText], [api:StringField], [api:Text] for all the methods you can use to format
your text instances. For other objects such as [api:SS_Datetime] objects see their respective API documentation pages.
</div>
## forTemplate
When rendering an object to the template such as `$Me` the `forTemplate` method is called. This method can be used to
provide default template for an object.
**mysite/code/Page.php**
:::php
<?php
class Page extends SiteTree {
public function forTemplate() {
return "Page: ". $this->Title;
}
}
**mysite/templates/Page.ss**
:::ss
$Me
<!-- returns Page: Home -->
## Casting
Methods which return data to the template should either return an explicit object instance describing the type of
content that method sends back, or, provide a type in the `$casting` array for the object. When rendering that method
to a template, SilverStripe will ensure that the object is wrapped in the correct type and values are safely escaped.
:::php
<?php
class Page extends SiteTree {
private static $casting = array(
'MyCustomMethod' => 'HTMLText'
);
public function MyCustomMethod() {
return "<h1>This is my header</h1>";
}
}
When calling `$MyCustomMethod` SilverStripe now has the context that this method will contain HTML and escape the data
accordingly.
<div class="note" markdown="1">
By default, all content without a type explicitly defined in a `$casting` array will be assumed to be `Text` content
and HTML characters encoded.
</div>
## Escaping
Properties are usually auto-escaped in templates to ensure consistent representation, and avoid format clashes like
displaying un-escaped ampersands in HTML. By default, values are escaped as `XML`, which is equivalent to `HTML` for
this purpose.
<div class="note" markdown="1">
There's some exceptions to this rule, see the ["security" guide](../security).
</div>
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.
:::ss
$Content.XML
// transforms e.g. "<em>alert</em>" to "&lt;em&gt;alert&lt;/em&gt;"

View File

@ -0,0 +1,34 @@
title: How to Create a Navigation Menu
# How to Create a Navigation Menu
In this how-to, we'll create a simple menu which you can use as the primary navigation for your website. This outputs a
top level menu with a nested second level using the `Menu` loop and a `Children` loop.
**mysite/templates/Page.ss**
:::ss
<ul>
<% loop $Menu(1) %>
<li>
<a href="$Link" title="Go to the $Title page" class="$LinkingMode">
$MenuTitle
</a>
<% if $LinkOrSection == section %>
<% if $Children %>
<li><ul class="secondary">
<% loop $Children %>
<li class="$LinkingMode"><a href="$Link">$MenuTitle</a></li>
<% end_loop %>
</ul></li>
<% end_if %>
<% end_if %>
</li>
<% end_loop %>
</ul>
## Related
* [Template Syntax](../syntax)
* [Common Variables](../command_variables)

View File

@ -0,0 +1,110 @@
title: How to Create a Paginated List
# How to Create a Paginated List
In order to create a paginated list, create a method on your controller that first creates a `SS_List` that contains
all your record, then wraps it in a [api:PaginatedList] object. The `PaginatedList` object should also passed the
[api:SS_HTTPRequest] object so it can read the current page information from the "?start=" GET var.
The `PaginatedList` will automatically set up query limits and read the request for information.
**mysite/code/Page.php**
:::php
/**
* Returns a paginated list of all pages in the site.
*/
public function PaginatedPages() {
$list = Page::get();
return new PaginatedList($list, $this->request);
}
<div class="notice" markdown="1">
Note that the concept of "pages" used in pagination does not necessarily mean that we're dealing with `Page` classes,
it's just a term to describe a sub-collection of the list.
</div>
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:
**mysite/templates/Page.ss**
:::ss
<ul>
<% loop $PaginatedPages %>
<li><a href="$Link">$Title</a></li>
<% end_loop %>
</ul>
By default this will display 10 pages at a time. The next step is to add pagination controls below this so the user can
switch between pages:
**mysite/templates/Page.ss**
:::ss
<% if $PaginatedPages.MoreThanOnePage %>
<% if $PaginatedPages.NotFirstPage %>
<a class="prev" href="$PaginatedPages.PrevLink">Prev</a>
<% end_if %>
<% loop $PaginatedPages.Pages %>
<% if $CurrentBool %>
$PageNum
<% else %>
<% if $Link %>
<a href="$Link">$PageNum</a>
<% else %>
...
<% end_if %>
<% end_if %>
<% end_loop %>
<% if $PaginatedPages.NotLastPage %>
<a class="next" href="$PaginatedPages.NextLink">Next</a>
<% end_if %>
<% end_if %>
If there is more than one page, this block will render a set of pagination controls in the form
`[1] ... [3] [4] [5] [6] [7] ... [10]`.
## Paginating Custom Lists
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
when using custom lists.
:::php
$myPreLimitedList = Page::get()->limit(10);
$pages = new PaginatedList($myPreLimitedList, $this->request);
$pages->setLimitItems(false);
## Setting the limit of items
:::php
$pages = new PaginatedList(Page::get(), $this->request);
$pages->setPageLength(25);
## Template Variables
| Variable | Description |
| -------- | -------- |
| `$MoreThanOnePage` | Returns true when we have a multi-page list, restricted with a limit. |
| `$NextLink`, `$PrevLink` | They will return blank if there's no appropriate page to go to, so `$PrevLink` will return blank when you're on the first page. |
| `$CurrentPage` | Current page iterated on. |
| `$TotalPages` | The actual (limited) list of records, use in an inner loop |
| `$TotalItems` | This returns the total number of items across all pages. |
| `$Pages` | Total number of pages. |
| `$PageNum` | Page number, starting at 1 (within `$Pages`) |
| `$Link` | Links to the current controller URL, setting this page as current via a GET parameter |
| `$CurrentBool` | Returns true if you're currently on that page |
## API Documentation
* [api:PaginatedList]

View File

@ -0,0 +1,46 @@
title: Disable Anchor Rewriting
# Disable Anchor Rewriting
Anchor links are links with a "#" in them. A frequent use-case is to use anchor links to point to different sections of
the current page. For example, we might have this in our template:
:::ss
<ul>
<li><a href="#section1">Section 1</a></li>
<li><a href="#section2">Section 2</a></li>
</ul>
Things get tricky because of we have set our `<base>` tag to point to the root of the site. So, when you click the
first link you will be sent to http://yoursite.com/#section1 instead of http://yoursite.com/my-long-page/#section1
In order to prevent this situation, the SSViewer template renderer will automatically rewrite any anchor link that
doesn't specify a URL before the anchor, prefixing the URL of the current page. For our example above, the following
would be created in the final HTML
:::ss
<ul>
<li><a href="my-long-page/#section1">Section 1</a></li>
<li><a href="my-long-page/#section2">Section 2</a></li>
</ul>
There are cases where this can be unhelpful. HTML anchors created from Ajax responses are the most common. In these
situations, you can disable anchor link rewriting by setting the `SSViewer.rewrite_hash_links` configuration value to
`false`.
**mysite/_config/app.yml**
SSViewer:
rewrite_hash_links: false
Or, a better way is to call this just for the rendering phase of this particular file:
:::php
public function RenderCustomTemplate() {
Config::inst()->update('SSViewer', 'rewrite_hash_links', false);
$html = $this->renderWith('MyCustomTemplate');
Config::inst()->update('SSViewer', 'rewrite_hash_links', true);
return $html;
}

View File

@ -0,0 +1,17 @@
title: Templates and Views
summary: This guide showcases the SilverStripe template engine and learn how to build your own themes.
introduction: SilverStripe comes with it's own templating engine. This guide walks you through the features of the template engine, how to create custom templates and ways to customize your data output.
Most of what will be public on your website comes from template files that are defined in SilverStripe. Either in the
core framework, the modules or themes you install, and your own custom templates.
SilverStripe templates are simple text files that have `.ss` extension. They can contain any markup language (e.g HTML,
XML, JSON..) and are processed to add features such as `$Var` to output variables and logic controls like
`<% if $Var %>`. In this guide we'll look at the syntax of the custom template engine [api:SSViewer] and how to render
templates from your controllers.
[CHILDREN Exclude=How_Tos]
## How to's
[CHILDREN Folder=How_Tos]

View File

@ -0,0 +1,177 @@
title: Introduction to a Controller
summary: A brief look at the definition of a Controller, creating actions and how to respond to requests.
# Introduction to Controllers
The following example is for a simple [api:Controller] class. When building off the SilverStripe Framework you will
subclass the base `Controller` class.
**mysite/code/controllers/TeamController.php**
:::php
<?php
class TeamController extends Controller {
private static $allowed_actions = array(
'players',
'index'
);
public function index(SS_HTTPRequest $request) {
// ..
}
public function players(SS_HTTPRequest $request) {
print_r($request->allParams());
}
}
## Routing
We need to define the URL that this controller can be accessed on. In our case, the `TeamsController` should be visible
at http://yoursite.com/teams/ and the `players` custom action is at http://yoursite.com/team/players/.
<div class="info" markdown="1">
If you're using the `cms` module with and dealing with `Page` objects then for your custom `Page Type` controllers you
would extend `ContentController` or `Page_Controller`. You don't need to define the routes value as the `cms` handles
routing.
</div>
<div class="alert" markdown="1">
Make sure that after you have modified the `routes.yml` file, that you clear your SilverStripe caches using `flush=1`.
</div>
**mysite/_config/routes.yml**
:::yml
---
Name: mysiteroutes
After: framework/routes#coreroutes
---
Director:
rules:
'teams//$Action/$ID/$Name': 'TeamController'
For more information about creating custom routes, see the [Routing](routing) documentation.
## Actions
Controllers respond by default to an `index` method. You don't need to define this method (as it's assumed) but you
can override the `index()` response to provide custom data back to the [Template and Views](../templates).
<div class="notice" markdown="1">
It is standard in SilverStripe for your controller actions to be `lowercasewithnospaces`
</div>
Action methods can return one of four main things:
* an array. In this case the values in the array are available in the templates and the controller completes as
usual by returning a [api:SS_HTTPResponse] with the body set to the current template.
* `HTML`. SilverStripe will wrap the `HTML` into a `SS_HTTPResponse` and set the status code to 200.
* an [api:SS_HTTPResponse] containing a manually defined `status code` and `body`.
* an [api:SS_HTTPResponse_Exception]. A special type of response which indicates a error. By returning the
exception, the execution pipeline can adapt and display any error handlers.
:::php
**mysite/code/controllers/TeamController.php**
/**
* Return some additional data to the current response that is waiting to go out, this makes $Title set to
* 'MyTeamName' and continues on with generating the response.
*/
public function index(SS_HTTPRequest $request) {
return array(
'Title' => 'My Team Name'
);
}
/**
* We can manually create a response and return that to ignore any previous data.
*/
public function someaction(SS_HTTPRequest $request) {
$this->response = new SS_HTTPResponse();
$this->response->setStatusCode(400);
$this->response->setBody('invalid');
return $this->response;
}
/**
* Or, we can modify the response that is waiting to go out.
*/
public function anotheraction(SS_HTTPRequest $request) {
$this->response->setStatusCode(400);
return $this->response;
}
/**
* We can render HTML and leave SilverStripe to set the response code and body.
*/
public function htmlaction() {
return $this->customize(new ArrayData(array(
'Title' => 'HTML Action'
)))->renderWith('MyCustomTemplate');
}
/**
* We can send stuff to the browser which isn't HTML
*/
public function ajaxaction() {
$this->response->setBody(json_encode(array(
'json' => true
)));
$this->response->addHeader("Content-type", "application/json");
return $this->response.
}
For more information on how a URL gets mapped to an action see the [Routing](routing) documentation.
## Security
See the [Access Controller](access_control) documentation.
## Templates
Controllers are automatically rendered with a template that makes their name. Our `TeamsController` would be rendered
with a `TeamsController.ss` template. Individual actions are rendered in `TeamsController_{actionname}.ss`.
If a template of that name does not exist, then SilverStripe will fall back to the `TeamsController.ss` then to
`Controller.ss`.
Controller actions can use `renderWith` to override this template selection process as in the previous example with
`htmlaction`. `MyCustomTemplate.ss` would be used rather than `TeamsController`.
For more information about templates, inheritance and how to rendering into views, See the
[Templates and Views](templates) documentation.
## Link
Each controller should define a `Link()` method. This should be used to avoid hard coding your routing in views etc.
**mysite/code/controllers/TeamController.php**
:::php
public function Link($action = null) {
return Controller::join_links('teams', $action);
}
<div class="info">
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.
</div>
## Related Documentation
* [Execution Pipeline](../execution_pipeline)
* [Templates and Views](../templates)
## API Documentation
* [api:Controller]
* [api:Director]

View File

@ -0,0 +1,168 @@
title: Routing
summary: A more in depth look at how to map requests to particular controllers and actions.
# Routing
Routing is the process of mapping URL's to [api:Controllers] and actions. In the introduction we defined a new custom route
for our `TeamsController` mapping any `teams` URL to our `TeamsController`
<div class="info" markdown="1">
If you're using the `cms` module with and dealing with `Page` objects then for your custom `Page Type` controllers you
would extend `ContentController` or `Page_Controller`. You don't need to define the routes value as the `cms` handles
routing.
</div>
These routes by standard, go into a `routes.yml` file in your applications `_config` folder alongside your other
[Configuration](../configuration) information.
**mysite/_config/routes.yml**
:::yml
---
Name: mysiteroutes
After: framework/routes#coreroutes
---
Director:
rules:
'teams//$Action/$ID/$Name': 'TeamController'
'player/': 'PlayerController'
'': 'HomeController'
<div class="notice" markdown="1">
To understand the syntax for the `routes.yml` file better, read the [Configuration](../configuration) documentation.
</div>
## Parameters
:::yml
'teams//$Action/$ID/$Name': 'TeamController'
This route has defined that any URL beginning with `team` should create, and be handled by a `TeamController` instance.
It also contains 3 `parameters` or `params` for short. `$Action`, `$ID` and `$Name`. These variables are placeholders
which will be filled when the user makes their request. Request parameters are available on the `SS_HTTPRequest` object
and able to be pulled out from a controller using `$this->request->param($name)`.
<div class="info" markdown="1">
All Controllers have access to `$this->request` for the request object and `$this->response` for the response.
</div>
Here is what those parameters would look like for certain requests
:::php
// GET /teams/
print_r($this->request->params());
// Array
// (
// [Action] => null
// [ID] => null
// [Name] => null
// )
// GET /teams/players/
print_r($this->request->params());
// Array
// (
// [Action] => 'players'
// [ID] => null
// [Name] => null
// )
// GET /teams/players/1
print_r($this->request->params());
// Array
// (
// [Action] => 'players'
// [ID] => 1
// [Name] => null
// )
You can also fetch one parameter at a time.
:::php
// GET /teams/players/1/
echo $this->request->param('ID');
// returns '1'
## URL Patterns
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.
<div class="alert">
A rule must always start with alphabetical ([A-Za-z]) characters or a $Variable declaration
</div>
| Pattern | Description |
| ----------- | --------------- |
| `$` | **Param Variable** - Starts the name of a paramater variable, it is optional to match this unless ! is used |
| `!` | **Require Variable** - Placing this after a parameter variable requires data to be present for the rule to match |
| `//` | **Shift Point** - Declares that only variables denoted with a $ are parsed into the $params AFTER this point in the regex |
:::yml
'teams/$Action/$ID/$OtherID': 'TeamController'
# /teams/
# /teams/players/
# /teams/
Standard URL handler syntax. For any URL that contains 'team' this rule will match and hand over execution to the
matching controller. The `TeamsController` is passed an optional action, id and other id parameters to do any more
decision making.
:::yml
'teams/$Action!/$ID!/': 'TeamController'
This does the same matching as the previous example, any URL starting with `teams` will look at this rule **but** both
`$Action` and `$ID` are required. Any requests to `team/` will result in a `404` error rather than being handed off to
the `TeamController`.
:::yml
`admin/help//$Action/$ID`: 'AdminHelp'
Match an url starting with `/admin/help/`, but don't include `/help/` as part of the action (the shift point is set to
start parsing variables and the appropriate controller action AFTER the `//`).
## URL Handlers
In the above example the URLs were configured using the [api:Director] rules in the **routes.yml** file. Alternatively
you can specify these in your Controller class via the **$url_handlers** static array. This array is processed by the
[api:RequestHandler] at runtime once the `Controller` has been matched.
This is useful when you want to provide custom actions for the mapping of `teams/*`. Say for instance we want to respond
`coaches`, and `staff` to the one controller action `payroll`.
**mysite/code/controllers/TeamController.php**
:::php
<?php
class TeamController extends Controller {
private static $allowed_actions = array(
'payroll'
);
private static $url_handlers = array(
'staff/$ID/$Name' => 'payroll'
'coach/$ID/$Name' => 'payroll'
);
The syntax for the `$url_handlers` array users the same pattern matches as the `YAML` configuration rules.
## Links
* [api:Controller] API documentation
* [api:Director] API documentation
* [Example routes: framework](https://github.com/silverstripe/silverstripe-framework/blob/master/_config/routes.yml)
* [Example routes: cms](https://github.com/silverstripe/silverstripe-cms/blob/master/_config/routes.yml)

View File

@ -0,0 +1,203 @@
title: Access Control
summary: Define allowed behavior and add permission based checks to your Controllers.
# Access Control
Within your controllers you should declare and restrict what people can see and do to ensure that users cannot run
actions on the website they shouldn't be able to.
## Allowed Actions
Any action you define on a controller must be defined in a `$allowed_actions` static array. This prevents users from
directly calling methods that they shouldn't.
:::php
<?php
class MyController extends Controller {
private static $allowed_actions = array(
// someaction can be accessed by anyone, any time
'someaction',
// So can otheraction
'otheraction' => true,
// restrictedaction can only be people with ADMIN privilege
'restrictedaction' => 'ADMIN',
// restricted to uses that have the 'CMS_ACCESS_CMSMain' access
'cmsrestrictedaction' => 'CMS_ACCESS_CMSMain',
// complexaction can only be accessed if $this->canComplexAction() returns true.
'complexaction' '->canComplexAction'
// complexactioncheck can only be accessed if $this->canComplexAction("MyRestrictedAction", false, 42) is true.
'complexactioncheck' => '->canComplexAction("MyRestrictedAction", false, 42)',
);
}
<div class="info">
If the permission check fails, SilverStripe will return a `403` Forbidden HTTP status.
</div>
An action named "index" is white listed by default, unless `allowed_actions` is defined as an empty array, or the action
is specifically restricted.
:::php
<?php
class MyController extends Controller {
public function index() {
// allowed without an $allowed_action defined
}
}
`$allowed_actions` can be defined on `Extension` classes applying to the controller.
:::php
<?php
class MyExtension extends Extension {
private static $allowed_actions = array(
'mycustomaction'
);
}
Only public methods can be made accessible.
:::php
<?php
class MyController extends Controller {
private static $allowed_actions = array(
'secure',
// secureaction won't work as it's private.
);
public function secure() {
// ..
}
private function secureaction() {
// ..
}
}
If a method on a parent class is overwritten, access control for it has to be redefined as well.
:::php
<?php
class MyController extends Controller {
private static $allowed_actions = array(
'action',
);
public function action() {
// ..
}
}
class MyChildController extends MyController {
private static $allowed_actions = array(
'action', // required as we are redefining action
);
public function action() {
}
}
<div class="notice" markdown="1">
Access checks on parent classes need to be overwritten via the [Configuration API](../configuration).
</div>
## Forms
Form action methods should **not** be included in `$allowed_actions`. However, the form method **should** be included
as an `allowed_action`.
:::php
<?php
class MyController extends Controller {
private static $allowed_actions = array(
'ContactForm' // use the Form method, not the action
);
public function ContactForm() {
return new Form(..);
}
public function doContactForm($data, $form) {
// ..
}
}
## Action Level Checks
Each method responding to a URL can also implement custom permission checks, e.g. to handle responses conditionally on
the passed request data.
:::php
<?php
class MyController extends Controller {
private static $allowed_actions = array(
'myaction'
);
public function myaction($request) {
if(!$request->getVar('apikey')) {
return $this->httpError(403, 'No API key provided');
}
return 'valid';
}
}
<div class="notice" markdown="1">
This is recommended as an addition for `$allowed_actions`, in order to handle more complex checks, rather than a
replacement.
</div>
## Controller Level Checks
After checking for allowed_actions, each controller invokes its `init()` method, which is typically used to set up
common state, If an `init()` method returns a `SS_HTTPResponse` with either a 3xx or 4xx HTTP status code, it'll abort
execution. This behavior can be used to implement permission checks.
<div class="info" markdown="1">
`init` is called for any possible action on the controller and before any specific method such as `index`.
</div>
:::php
<?php
class MyController extends Controller {
private static $allowed_actions = array();
public function init() {
parent::init();
if(!Permission::check('ADMIN')) {
return $this->httpError(403);
}
}
}
## Related Documentation
* [Security](../security)
## API Documentation
* [api:Controller]

View File

@ -0,0 +1,47 @@
title: Redirection
summary: Move users around your site using automatic redirection.
# Redirection
Controllers can facilitate redirecting users from one place to another using `HTTP` redirection using the `Location`
HTTP header.
**mysite/code/Page.php**
:::php
$this->redirect('goherenow');
// redirect to Page::goherenow(), i.e on the contact-us page this will redirect to /contact-us/goherenow/
$this->redirect('goherenow/');
// redirect to the URL on yoursite.com/goherenow/. (note the trailing slash)
$this->redirect('http://google.com');
// redirect to http://google.com
$this->redirectBack();
// go back to the previous page.
## Status Codes
The `redirect()` method takes an optional HTTP status code, either `301` for permanent redirects, or `302` for
temporary redirects (default).
:::php
$this->redirect('/', 302);
// go back to the homepage, don't cache that this page has moved
## Redirection in URL Handling
Controllers can specify redirections in the `$url_handlers` property rather than defining a method by using the '~'
operator.
:::php
private static $url_handlers = array(
'players/john' => '~>coach'
);
For more information on `$url_handlers` see the [Routing](routing) documenation.
## API Documentation
* [api:Controller]

View File

@ -0,0 +1,57 @@
title: Request Filters
summary: Create objects for modifying request and response objects across controllers.
# Request Filters
[api:RequestFilter] is an interface that provides two key methods. `preRequest` and `postRequest`. These methods are
executed before and after a request occurs to give developers a hook to modify any global state, add request tracking or
perform operations wrapped around responses and request objects. A `RequestFilter` is defined as:
**mysite/code/CustomRequestFilter.php**
:::php
<?php
class CustomRequestFilter implements RequestFilter {
public function preRequest(SS_HTTPRequest $request, Session $session, DataModel $model) {
// if(!something) {
// By returning 'false' from the preRequest method, request execution will be stopped from continuing.
// return false;
// }
// we can also set any properties onto the request that we need or add any tracking
// Foo::bar();
// return true to continue processing.
return true;
}
public function postRequest(SS_HTTPRequest $request, SS_HTTPResponse $response, DataModel $model) {
// response is about to be sent.
// any modifications or tracking to be done?
// Foo::unbar();
// return true to send the response.
return true;
}
}
After defining the `RequestFilter`, add it as an allowed `filter` through the [Configuration API](../configuration)
**mysite/_config/app.yml**
:::yml
Injector:
RequestProcessor:
properties:
filters:
- '%$CustomRequestFilter'
## API Documentation
* [api:RequestFilter]
* [api:RequestProcessor]

View File

@ -0,0 +1,23 @@
title: Controllers
summary: Controllers form the backbone of your SilverStripe application. They handle routing URLs to your templates.
introduction: In this guide you will learn how to define a Controller class and how they fit into the SilverStripe response and request cycle.
The [api:Controller] class handles the responsibility of delivering the correct outgoing [api:SS_HTTPResponse] for a
given incoming [api:SS_HTTPRequest]. A request is along the lines of a user requesting the homepage and contains
information like the URL, any parameters and where they've come from. The response on the other hand is the actual
content of the homepage and the HTTP information we want to give back to the user.
Controllers are the main handlers for functionality like interactive forms, rendering the correct templates and
performing and navigating around the permission checks on the users actions.
[CHILDREN]
## Related Documentation
* [Execution Pipeline](../execution_pipeline)
## API Documentation
* [api:Controller]
* [api:SS_HTTPRequest]
* [api:SS_HTTPResponse]

View File

@ -0,0 +1,311 @@
title: Introduction to Forms
summary: An introduction to creating a Form instance and handling submissions.
# Forms
The HTML `Form` is the most used way to interact with a user. SilverStripe provides classes to generate forms through
the [api:Form] class, [api:FormField] instances to capture data and submissions through [api:FormAction].
<div class="notice" markdown="1">
See the [Forms Tutorial](../../tutorials/forms/) for a step by step process of creating a `Form`
</div>
## Creating a Form
Creating a [api:Form] has the following signature.
:::php
$form = new Form(
$controller, // the Controller to render this form on
$name, // name of the method that returns this form on the controller
FieldList $fields, // list of FormField instances
FieldList $actions, // list of FormAction instances
$required // optional use of RequiredFields object
);
In practice, this looks like:
**mysite/code/Page.php**
:::php
<?php
class Page_Controller extends ContentController {
private static $allowed_actions = array(
'HelloForm'
);
public function HelloForm() {
$fields = new FieldList(
TextField::create('Name', 'Your Name')
);
$actions = new FieldList(
FormAction::create("doSayHello")->setTitle("Say hello")
);
$required = new RequiredFields('Name')
$form = new Form($this, 'HelloForm', $fields, $actions, $required);
return $form;
}
public function doSayHello($data, Form $form) {
$form->sessionMessage('Hello '. $data['Name'], 'success');
return $this->redirectBack();
}
}
**mysite/templates/Page.ss**
:::ss
$HelloForm
<div class="info" markdown="1">
The examples above use `FormField::create()` instead of the `new` operator (`new FormField()`). These are functionally
equivalent, but allows PHP to chain operations like `setTitle()` without assigning the field instance to a temporary
variable.
</div>
When constructing the `Form` instance (`new Form($controller, $name)`) both controller and name are required. The
`$controller` and `$name` are used to allow SilverStripe to calculate the origin of the `Form object`. When a user
submits the `HelloForm` from your `contact-us` page the form submission will go to `contact-us/HelloForm` before any of
the [api:FormActions]. The URL is known as the `$controller` instance will know the 'contact-us' link and we provide
`HelloForm` as the `$name` of the form. `$name` **needs** to match the method name.
Because the `HelloForm()` method will be the location the user is taken to, it needs to be handled like any other
controller action. To grant it access through URLs, we add it to the `$allowed_actions` array.
:::php
private static $allowed_actions = array(
'HelloForm'
);
<div class="notice" markdown="1">
Form actions (`doSayHello`), on the other hand, should _not_ be included in `$allowed_actions`; these are handled
separately through [api:Form::httpSubmission].
</div>
## Adding FormFields
Fields in a [api:Form] are represented as a single [api:FieldList] instance containing subclasses of [api:FormField].
Some common examples are [api:TextField] or [api:DropdownField].
:::php
TextField::create($name, $title, $value);
<div class="info" markdown='1'>
A list of the common FormField subclasses is available on the [Common Subclasses](fields/common_subclasses) page.
</div>
The fields are added to the [api:FieldList] `fields` property on the `Form` and can be modified at up to the point the
`Form` is rendered.
:::php
$fields = new FieldList(
TextField::create('Name'),
EmailField::create('Email')
);
$form = new Form($controller, 'MethodName', $fields, ...);
// or use `setFields`
$form->setFields($fields);
// to fetch the current fields..
$fields = $form->getFields();
A field can be appended to the [api:FieldList].
:::php
$fields = $form->Fields();
// add a field
$fields->push(new TextField(..));
// insert a field before another one
$fields->insertBefore(new TextField(..), 'Email');
// insert a field after another one
$fields->insertAfter(new TextField(..), 'Name');
Fields can be fetched after they have been added in.
:::php
$email = $form->Fields()->dataFieldByName('Email');
$email->setTitle('Your Email Address');
Fields can be removed from the form.
:::php
$form->getFields()->removeByName('Email');
<div class="alert" markdown="1">
Forms can be tabbed (such as the CMS interface). In these cases, there are additional functions such as `addFieldToTab`
and `removeFieldByTab` to ensure the fields are on the correct interface. See [Tabbed Forms](tabbed_forms) for more
information on the CMS interface.
</div>
## Modifying FormFields
Each [api:FormField] subclass has a number of methods you can call on it to customize its' behavior or HTML markup. The
default `FormField` object has several methods for doing common operations.
<div class="notice">
Most of the `set` operations will return the object back so methods can be chained.
</div>
:::php
$field = new TextField(..);
$field
->setMaxLength(100)
->setAttribute('placeholder', 'Enter a value..')
->setTitle('');
### Custom Templates
The [api:Form] HTML markup and each of the [api:FormField] instances are rendered into templates. You can provide custom
templates by using the `setTemplate` method on either the `Form` or `FormField`. For more details on providing custom
templates see [Form Templates](form_templates)
:::php
$form = new Form(..);
$form->setTemplate('CustomForm');
// or, for a FormField
$field = new TextField(..);
$field->setTemplate('CustomTextField');
$field->setFieldHolderTemplate('CustomTextField_Holder');
## Adding FormActions
[api:FormAction] objects are displayed at the bottom of the `Form` in the form of a `button` or `input` tag. When a
user presses the button, the form is submitted to the corresponding method.
:::php
FormAction::create($action, $title);
As with [api:FormField], the actions for a `Form` are stored within a [api:FieldList] instance in the `actions` property
on the form.
:::php
public function MyForm() {
$fields = new FieldList(..);
$actions = new FieldList(
FormAction::create('doSubmitForm', 'Submit')
);
$form = new Form($controller, 'MyForm', $fields, $actions);
// Get the actions
$actions = $form->Actions();
// As actions is a FieldList, push, insertBefore, removeByName and other
// methods described for `Fields` also work for actions.
$actions->push(
FormAction::create('doSecondaryFormAction', 'Another Button')
);
$actions->removeByName('doSubmitForm');
$form->setActions($actions);
return $form
}
public function doSubmitForm($data, $form) {
//
}
public function doSecondaryFormAction($data, $form) {
//
}
The first `$action` argument for creating a `FormAction` is the name of the method to invoke when submitting the form
with the particular button. In the previous example, clicking the 'Another Button' would invoke the
`doSecondaryFormAction` method. This action can be defined (in order) on either:
* One of the `FormField` instances.
* The `Form` instance.
* The `Controller` instance.
<div class="notice">
If the `$action` method cannot be found on any of those or is marked as `private` or `protected`, an error will be
thrown.
</div>
The `$action` method takes two arguments:
* `$data` an array containing the values of the form mapped from `$name` => '$value'
* `$form` the submitted [api:Form] instance.
:::php
<?php
class Page_Controller extends ContentController {
private static $allowed_actions = array(
'MyForm'
);
public function MyForm() {
$fields = new FieldList(
TextField::create('Name'),
EmailField::create('Email')
);
$actions = new FieldList(
FormAction::create('doSubmitForm', 'Submit')
);
$form = new Form($controller, 'MyForm', $fields, $actions);
return $form
}
public function doSubmitForm($data, $form) {
// Submitted data is available as a map.
echo $data['Name'];
echo $data['Email'];
// You can also fetch the value from the field.
echo $form->Fields()->dataFieldByName('Email')->Value();
// Using the Form instance you can get / set status such as error messages.
$form->sessionMessage("Successful!", 'good');
// After dealing with the data you can redirect the user back.
return $this->redirectBack();
}
}
## Validation
Form validation is handled by the [api:Validator] class and the `validator` property on the `Form` object. The validator
is provided with a name of each of the [api:FormField]s to validate and each `FormField` instance is responsible for
validating its' own data value.
For more information, see the [Form Validation](validation) documentation.
:::php
$validator = new RequiredFields(array(
'Name', 'Email'
));
$form = new Form($this, 'MyForm', $fields, $actions, $validator);
## API Documentation
* [api:Form]
* [api:FormField]
* [api:FieldList]
* [api:FormAction]

View File

@ -0,0 +1,235 @@
title: Form Validation
summary: Validate form data through the server side validation API.
# Form Validation
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`.
:::php
<?php
class Page_Controller extends ContentController {
private static $allowed_actions = array(
'MyForm'
);
public function MyForm() {
$fields = new FieldList(
TextField::create('Name'),
EmailField::create('Email')
);
$actions = new FieldList(
FormAction::create('doSubmitForm', 'Submit')
);
// the fields 'Name' and 'Email' are required.
$required = new RequiredFields(array(
'Name', 'Email'
));
// $required can be set as an argument
$form = new Form($controller, 'MyForm', $fields, $actions, $required);
// Or, through a setter.
$form->setValidator($required);
return $form;
}
public function doSubmitForm($data, $form) {
//..
}
}
In this example we will be required to input a value for `Name` and a valid email address for `Email` before the
`doSubmitForm` method is called.
<div class="info" markdown="1">
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
`validate` to check for a specific format.
</div>
Subclasses of `FormField` can define their own version of `validate` to provide custom validation rules such as the
above example with the `Email` validation. The `validate` method on `FormField` takes a single argument of the current
`Validator` instance.
<div class="notice" markdown="1">
The data value of the `FormField` submitted is not passed into validate. It is stored in the `value` property through
the `setValue` method.
</div>
:::php
public function validate($validator) {
if($this->value == 10) {
return false;
}
return true;
}
The `validate` method should return `true` if the value passes any validation and `false` if SilverStripe should trigger
a validation error on the page.
<div class="notice" markdown="1">
You can also override the entire `Form` validation by subclassing `Form` and defining a `validate` method on the form.
</div>
Say we need a custom `FormField` which requires the user input a value in a `TextField` between 2 and 5. There would be
two ways to go about this:
A custom `FormField` which handles the validation. This means the `FormField` can be reused throughout the site and have
the same validation logic applied to it throughout.
**mysite/code/formfields/CustomNumberField.php**
:::php
<?php
class CustomNumberField extends TextField {
public function validate($validator) {
if(!is_numeric($this->value)) {
$validator->validationError(
$this->name, "Not a number. This must be between 2 and 5", "validation", false
);
return false;
}
else if($this->value > 5 || $this->value < 2) {
$validator->validationError(
$this->name, "Your number must be between 2 and 5, "validation", false
);
return false;
}
return true;
}
}
Or, an alternative approach to the custom class is to define the behavior inside the Form's action method. This is less
reusable and would not be possible within the `CMS` or other automated `UI` but does not rely on creating custom
`FormField` classes.
:::php
<?php
class Page_Controller extends ContentController {
private static $allowed_actions = array(
'MyForm'
);
public function MyForm() {
$fields = new FieldList(
TextField::create('Name'),
EmailField::create('Email')
);
$actions = new FieldList(
FormAction::create('doSubmitForm', 'Submit')
);
$form = new Form($controller, 'MyForm', $fields, $actions);
return $form
}
public function doSubmitForm($data, $form) {
// At this point, RequiredFields->validate() will have been called already,
// so we can assume that the values exist. Say we want to make sure that email hasn't already been used.
$check = Member::get()->filter('Email', $data['Email'])->first();
if($check) {
$form->addErrorMessage('Email', 'This email already exists', 'bad');
return $this->redirectBack();
}
$form->sessionMessage("You have been added to our mailing list", 'good');
return $this->redirectBack();
}
}
## Server-side validation messages
If a `FormField` fails to pass `validate()` the default error message is returned.
:::php
'$Name' is required
Use `setCustomValidationMessage` to provide a custom message.
:::php
$field = new TextField(..);
$field->setCustomValidationMessage('Whoops, looks like you have missed me!');
## JavaScript validation
Although there are no built-in JavaScript validation handlers in SilverStripe, the `FormField` API is flexible enough
to provide the information required in order to plug in custom libraries like [Parsley.js](http://parsleyjs.org/) or
[jQuery.Validate](http://jqueryvalidation.org/). Most of these libraries work on HTML `data-` attributes or special
classes added to each input. For Parsley we can structure the form like.
:::php
$form = new Form(..);
$form->setAttribute('data-parsley-validate', true);
$field = $fields->dataFieldByName('Name');
$field->setAttribute('required', true);
$field->setAttribute('data-parsley-mincheck', '2');
## 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
[Data Model Validation](../model/validation).
### Validation in the CMS
In the CMS, we're not creating the forms for editing CMS records. The `Form` instance is generated for us so we cannot
call `setValidator` easily. However, a `DataObject` can provide its' own `Validator` instance through the
`getCMSValidator()` method. The CMS interfaces such as [api:LeftAndMain], [api:ModelAdmin] and [api:GridField] will
respect the provided `Validator` and handle displaying error and success responses to the user.
<div class="info" markdown="1">
Again, custom error messages can be provided through the `FormField`
</div>
:::php
<?php
class Page extends SiteTree {
private static $db = array(
'MyRequiredField' => 'Text'
);
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->addFieldToTab('Root.Main',
TextField::create('MyRequiredField')->setCustomValidationMessage('You missed me.')
);
}
public function getCMSValidator() {
return new RequiredFields(array(
'MyRequiredField'
));
}
## API Documentation
* [api:RequiredFields]
* [api:Validator]

View File

@ -0,0 +1,69 @@
title: Form Templates
summary: Customize the generated HTML for a FormField or an entire Form.
# Form Templates
Most markup generated in SilverStripe can be replaced by custom templates. Both [api:Form] and [api:FormField] instances
can be rendered out using custom templates using `setTemplate`.
:::php
$form = new Form(..);
$form->setTemplate('MyCustomFormTemplate');
// or, just a field
$field = new TextField(..);
$field->setTemplate('MyCustomTextField');
Both `MyCustomTemplate.ss` and `MyCustomTextField.ss` should be located in **mysite/templates/Includes/**
<div class="notice" markdown="1">
It's recommended to copy the contents of the template you're going to replace and use that as a start. For instance, if
you want to create a `MyCustomFormTemplate` copy the contents of `Form.ss` to a `MyCustomFormTemplate.ss` file and
modify as you need.
</div>
By default, Form and Fields follow the SilverStripe Template convention and are rendered into templates of the same
class name (i.e EmailField will attempt to render into `EmailField.ss` and if that isn't found, `TextField.ss` or
finally `FormField.ss`).
<div class="alert" markdown="1">
While you can override all templates using normal view inheritance (i.e defining a `Form.ss`) other modules may rely on
the core template structure. It is recommended to use `setTemplate` and unique templates for specific forms.
</div>
For [api:FormField] instances, there are several other templates that are used on top of the main `setTemplate`.
:::php
$field = new TextField();
$field->setTemplate('CustomTextField');
// Sets the template for the <input> tag. i.e '<input $AttributesHTML />'
$field->setFieldHolderTemplate('CustomTextField_Holder');
// Sets the template for the wrapper around the text field. i.e
// '<div class="text">'
//
// The actual FormField is rendered into the holder via the `$Field`
// variable.
//
// setFieldHolder() is used in most `Form` instances and needs to output
// labels, error messages and the like.
$field->setSmallFieldHolderTemplate('CustomTextField_Holder_Small');
// Sets the template for the wrapper around the text field.
//
// The difference here is the small field holder template is used when the
// field is embedded within another field. For example, if the field is
// part of a `FieldGroup` or `CompositeField` alongside other fields.
All templates are rendered within the scope of the [api:FormField]. To understand more about Scope within Templates as
well as the available syntax, see the [Templates](../templates) documentation.
## Related Documentation
* [How to: Create a lightweight Form](how_tos/lightweight_form)
## API Documentation
* [api:Form]
* [api:FormField]

View File

@ -0,0 +1,76 @@
title: Form Security
summary: Ensure Forms are secure against Cross-Site Request Forgery attacks, bots and other malicious intent.
# Form Security
Whenever you are accepting or asking users to input data to your application there comes an added responsibility that it
should be done as safely as possible. Below outlines the things to consider when building your forms.
## Cross-Site Request Forgery (CSRF)
SilverStripe protect users against [Cross-Site Request Forgery](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)
(known as `CSRF`) by adding a SecurityID [api:HiddenField] to each [api:Form] instance. The `SecurityID` contains a
random string generated by [api:SecurityToken] to identify the particular user request vs a third-party forging fake
requests.
<div class="info" markdown="1">
For more information on Cross-Site Request Forgery, consult the [OWASP](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)
website.
</div>
The `SecurityToken` automatically added looks something like:
:::php
$form = new Form(..);
echo $form->getSecurityToken()->getValue();
// 'c443076989a7f24cf6b35fe1360be8683a753e2c'
This token value is passed through the rendered Form HTML as a [api:HiddenField].
:::html
<input type="hidden" name="SecurityID" value="c443076989a7f24cf6b35fe1360be8683a753e2c" class="hidden" />
The token should be present whenever a operation has a side effect such as a `POST` operation.
It can be safely disabled for `GET` requests as long as it does not modify the database (i.e a search form does not
normally require a security token).
:::php
$form = new Form(..);
$form->disableSecurityToken();
<div class="alert" markdown="1">
Do not disable the SecurityID for forms that perform some modification to the users session. This will open your
application up to `CSRF` security holes.
</div>
## Strict Form Submission
Forms should be limited to the intended HTTP verb (mostly `GET` or `POST`) to further reduce attack exposure. Without
this check, forms that rely on `GET` can be submitted via `POST` or `PUT` or vice-versa potentially leading to
application errors or edge cases.
:::php
$form = new Form(..);
$form->setFormMethod('POST');
$form->setStrictFormMethodCheck(true);
// or alternative short notation..
$form->setFormMethod('POST', true);
## Spam and Bot Attacks
SilverStripe has no built-in protection for detailing with bots, captcha or other spam protection methods. This
functionality is available as an additional [Spam Protection](https://github.com/silverstripe/silverstripe-spamprotection)
module if required. The module provides an consistent API for allowing third-party spam protection handlers such as
[Recaptcha](http://www.google.com/recaptcha/intro/) and [Mollom](https://mollom.com/) to work within the `Form` API.
## Related Documentation
* [Security](../security)
## API Documentation
* [api:SecurityToken]

View File

@ -0,0 +1,53 @@
title: Form Transformations
summary: Provide read-only and disabled views of your Form data.
# Read-only and Disabled Forms
[api:Form] and [api:FormField] instances can be turned into a read-only version for things like confirmation pages or
when certain fields cannot be edited due to permissions. Creating the form is done the same way and markup is similar,
`readonly` mode converts the `input`, `select` and `textarea` tags to static HTML elements like `span`.
To make an entire [api:Form] read-only.
:::php
$form = new Form(..);
$form->makeReadonly();
To make all the fields within a [api:FieldList] read-only (i.e to make fields read-only but not buttons).
:::php
$fields = new FieldList(..);
$fields = $fields->makeReadonly();
To make a [api:FormField] read-only you need to know the name of the form field or call it direct on the object
:::php
$field = new TextField(..);
$field = $field->performReadonlyTransformation();
$fields = new FieldList(
$field
);
// Or,
$field = new TextField(..);
$field->setReadonly(true);
$fields = new FieldList(
$field
);
## Disabled FormFields
Disabling [api:FormField] instances, sets the `disabled` property on the class. This will use the same HTML markup as
a normal form, but set the `disabled` attribute on the `input` tag.
:::php
$field = new TextField(..);
$field->setDisabled(true);
echo $field->forTemplate();
// returns '<input type="text" class="text" .. disabled="disabled" />'

View File

@ -0,0 +1,55 @@
title: Tabbed Forms
summary: Find out how CMS interfaces use jQuery UI tabs to provide nested FormFields.
# Tabbed Forms
SilverStripe's [api:FormScaffolder] can automatically generate [api:Form] instances for certain database models. In the
CMS and other scaffolded interfaces, it will output [api:TabSet] and [api:Tab] objects and use jQuery Tabs to split
parts of the data model.
<div class="notice" markdown="1">
All interfaces within the CMS such as [api:ModelAdmin] and [api:LeftAndMain] use tabbed interfaces by default.
</div>
When dealing with tabbed forms, modifying the fields in the form has a few differences. Each [api:Tab] will be given a
name, and normally they all exist under the `Root` [api:TabSet].
<div class="notice" markdown="1">
[api:TabSet] instances can contain child [api:Tab] and further [api:TabSet] instances, however the CMS UI will only
display up to two levels of tabs in the interface. If you want to group data further than that, try [api:ToggleField].
</div>
## Adding a field from a tab.
:::php
$fields->addFieldToTab('Root.Main', new TextField(..));
## Removing a field from a tab
:::php
$fields->removeFieldFromTab('Root.Main', 'Content');
## Creating a new tab
:::php
$fields->addFieldToTab('Root.MyNewTab', new TextField(..));
## Moving a field between tabs
:::php
$field = $fields->dataFieldByName('Content');
$fields->removeFieldFromTab('Root.Main', 'Content');
$fields->addFieldToTab('Root.MyContent', $content);
## Add multiple fields at once
:::php
$fields->addFieldsToTab('Root.Content', array(
TextField::create('Name'),
TextField::create('Email')
));
## API Documentation
* [api:FormScaffolder]

View File

@ -1,6 +1,10 @@
# Form Field Types
title: Common FormField type subclasses
summary: A table containing a list of the common FormField subclasses.
This is a highlevel overview of available `[api:FormField]` subclasses. An automatically generated list is available through our [API]
# Common FormField type subclasses
This is a high level overview of available `[api:FormField]` subclasses. An automatically generated list is available
on the SilverStripe API documentation.
## Basic
@ -16,7 +20,7 @@ This is a highlevel overview of available `[api:FormField]` subclasses. An autom
* `[api:FormAction]`: Button element for forms, both for `<input type="submit">` and `<button>`.
* `[api:ResetFormAction]`: Action that clears all fields on a form.
## Formatted Input
## Formatted input
* `[api:AjaxUniqueTextField]`: Text field that automatically checks that the value entered is unique for the given set of fields in a given set of tables.
* `[api:ConfirmedPasswordField]`: Two masked input fields, checks for matching passwords.
@ -28,13 +32,13 @@ This is a highlevel overview of available `[api:FormField]` subclasses. An autom
* `[api:DatetimeField]`: Combined date- and time field.
* `[api:EmailField]`: Text input field with validation for correct email format according to RFC 2822.
* `[api:GroupedDropdownField]`: Grouped dropdown, using <optgroup> tags.
* `[api:HtmlEditorField]`.
* `[api:HtmlEditorField]`: A WYSIWYG editor interface.
* `[api:MoneyField]`: A form field that can save into a `[api:Money]` database field.
* `[api:NumericField]`: Text input field with validation for numeric values.
* `[api:OptionsetField]`: Set of radio buttons designed to emulate a dropdown.
* `[api:PhoneNumberField]`: Field for displaying phone numbers. It separates the number, the area code and optionally the country code and extension.
* `[api:SelectionGroup]`: SelectionGroup represents a number of fields which are selectable by a radio button that appears at the beginning of each item.
* `[api:TimeField]`: Input field with time-specific, localized validation.
* `[api:TimeField]`: Input field with time-specific, localised validation.
## Structure
@ -67,6 +71,6 @@ doesn't necessarily have any visible styling.
* `[api:DatalessField]` - Base class for fields which add some HTML to the form but don't submit any data or
save it to the database
* `[api:HeaderField]`: Renders a simple HTML header element.
* `[api:HiddenField]`.
* `[api:HiddenField]` - Renders a hidden input field.
* `[api:LabelField]`: Simple label tag. This can be used to add extra text in your forms.
* `[api:LiteralField]`: Renders arbitrary HTML into a form.

View File

@ -0,0 +1,130 @@
title: DateField
summary: How to format and use the DateField class.
# DateField
This `FormField` subclass lets you display an editable date, either in a single text input field, or in three separate
fields for day, month and year. It also provides a calendar date picker.
The following example will add a simple DateField to your Page, allowing you to enter a date manually.
**mysite/code/Page.php**
:::php
<?php
class Page extends SiteTree {
private static $db = array(
'MyDate' => 'Date',
);
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->addFieldToTab(
'Root.Main',
DateField::create('MyDate', 'Enter a date')
);
return $fields;
}
}
## Custom Date Format
A custom date format for a [api:DateField] can be provided through `setConfig`.
:::php
// will display a date in the following format: 31-06-2012
DateField::create('MyDate')->setConfig('dateformat', 'dd-MM-yyyy');
<div class="info" markdown="1">
The formats are based on [Zend_Date constants](http://framework.zend.com/manual/1.12/en/zend.date.constants.html).
</div>
## Min and Max Dates
Sets the minimum and maximum allowed date values using the `min` and `max` configuration settings (in ISO format or
strtotime()).
:::php
DateField::create('MyDate')
->setConfig('min', '-7 days')
->setConfig('max', '2012-12-31')
## Separate Day / Month / Year Fields
The following setting will display your DateField as three input fields for day, month and year separately. HTML5
placeholders 'day', 'month' and 'year' are enabled by default.
:::php
DateField::create('MyDate')
->setConfig('dmyfields', true)
->setConfig('dmyseparator', '/') // set the separator
->setConfig('dmyplaceholders', 'true'); // enable HTML 5 Placeholders
<div class="alert" markdown="1">
Any custom date format settings will be ignored.
</div>
## Calendar Picker
The following setting will add a Calendar to a single DateField, using the jQuery UI DatePicker widget.
:::php
DateField::create('MyDate')
->setConfig('showcalendar', true);
The jQuery DatePicker doesn't support every constant available for `Zend_Date`. If you choose to use the calendar, the
following constants should at least be safe:
Constant | xxxxx
-------- | -----
d | numeric day of the month (without leading zero)
dd | numeric day of the month (with leading zero)
EEE | dayname, abbreviated
EEEE | dayname
M | numeric month of the year (without leading zero)
MM | numeric month of the year (with leading zero)
MMM | monthname, abbreviated
MMMM | monthname
y | year (4 digits)
yy | year (2 digits)
yyyy | year (4 digits)
Unfortunately the day- and monthname values in Zend Date do not always match those in the existing jQuery UI locale
files, so constants like `EEE` or `MMM`, for day and month names could break validation. To fix this we had to slightly
alter the jQuery locale files, situated in */framework/thirdparty/jquery-ui/datepicker/i18n/*, to match Zend_Date.
<div class="info">
At this moment not all locale files may be present. If a locale file is missing, the DatePicker calendar will fallback
to 'yyyy-MM-dd' whenever day - and/or monthnames are used. After saving, the correct format will be displayed.
</div>
## Formatting Hints
It's often not immediate apparent which format a field accepts, and showing the technical format (e.g. `HH:mm:ss`) is
of limited use to the average user. An alternative is to show the current date in the desired format alongside the
field description as an example.
:::php
$dateField = DateField::create('MyDate');
// Show long format as text below the field
$dateField->setDescription(sprintf(
_t('FormField.Example', 'e.g. %s', 'Example format'),
Convert::raw2xml(Zend_Date::now()->toString($dateField->getConfig('dateformat')))
));
// Alternatively, set short format as a placeholder in the field
$dateField->setAttribute('placeholder', $dateField->getConfig('dateformat'));
<div class="notice" markdown="1">
Fields scaffolded through [api:DataObject::scaffoldCMSFields] automatically have a description attached to them.
</div>
## API Documentation
* [api:DateField]

View File

@ -1,78 +1,90 @@
# Rich-Text Editing (WYSIWYG)
title: Rich-text editing (WYSIWYG)
summary: SilverStripe's use and configuration of TinyMCE html editor.
## Introduction
# Rich-text editing (WYSIWYG)
Editing and formatting content is the bread and butter of every content management system,
which is why SilverStripe has a tight integration with our preferred editor library, [TinyMCE](http://tinymce.com).
On top of the base functionality, we use our own insertion dialogs to ensure
you can effectively select and upload files. In addition to the markup managed by TinyMCE,
we use [shortcodes](/reference/shortcodes) to store information about inserted
images or media elements.
Editing and formatting content is the bread and butter of every content management system, which is why SilverStripe
has a tight integration with our preferred editor library, [TinyMCE](http://tinymce.com).
## Usage
On top of the base functionality, we use our own insertion dialogs to ensure you can effectively select and upload
files. In addition to the markup managed by TinyMCE, we use [shortcodes](../../extending/shortcodes) to store
information about inserted images or media elements.
The framework comes with a `[api:HTMLEditorField]` form field class which encapsulates most of the required
functionality. It is usually added through the `[api:DataObject->getCMSFields()]` method:
**mysite/code/MyObject.php**
:::php
<?php
class MyObject extends DataObject {
private static $db = array('Content' => 'HTMLText');
private static $db = array(
'Content' => 'HTMLText'
);
public function getCMSFields() {
return new FieldList(new HTMLEditorField('Content'));
return new FieldList(
new HTMLEditorField('Content')
);
}
}
## Configuration
To keep the JavaScript editor configuration manageable and extensible,
we've wrapped it in a PHP class called `[api:HtmlEditorConfig]`.
The class comes with its own defaults, which are extended through [configuration files](/topics/configuration)
To keep the JavaScript editor configuration manageable and extensible, we've wrapped it in a PHP class called
`[api:HtmlEditorConfig]`. The class comes with its own defaults, which are extended through the [Configuration API](../../configuration)
in the framework (and the `cms` module in case you've got that installed).
There can be multiple configs, which should always be created / accessed using `[api:HtmlEditorConfig::get]`.
You can then set the currently active config using `set_active()`.
There can be multiple configs, which should always be created / accessed using `[api:HtmlEditorConfig::get]`. You can
then set the currently active config using `set_active()`.
<div class="info" markdown="1">
By default, a config named 'cms' is used in any field created throughout the CMS interface.
<div class="notice" markdown='1'>
Caveat: currently the order in which the `_config.php` files are executed depends on the module directory
names. Execution order is alphabetical, so if you set a TinyMCE option in the `aardvark/_config.php`, this
will be overriden in `framework/admin/_config.php` and your modification will disappear.
This is a general problem with `_config.php` files - it may be fixed in the future by making it possible to
configure the TinyMCE with the new [configuration system](../topics/configuration).
</div>
### Adding and removing capabilities
<div class="notice" markdown='1'>
Currently the order in which the `_config.php` files are executed depends on the module directory names. Execution
order is alphabetical, so if you set a TinyMCE option in the `aardvark/_config.php`, this will be overridden in
`framework/admin/_config.php` and your modification will disappear.
</div>
## Adding and removing capabilities
In its simplest form, the configuration of the editor includes adding and removing buttons and plugins.
You can add plugins to the editor using the Framework's `[api:HtmlEditorConfig::enablePlugins]` method. This will
transparently generate the relevant underlying TinyMCE code.
**mysite/_config.php**
:::php
// File: mysite/_config.php
HtmlEditorConfig::get('cms')->enablePlugins('media');
Note: this utilises the TinyMCE's `PluginManager::load` function under the hood (check the
[TinyMCE documentation on plugin
loading](http://www.tinymce.com/wiki.php/API3:method.tinymce.AddOnManager.load) for details).
<div class="notice" markdown="1">
This utilities the TinyMCE's `PluginManager::load` function under the hood (check the
[TinyMCE documentation on plugin loading](http://www.tinymce.com/wiki.php/API3:method.tinymce.AddOnManager.load) for
details).
</div>
Plugins and advanced themes can provide additional buttons that can be added (or removed) through the
configuration. Here is an example of adding a `ssmacron` button after the `charmap` button:
**mysite/_config.php**
:::php
// File: mysite/_config.php
HtmlEditorConfig::get('cms')->insertButtonsAfter('charmap', 'ssmacron');
Buttons can also be removed:
**mysite/_config.php**
:::php
// File: mysite/_config.php
HtmlEditorConfig::get('cms')->removeButtons('tablecontrols', 'blockquote', 'hr');
Note: internally `[api:HtmlEditorConfig]` uses the TinyMCE's `theme_advanced_buttons` option to configure these. See
the [TinyMCE documentation of this option](http://www.tinymce.com/wiki.php/Configuration:theme_advanced_buttons_1_n)
<div class="notice" markdown="1">
Internally `[api:HtmlEditorConfig]` uses the TinyMCE's `theme_advanced_buttons` option to configure these. See the
[TinyMCE documentation of this option](http://www.tinymce.com/wiki.php/Configuration:theme_advanced_buttons_1_n)
for more details.
</div>
### Setting options
@ -83,6 +95,7 @@ One example of the usage of this capability is to redefine the TinyMCE's [whitel
tags](http://www.tinymce.com/wiki.php/Configuration:extended_valid_elements) - the tags that will not be stripped
from the HTML source by the editor.
**mysite/_config.php**
:::php
// Add start and type attributes for <ol>, add <object> and <embed> with all attributes.
HtmlEditorConfig::get('cms')->setOption(
@ -97,17 +110,19 @@ from the HTML source by the editor.
'ol[start|type]'
);
Note: the default setting for the CMS's `extended_valid_elements` we are overriding here can be found in
<div class="notice" markdown="1">
The default setting for the CMS's `extended_valid_elements` we are overriding here can be found in
`framework/admin/_config.php`.
</div>
### Writing custom plugins
## Writing custom plugins
It is also possible to add custom buttons to TinyMCE. A simple example of this is SilverStripe's `ssmacron`
plugin. The source can be found in the Framework's `thirdparty/tinymce_ssmacron` directory.
It is also possible to add custom buttons to TinyMCE. A simple example of this is SilverStripe's `ssmacron` plugin. The
source can be found in the Framework's `thirdparty/tinymce_ssmacron` directory.
Here is how we can create a project-specific plugin. Create a `mysite/javascript/myplugin` directory,
add the plugin button icon - here `myplugin.png` - and the source code - here `editor_plugin.js`. Here is a very
simple example of a plugin that adds a button to the editor:
Here is how we can create a project-specific plugin. Create a `mysite/javascript/myplugin` directory, add the plugin
button icon - here `myplugin.png` - and the source code - here `editor_plugin.js`. Here is a very simple example of a
plugin that adds a button to the editor:
:::js
(function() {
@ -140,65 +155,65 @@ simple example of a plugin that adds a button to the editor:
tinymce.PluginManager.add('myplugin', tinymce.plugins.myplugin);
})();
You can then enable this plugin through the `[api:HtmlEditorConfig::enablePlugins]`:
You can then enable this plugin through the [api:HtmlEditorConfig::enablePlugins]:
**mysite/_config.php**
:::php
HtmlEditorConfig::get('cms')->enablePlugins(array('myplugin' => '../../../mysite/javascript/myplugin/editor_plugin.js'));
For more complex examples see the [Creating a Plugin](http://www.tinymce.com/wiki.php/Creating_a_plugin) in TinyMCE
documentation, or browse through plugins that come with the Framework at `thirdparty/tinymce/plugins`.
## Image and Media Insertion
## Image and media insertion
The `[api:HtmlEditorField]` API also handles inserting images and media
files into the managed HTML content. It can be used both for referencing
files on the webserver filesystem (through the `[api:File]` and `[api:Image]` APIs),
as well as hotlinking files from the web.
The `[api:HtmlEditorField]` API also handles inserting images and media files into the managed HTML content. It can be
used both for referencing files on the webserver filesystem (through the `[api:File]` and `[api:Image]` APIs), as well
as hotlinking files from the web.
We use [shortcodes](/reference/shortcodes) to store information about inserted images or media elements.
The `[api:ShortcodeParser]` API post-processes the HTML content on rendering,
and replaces the shortcodes accordingly. It also takes care of care of placing the
shortcode replacements relative to its surrounding markup (e.g. left/right alignment).
We use [shortcodes](../../configuration/shortcodes) to store information about inserted images or media elements. The
[api:ShortcodeParser] API post-processes the HTML content on rendering, and replaces the shortcodes accordingly. It also
takes care of care of placing the shortcode replacements relative to its surrounding markup (e.g. left/right alignment).
## oEmbed: Embedding media through external services
The ["oEmbed" standard](http://www.oembed.com/) is implemented by many media services
around the web, allowing easy representation of files just by referencing a website URL.
For example, a content author can insert a playable youtube video just by knowing
its URL, as opposed to dealing with manual HTML code.
The ["oEmbed" standard](http://www.oembed.com/) is implemented by many media services around the web, allowing easy
representation of files just by referencing a website URL. For example, a content author can insert a playable youtube
video just by knowing its URL, as opposed to dealing with manual HTML code.
oEmbed powers the "Insert from web" feature available through `[api:HtmlEditorField]`.
Internally, it makes HTTP queries to a list of external services
if it finds a matching URL. These services are described in the `Oembed.providers` configuration.
Since these requests are performed on page rendering, they typically have a long cache time (multiple days). To refresh
a cache, append `?flush=1` to a URL.
oEmbed powers the "Insert from web" feature available through `[api:HtmlEditorField]`. Internally, it makes HTTP
queries to a list of external services if it finds a matching URL. These services are described in the
`Oembed.providers` configuration. Since these requests are performed on page rendering, they typically have a long
cache time (multiple days).
<div class="info" markdown="1">
To refresh a oEmbed cache, append `?flush=1` to a URL.
</div>
To disable oEmbed usage, set the `Oembed.enabled` configuration property to "false".
### Doctypes
Since TinyMCE generates markup, it needs to know which doctype your documents
will be rendered in. You can set this through the [element_format](http://www.tinymce.com/wiki.php/Configuration:element_format) configuration variable. It defaults to the stricter 'xhtml'
setting, for example rendering self closing tags like `<br/>` instead of `<br>`.
Since TinyMCE generates markup, it needs to know which doctype your documents will be rendered in. You can set this
through the [element_format](http://www.tinymce.com/wiki.php/Configuration:element_format) configuration variable. It
defaults to the stricter 'xhtml' setting, for example rendering self closing tags like `<br/>` instead of `<br>`.
In case you want to adhere to HTML4 instead, use the following configuration:
:::php
HtmlEditorConfig::get('cms')->setOption('element_format', 'html');
By default, TinyMCE and SilverStripe will generate valid HTML5 markup,
but it will strip out HTML5 tags like `<article>` or `<figure>`.
If you plan to use those, add them to the [valid_elements](http://www.tinymce.com/wiki.php/Configuration:valid_elements)
configuration setting.
By default, TinyMCE and SilverStripe will generate valid HTML5 markup, but it will strip out HTML5 tags like
`<article>` or `<figure>`. If you plan to use those, add them to the
[valid_elements](http://www.tinymce.com/wiki.php/Configuration:valid_elements) configuration setting.
Also, the `[api:SS_HTMLValue]` API underpinning the HTML processing parses the markup
into a temporary object tree which can be traversed and modified before saving.
The built-in parser only supports HTML4 and XHTML syntax. In order to successfully
process HTML5 tags, please use the
Also, the `[api:SS_HTMLValue]` API underpinning the HTML processing parses the markup into a temporary object tree
which can be traversed and modified before saving. The built-in parser only supports HTML4 and XHTML syntax. In order
to successfully process HTML5 tags, please use the
['silverstripe/html5' module](https://github.com/silverstripe/silverstripe-html5).
## Recipes
### Customizing the "Insert" panels
### Customising the "Insert" panels
In the standard installation, you can insert links (internal/external/anchor/email),
images as well as flash media files. The forms used for preparing the new content element
@ -239,8 +254,8 @@ Note: The dropdown is only available if more than one config exists.
Each interface can have multiple fields of this type, each with their own toolbar to set formatting
and insert HTML elements. They do share one common set of dialogs for inserting links and other media though,
encapsulated in the `[api:HtmlEditorField_Toolbar]` class.
In the CMS, those dialogs are automatically instanciated, but in your own interfaces outside
of the CMS you have to take care of instanciation yourself:
In the CMS, those dialogs are automatically instantiate, but in your own interfaces outside
of the CMS you have to take care of instantiate yourself:
:::php
// File: mysite/code/MyController.php
@ -299,7 +314,3 @@ Now change the default spellchecker in `framework/thirdparty/tinymce-spellchecke
:::php
// ...
$config['general.engine'] = 'PSpell';
## Related
* [Howto: Extend the CMS Interface](../howto/extend-cms-interface)

View File

@ -0,0 +1,394 @@
title: GridField
summary: How to use the GridField class for managing tabular data.
# GridField
[api:GridField] is SilverStripe's implementation of data grids. The main purpose of the `FormField` is to display
tabular data in a format that is easy to view and modify. It can be thought of as a HTML table with some tricks.
:::php
$field = new GridField($name, $title, $list);
<div class="hint" markdown='1'>
GridField can only be used with `$list` data sets that are of the type `SS_List` such as `DataList` or `ArrayList`.
</div>
<div class="notice" markdown="1">
[api:GridField] powers the automated data UI of [api:ModelAdmin]. For more information about `ModelAdmin` see the
[Customizing the CMS](../../customizing_the_cms) guide.
</div>
Each `GridField` is built from a number of components grouped into the [api:GridFieldConfig]. Without any components,
a `GridField` has almost no functionality. The `GridFieldConfig` instance and the attached [api:GridFieldComponent] are
responsible for all the user interactions including formatting data to be readable, modifying data and performing any
actions such as deleting records.
**mysite/code/Page.php**
:::php
<?php
class Page extends SiteTree {
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->addFieldToTab('Root.Pages',
new GridField('Pages', 'All pages', SiteTree::get())
);
return $fields;
}
}
This will display a bare bones `GridField` instance under `Pages` tab in the CMS. As we have not specified the
`GridField` configuration, the default configuration is an instance of [api:GridFieldConfig_Base] which provides:
* [api:GridFieldToolbarHeader]
* [api:GridFieldSortableHeader]
* [api:GridFieldFilterHeader]
* [api:GridFieldDataColumns]
* [api:GridFieldPageCount]
* [api:GridFieldPaginator]
The configuration of those `GridFieldComponent` instances and the addition or subtraction of components is done through
the `getConfig()` method on `GridField`.
**mysite/code/Page.php**
:::php
<?php
class Page extends SiteTree {
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->addFieldToTab('Root.Pages',
$grid = GridField('Pages', 'All pages', SiteTree::get())
);
// GridField configuration
$config = $gridField->getConfig();
//
// Modification of existing components can be done by fetching that component.
// Consult the API documentation for each component to determine the configuration
// you can do.
//
$dataColumns = $config->getComponentByType('GridFieldDataColumns');
$dataColumns->setDisplayFields(array(
'Title' => 'Title',
'Link'=> 'URL',
'LastEdited' => 'Changed'
));
return $fields;
}
}
With the `GridFieldConfig` instance, we can modify the behavior of the `GridField`.
:::php
// `GridFieldConfig::create()` will create an empty configuration (no components).
$config = GridFieldConfig::create();
// add a component
$config->addComponent(new GridFieldDataColumns());
// Update the GridField with our custom configuration
$gridField->setConfig($config);
`GridFieldConfig` provides a number of methods to make setting the configuration easier. We can insert a component
before another component by passing the second parameter.
:::php
$config->addComponent(new GridFieldFilterHeader(), 'GridFieldDataColumns');
We can add multiple components in one call.
:::php
$config->addComponents(
new GridFieldDataColumns(),
new GridFieldToolbarHeader()
);
Or, remove a component.
:::php
$config->removeComponentsByType('GridFieldDeleteAction');
Fetch a component to modify it later on.
:::php
$component = $config->getComponentByType('GridFieldFilterHeader')
Here is a list of components for use bundled with the core framework. Many more components are provided by third-party
modules and extensions.
- [api:GridFieldToolbarHeader]
- [api:GridFieldSortableHeader]
- [api:GridFieldFilterHeader]
- [api:GridFieldDataColumns]
- [api:GridFieldDeleteAction]
- [api:GridFieldViewButton]
- [api:GridFieldEditButton]
- [api:GridFieldExportButton]
- [api:GridFieldPrintButton]
- [api:GridFieldPaginator]
- [api:GridFieldDetailForm]
## Bundled GridFieldConfig
As a shortcut, `GridFieldConfig` subclasses can define a list of `GridFieldComponent` objects to use. This saves
developers manually adding each component.
### GridFieldConfig_Base
A simple read-only and paginated view of records with sortable and searchable headers.
:::php
$config = GridFieldConfig_Base::create();
$gridField->setConfig($config);
// Is the same as adding the following components..
// .. new GridFieldToolbarHeader()
// .. new GridFieldSortableHeader()
// .. new GridFieldFilterHeader()
// .. new GridFieldDataColumns()
// .. new GridFieldPageCount('toolbar-header-right')
// .. new GridFieldPaginator($itemsPerPage)
### GridFieldConfig_RecordViewer
Similar to `GridFieldConfig_Base` with the addition support of the ability to view a `GridFieldDetailForm` containing
a read-only view of the data record.
<div class="info" markdown="1">
The data row show must be a `DataObject` subclass. The fields displayed in the read-only view come from
`DataObject::getCMSFields()`.
</div>
<div class="alert" markdown="1">
The `DataObject` class displayed must define a `canView()` method that returns a boolean on whether the user can view
this record.
</div>
:::php
$config = GridFieldConfig_RecordViewer::create();
$gridField->setConfig($config);
// Same as GridFieldConfig_Base with the addition of
// .. new GridFieldViewButton(),
// .. new GridFieldDetailForm()
### GridFieldConfig_RecordEditor
Similar to `GridFieldConfig_RecordViewer` with the addition support to edit or delete each of the records.
<div class="info" markdown="1">
The data row show must be a `DataObject` subclass. The fields displayed in the edit view come from
`DataObject::getCMSFields()`.
</div>
<div class="alert" markdown="1">
Permission control for editing and deleting the record uses the `canEdit()` and `canDelete()` methods on the
`DataObject` object.
</div>
:::php
$config = GridFieldConfig_RecordEditor::create();
$gridField->setConfig($config);
// Same as GridFieldConfig_RecordViewer with the addition of
// .. new GridFieldAddNewButton(),
// .. new GridFieldEditButton(),
// .. new GridFieldDeleteAction()
### GridFieldConfig_RelationEditor
Similar to `GridFieldConfig_RecordEditor`, but adds features to work on a record's has-many or many-many relationships.
As such, it expects the list used with the `GridField` to be a instance of `RelationList`.
:::php
$config = GridFieldConfig_RelationEditor::create();
$gridField->setConfig($config);
This configuration adds the ability to searched for existing records and add a relationship
(`GridFieldAddExistingAutocompleter`).
Records created or deleted through the `GridFieldConfig_RelationEditor` automatically update the relationship in the
database.
## GridFieldDetailForm
The `GridFieldDetailForm` component drives the record viewing and editing form. It takes its' fields from
`DataObject->getCMSFields()` method but can be customized to accept different fields via the
[api:GridFieldDetailForm->setFields] method.
:::php
$form = $gridField->getConfig()->getComponentByType('GridFieldDetailForm');
$form->setFields(new FieldList(
new TextField('Title')
));
### many_many_extraFields
The component also has the ability to load and save data stored on join tables when two records are related via a
"many_many" relationship, as defined through [api:DataObject::$many_many_extraFields]. While loading and saving works
transparently, you need to add the necessary fields manually, they're not included in the `getCMSFields()` scaffolding.
These extra fields act like usual form fields, but need to be "namespaced" in order for the `GridField` logic to detect
them as fields for relation extra data, and to avoid clashes with the other form fields.
The namespace notation is `ManyMany[<extradata-field-name>]`, so for example `ManyMany[MyExtraField]`.
:::php
<?php
class Team extends DataObject {
private static $db = array(
'Name' => 'Text'
);
public static $many_many = array(
'Players' => 'Player'
);
}
class Player extends DataObject {
private static $db = array(
'Name' => 'Text'
);
public static $many_many = array(
'Teams' => 'Team'
);
public static $many_many_extraFields = array(
'Teams' => array(
'Position' => 'Text'
)
);
public function getCMSFields() {
$fields = parent::getCMSFields();
if($this->ID) {
$teamFields = singleton('Team')->getCMSFields();
$teamFields->addFieldToTab(
'Root.Main',
// The "ManyMany[<extradata-name>]" convention
new TextField('ManyMany[Position]', 'Current Position')
);
$config = GridFieldConfig_RelationEditor::create();
$config->getComponentByType('GridFieldDetailForm')->setFields($teamFields);
$gridField = new GridField('Teams', 'Teams', $this->Teams(), $config);
$fields->findOrMakeTab('Root.Teams')->replaceField('Teams', $gridField);
}
return $fields;
}
}
## Flexible Area Assignment through Fragments
`GridField` layouts can contain many components other than the table itself, for example a search bar to find existing
relations, a button to add those, and buttons to export and print the current data. The `GridField` has certain defined
areas called `fragments` where these components can be placed.
The goal is for multiple components to share the same space, for example a header row. The built-in components:
- `header`/`footer`: Renders in a `<thead>`/`<tfoot>`, should contain table markup
- `before`/`after`: Renders before/after the actual `<table>`
- `buttons-before-left`/`buttons-before-right`/`buttons-after-left`/`buttons-after-right`:
Renders in a shared row before the table. Requires [api:GridFieldButtonRow].
These built-ins can be used by passing the fragment names into the constructor of various components. Note that some
[api:GridFieldConfig] classes will already have rows added to them. The following example will add a print button at the
bottom right of the table.
:::php
$config->addComponent(new GridFieldButtonRow('after'));
$config->addComponent(new GridFieldPrintButton('buttons-after-right'));
### Creating your own Fragments
Fragments are designated areas within a `GridField` which can be shared between component templates. You can define
your own fragments by using a `\$DefineFragment' placeholder in your components' template. This example will simply
create an area rendered before the table wrapped in a simple `<div>`.
:::php
<?php
class MyAreaComponent implements GridField_HTMLProvider {
public function getHTMLFragments( $gridField) {
return array(
'before' => '<div class="my-area">$DefineFragment(my-area)</div>'
);
}
}
<div class="notice" markdown="1">
Please note that in templates, you'll need to escape the dollar sign on `\$DefineFragment`. These are specially
processed placeholders as opposed to native template syntax.
</div>
Now you can add other components into this area by returning them as an array from your
[api:GridFieldComponent->getHTMLFragments()] implementation:
:::php
<?php
class MyShareLinkComponent implements GridField_HTMLProvider {
public function getHTMLFragments( $gridField) {
return array(
'my-area' => '<a href>...</a>'
);
}
}
Your new area can also be used by existing components, e.g. the [api:GridFieldPrintButton]
:::php
new GridFieldPrintButton('my-component-area');
## Creating a Custom GridFieldComponent
Customizing a `GridField` is easy, applications and modules can provide their own `GridFieldComponent` instances to add
functionality. See [How to Create a GridFieldComponent](../how_tos/create_a_gridfield_component).
## Creating a Custom GridField_ActionProvider
[api:GridField_ActionProvider] provides row level actions such as deleting a record. See
[How to Create a GridField_ActionProvider](../how_tos/create_a_gridfield_actionprovider).
## Saving the GridField State
`GridState` is a class that is used to contain the current state and actions on the `GridField`. It's transfered
between page requests by being inserted as a hidden field in the form.
The `GridState_Component` sets and gets data from the `GridState`.
## API Documentation
* [api:GridField]
* [api:GridFieldConfig]
* [api:GridFieldComponent]

View File

@ -0,0 +1,151 @@
title: How to Encapsulate Forms
# How to Encapsulate Forms
Form definitions can often get long, complex and often end up cluttering up a `Controller` definition. We may also want
to reuse the `Form` across multiple `Controller` classes rather than just one. A nice way to encapsulate the logic and
code for a `Form` is to create it as a subclass to `Form`. Let's look at a example of a `Form` which is on our
`Controller` but would be better written as a subclass.
**mysite/code/Page.php**
:::php
<?php
class Page_Controller extends ContentController {
public function SearchForm() {
$fields = new FieldList(
HeaderField::create('Header', 'Step 1. Basics'),
OptionsetField::create('Type', '', array(
'foo' => 'Search Foo',
'bar' => 'Search Bar',
'baz' => 'Search Baz'
)),
CompositeField::create(
HeaderField::create('Header2', 'Step 2. Advanced '),
CheckboxSetField::create('Foo', 'Select Option', array(
'qux' => 'Search Qux'
)),
CheckboxSetField::create('Category', 'Category', array(
'Foo' => 'Foo',
'Bar' => 'Bar'
)),
NumericField::create('Minimum', 'Minimum'),
NumericField::create('Maximum', 'Maximum')
)
);
$actions = new FieldList(
FormAction::create('doSearchForm', 'Search')
);
$required = new RequiredFields(array(
'Type'
));
$form = new Form($this, 'SearchForm', $fields, $actions, $required);
$form->setFormMethod('GET');
$form->addExtraClass('no-action-styles');
$form->disableSecurityToken();
$form->loadDataFrom($_REQUEST);
return $form;
}
..
}
Now that is a bit of code to include on our controller and generally makes the file look much more complex than it
should be. Good practice would be to move this to a subclass and create a new instance for your particular controller.
**mysite/code/forms/SearchForm.php**
:::php
<?php
class SearchForm extends Form {
/**
* Our constructor only requires the controller and the name of the form
* method. We'll create the fields and actions in here.
*
*/
public function __construct($controller, $name) {
$fields = new FieldList(
HeaderField::create('Header', 'Step 1. Basics'),
OptionsetField::create('Type', '', array(
'foo' => 'Search Foo',
'bar' => 'Search Bar',
'baz' => 'Search Baz'
)),
CompositeField::create(
HeaderField::create('Header2', 'Step 2. Advanced '),
CheckboxSetField::create('Foo', 'Select Option', array(
'qux' => 'Search Qux'
)),
CheckboxSetField::create('Category', 'Category', array(
'Foo' => 'Foo',
'Bar' => 'Bar'
)),
NumericField::create('Minimum', 'Minimum'),
NumericField::create('Maximum', 'Maximum')
)
);
$actions = new FieldList(
FormAction::create('doSearchForm', 'Search')
);
$required = new RequiredFields(array(
'Type'
));
// now we create the actual form with our fields and actions defined
// within this class
parent::__construct($controller, $name, $fields, $actions, $required);
// any modifications we need to make to the form.
$this->setFormMethod('GET');
$this->addExtraClass('no-action-styles');
$this->disableSecurityToken();
$this->loadDataFrom($_REQUEST);
}
}
Our controller will now just have to create a new instance of this form object. Keeping the file light and easy to read.
**mysite/code/Page.php**
:::php
<?php
class Page_Controller extends ContentController {
private static $allowed_actions = array(
'SearchForm',
);
public function SearchForm() {
return new SearchForm($this, 'SearchForm');
}
}
Form actions can also be defined within your `Form` subclass to keep the entire form logic encapsulated.
## Related Documentation
* [Introduction to Forms](../introduction)
## API Documentation
* [api:Form]

View File

@ -0,0 +1,52 @@
title: How to Create Lightweight Form
# How to Create Lightweight Form
Out of the box, SilverStripe provides a robust and reusable set of HTML markup for [api:FormFields], however this can
sometimes produce markup which is unnecessarily bloated.
For example, a basic search form. We want to use the [api:Form] API to handle the form but we may want to provide a
totally custom template to meet our needs. To do this, we'll provide the class with a unique template through
`setTemplate`.
**mysite/code/Page.php**
:::php
<?php
public function SearchForm() {
$fields = new FieldList(
TextField::create('q')
);
$actions = new FieldList(
FormAction::create('doSearch', 'Search')
);
$form = new Form($this, 'SearchForm', $fields, $actions);
$form->setTemplate('SearchForm');
return $form;
}
**mysite/templates/Includes/SearchForm.ss**
:::ss
<form $FormAttributes>
<fieldset>
$Fields.dataFieldByName(q)
</fieldset>
<div class="Actions">
<% loop $Actions %>$Field<% end_loop %>
</div>
</form>
`SearchForm.ss` will be executed within the scope of the `Form` object so has access to any of the methods and
properties on [api:Form] such as `$Fields` and `$Actions`.
<div class="notice">
To understand more about Scope or the syntax for custom templates, read the [Templates](../../templates) guide.
</div>

View File

@ -0,0 +1,49 @@
A single component often uses a number of interfaces.
### GridField_HTMLProvider
Provides HTML for the header/footer rows in the table or before/after the template.
Examples:
- A header html provider displays a header before the table
- A pagination html provider displays pagination controls under the table
- A filter html fields displays filter fields on top of the table
- A summary html field displays sums of a field at the bottom of the table
### GridField_ColumnProvider
Add a new column to the table display body, or modify existing columns. Used once per record/row.
Examples:
- A data columns provider that displays data from the list in rows and columns.
- A delete button column provider that adds a delete button at the end of the row
### GridField_ActionProvider
Action providers runs actions, some examples are:
- A delete action provider that deletes a DataObject.
- An export action provider that will export the current list to a CSV file.
### GridField_DataManipulator
Modifies the data list. In general, the data manipulator will make use of `GridState` variables
to decide how to modify the data list.
Examples:
- A paginating data manipulator can apply a limit to a list (show only 20 records)
- A sorting data manipulator can sort the Title in a descending order.
### GridField_URLHandler
Sometimes an action isn't enough, we need to provide additional support URLs for the grid. It
has a list of URL's that it can handle and the GridField passes request on to URLHandlers on matches.
Examples:
- A pop-up form for editing a record's details.
- JSON formatted data used for javascript control of the gridfield.

View File

@ -55,7 +55,7 @@ The reason it's standard practice to name the form function 'Form' is so that we
If you now create a ContactPage in the CMS (making sure you have rebuilt the database and flushed the templates /dev/build?flush=all) and visit the page, you will now see a contact form.
![](../_images/howto_contactForm.jpg)
![](..//_images/howto_contactForm.jpg)
Now that we have a contact form, we need some way of collecting the data submitted. We do this by creating a function on the controller with the same name as the form action. In this case, we create the function 'submit' on the ContactPage_Controller class.

View File

@ -0,0 +1,22 @@
title: Forms
summary: Capture user information through Forms. This guide will work through how to create SilverStripe forms, adding and modifying fields and how to handle form submissions.
introduction: This guide will work through how to create SilverStripe forms, adding and modifying fields and how to handle form submissions.
The [api:Form] class provides a way to create interactive forms in your web application with very little effort.
SilverStripe handles generating the correct semantic HTML markup for the form and each of the fields, as well as the
framework for dealing with submissions and validation.
[CHILDREN Exclude="How_Tos,Fields"]
## FormField Documentation
[CHILDREN Folder="Fields"]
## How to's
[CHILDREN Folder="How_Tos"]
## API Documentation
* [api:Form]
* [api:FormField]

View File

@ -0,0 +1,317 @@
title: Configuration API
summary: SilverStripe's YAML based Configuration API for setting runtime configuration.
# Configuration API
SilverStripe comes with a comprehensive code based configuration system through the [api:Config] class. It primarily
relies on declarative [YAML](http://en.wikipedia.org/wiki/YAML) files, and falls back to procedural PHP code, as well
as PHP static variables.
The Configuration API can be seen as separate from other forms of variables in the SilverStripe system due to three
properties API:
- Configuration is **per class**, not per instance.
- Configuration is normally set once during initialization and then not changed.
- Configuration is normally set by a knowledgeable technical user, such as a developer, not the end user.
<div class="notice" markdown="1">
For providing content editors or CMS users a place to manage configuration see the [SiteConfig](siteconfig) module.
</div>
## Configuration Properties
Configuration values are static properties on any SilverStripe class. These should be at the top of the class and
marked with a `@config` docblock. The API documentation will also list the static properties for the class. They should
be marked `private static` and follow the `lower_case_with_underscores` structure.
**mysite/code/MyClass.php**
:::php
<?php
class MyClass extends Page {
/**
* @config
*/
private static $option_one = true;
/**
* @config
*/
private static $option_two = array();
// ..
}
## Accessing and Setting Configuration Properties
This can be done by calling the static method `[api:Config::inst]`, like so:
:::php
$config = Config::inst()->get('MyClass');
Or through the `config()` object on the class.
$config = $this->config();
There are three public methods available on the instance. `get($class, $variable)`, `remove($class, $variable)` and
`update($class, $variable, $value)`.
<div class="notice" markdown="1">
There is no "set" method. It is not possible to completely set the value of a classes' property. `update` adds new
values that are treated as the highest priority in the merge, and remove adds a merge mask that filters out values.
</div>
To set those configuration options on our previously defined class we can define it in a `YAML` file.
**mysite/_config/app.yml**
:::yml
MyClass:
option_one: false
option_two:
- Foo
- Bar
- Baz
To use those variables in your application code:
:::php
$me = new MyClass();
echo $me->config()->option_one;
// returns false
echo implode(', ', $me->config()->option_two);
// returns 'Foo, Bar, Baz'
echo Config::inst()->get('MyClass', 'option_one');
// returns false
echo implode(', ', Config::inst()->get('MyClass', 'option_two'));
// returns 'Foo, Bar, Baz'
Config::inst()->update('MyClass', 'option_one', true);
echo Config::inst()->get('MyClass', 'option_one');
// returns true
// You can also use the static version
MyClass::config()->option_two = array(
'Qux'
);
echo implode(', ', MyClass::config()->option_one);
// returns 'Qux'
<div class="notice" markdown="1">
There is no way currently to restrict read or write access to any configuration property, or influence/check the values
being read or written.
</div>
## Configuration Values
Each configuration property can contain either a literal value (`'foo'`), integer (`2`), boolean (`true`) or an array.
If the value is an array, each value in the array may also be one of those types.
The value of any specific class configuration property comes from several sources. These sources do not override each
other - instead the values from each source are merged together to give the final configuration value, using these
rules:
- If the value is an array, each array is added to the _beginning_ of the composite array in ascending priority order.
If a higher priority item has a non-integer key which is the same as a lower priority item, the value of those items
is merged using these same rules, and the result of the merge is located in the same location the higher priority item
would be if there was no key clash. Other than in this key-clash situation, within the particular array, order is preserved.
- If the value is not an array, the highest priority value is used without any attempt to merge
<div class="alert" markdown="1">
The exception to this is "false-ish" values - empty arrays, empty strings, etc. When merging a non-false-ish value with
a false-ish value, the result will be the non-false-ish value regardless of priority. When merging two false-ish values
the result will be the higher priority false-ish value.
</div>
The locations that configuration values are taken from in highest -> lowest priority order are:
- Any values set via a call to Config#update
- The configuration values taken from the YAML files in `_config/` directories (internally sorted in before / after
order, where the item that is latest is highest priority)
- Any static set on an "additional static source" class (such as an extension) named the same as the name of the property
- Any static set on the class named the same as the name of the property
- The composite configuration value of the parent class of this class
<div class="notice">
It is an error to have mixed types of the same named property in different locations. An error will not necessarily
be raised due to optimizations in the lookup code.
</div>
## Configuration Masks
At some of these levels you can also set masks. These remove values from the composite value at their priority point
rather than add.
$actionsWithoutExtra = $this->config()->get(
'allowed_actions', Config::UNINHERITED
);
They are much simpler. They consist of a list of key / value pairs. When applied against the current composite value
- If the composite value is a sequential array, any member of that array that matches any value in the mask is removed
- If the composite value is an associative array, any member of that array that matches both the key and value of any
pair in the mask is removed
- If the composite value is not an array, if that value matches any value in the mask it is removed
## Configuration YAML Syntax and Rules
Each module can have a directory immediately underneath the main module directory called `_config/`. Inside this
directory you can add YAML files that contain values for the configuration system.
<div class="info" markdown="1">
The name of the files within the applications `_config` directly are arbitrary. Our examples use
`mysite/_config/app.yml` but you can break this file down into smaller files, or clearer patterns like `extensions.yml`,
`email.yml` if you want. For add-on's and modules, it is recommended that you name them with `<module_name>.yml`.
</div>
The structure of each YAML file is a series of headers and values separated by YAML document separators.
:::yml
---
Name: adminroutes
After:
- '#rootroutes'
- '#coreroutes'
---
Director:
rules:
'admin': 'AdminRootController'
---
<div class="info">
If there is only one set of values the header can be omitted.
</div>
Each value section of a YAML file has:
- A reference path, made up of the module name, the config file name, and a fragment identifier Each path looks a
little like a URL and is of this form: `module/file#fragment`.
- A set of rules for the value section's priority relative to other value sections
- A set of rules that might exclude the value section from being used
The fragment identifier component of the reference path and the two sets of rules are specified for each value section
in the header section that immediately precedes the value section.
- "module" is the name of the module this YAML file is in.
- "file" is the name of this YAML file, stripped of the extension (so for routes.yml, it would be routes).
- "fragment" is a specified identifier. It is specified by putting a `Name: {fragment}` key / value pair into the
header section. If you don't specify a name, a random one will be assigned.
This reference path has no affect on the value section itself, but is how other header sections refer to this value
section in their priority chain rules.
## Before / After Priorities
Values for a specific class property can be specified in several value sections across several modules. These values are
merged together using the same rules as the configuration system as a whole.
However unlike the configuration system, there is no inherent priority amongst the various value sections.
Instead, each value section can have rules that indicate priority. Each rule states that this value section must come
before (lower priority than) or after (higher priority than) some other value section.
To specify these rules you add an "After" and/or "Before" key to the relevant header section. The value for these
keys is a list of reference paths to other value sections. A basic example:
:::yml
---
Name: adminroutes
After:
- '#rootroutes'
- '#coreroutes'
---
Director:
rules:
'admin': 'AdminRootController'
---
You do not have to specify all portions of a reference path. Any portion may be replaced with a wildcard "\*", or left
out all together. Either has the same affect - that portion will be ignored when checking a value section's reference
path, and will always match. You may even specify just "\*", which means "all value sections".
When a particular value section matches both a Before _and_ an After rule, this may be a problem. Clearly
one value section can not be both before _and_ after another. However when you have used wildcards, if there
was a difference in how many wildcards were used, the one with the least wildcards will be kept and the other one
ignored.
The value section above has two rules:
- It must be merged in before (lower priority than) all other value sections
- It must be merged in after (higher priority than) any value section with a fragment name of "rootroutes"
In this case there would appear to be a problem - adminroutes can not be both before all other value sections _and_
after value sections with a name of `rootroutes`. However because `\*` has three wildcards
(it is the equivalent of `\*/\*#\*`) but `#rootroutes` only has two (it is the equivalent of `\*/\*#rootroutes`).
In this case `\*` means "every value section _except_ ones that have a fragment name of rootroutes".
<div class="alert" markdown="1">
It is possible to create chains that are unsolvable. For instance, A must be before B, B must be before C, C must be
before A. In this case you will get an error when accessing your site.
</div>
## Exclusionary rules
Some value sections might only make sense under certain environmental conditions - a class exists, a module is
installed, an environment variable or constant is set, or SilverStripe is running in a certain environment mode (live,
dev, etc).
To accommodate this, value sections can be filtered to only be used when either a rule matches or doesn't match the
current environment.
To achieve this, add a key to the related header section, either `Only` when the value section should be included
only when all the rules contained match, or `Except` when the value section should be included except when all of the
rules contained match.
You then list any of the following rules as sub-keys, with informational values as either a single value or a list.
- 'classexists', in which case the value(s) should be classes that must exist
- 'moduleexists', in which case the value(s) should be modules that must exist
- 'environment', in which case the value(s) should be one of "live", "test" or "dev" to indicate the SilverStripe
mode the site must be in
- 'envvarset', in which case the value(s) should be environment variables that must be set
- 'constantdefined', in which case the value(s) should be constants that must be defined
For instance, to add a property to "foo" when a module exists, and "bar" otherwise, you could do this:
:::yml
---
Only:
moduleexists: 'MyFineModule'
---
MyClass:
property: 'foo'
---
Except:
moduleexists: 'MyFineModule'
---
MyClass:
property: 'bar'
---
<div class="alert" markdown="1">
When you have more than one rule for a nested fragment, they're joined like
`FRAGMENT_INCLUDED = (ONLY && ONLY) && !(EXCEPT && EXCEPT)`.
That is, the fragment will be included if all Only rules match, except if all Except rules match.
</div>
<div class="alert" markdown="1">
Due to YAML limitations, having multiple conditions of the same kind (say, two `EnvVarSet` in one "Only" block)
will result in only the latter coming through.
</div>
## API Documentation
* [api:Config]

View File

@ -0,0 +1,72 @@
title: SiteConfig
summary: Content author configuration through the SiteConfig module.
# SiteConfig
The `SiteConfig` module provides a generic interface for managing site wide settings or functionality which is used
throughout the site. Out of the box this includes selecting the current site theme, site name and site wide access.
## Accessing variables
`SiteConfig` options can be accessed from any template by using the $SiteConfig variable.
:::ss
$SiteConfig.Title
$SiteConfig.Tagline
<% with $SiteConfig %>
$Title $AnotherField
<% end_loop %>
To access variables in the PHP:
:::php
$config = SiteConfig::current_site_config();
echo $config->Title;
// returns "Website Name"
## Extending SiteConfig
To extend the options available in the panel, define your own fields via a [api:DataExtension].
**mysite/code/extensions/CustomSiteConfig.php**
:::php
<?php
class CustomSiteConfig extends DataExtension {
private static $db = array(
'FooterContent' => 'HTMLText'
);
public function updateCMSFields(FieldList $fields) {
$fields->addFieldToTab("Root.Main",
new HTMLEditorField("FooterContent", "Footer Content")
);
}
}
Then activate the extension.
**mysite/_config/app.yml**
:::yml
SiteConfig:
extensions:
- CustomSiteConfig
<div class="notice" markdown="1">
After adding the class and the YAML change, make sure to rebuild your database by visiting http://yoursite.com/dev/build.
You may also need to reload the screen with a `flush=1` i.e http://yoursite.com/admin/settings?flush=1.
</div>
You can define as many extensions for `SiteConfig` as you need. For example, if you're developing a module and what to
provide the users a place to configure settings then the `SiteConfig` panel is the place to go it.
## API Documentation
* `[api:SiteConfig]`

View File

@ -0,0 +1,14 @@
title: Environment Variables
summary: Site configuration variables such as database connection details, environment type and remote login information.
# Environment Variables
Environment specific variables like database connection details, API keys and other server configuration should be kept
outside the application code in a separate `_ss_environment.php` file. This file is stored outside the web root and
version control for security reasons.
For more information on the environment file, see the [Environment Management](../../getting_started/environment_management/)
documentation.
Data which isn't sensitive that can be in version control but is mostly static such as constants is best suited to be
included through the [Configuration API](configuration) based on the standard environment types (dev / test / live).

View File

@ -0,0 +1,5 @@
title: Configuration
summary: SilverStripe provides several ways to store and modify your application settings. Learn about site wide settings and the YAML based configuration system.
introduction: SilverStripe provides several ways to store and modify your application settings. Learn about site wide settings and the YAML based configuration system.
[CHILDREN]

View File

@ -0,0 +1,107 @@
title: Modules
summary: Extend core functionality with modules.
# Modules
SilverStripe is designed to be a modular application system - even the CMS is simply a module that plugs into the core
framework.
A module is a collection of classes, templates, and other resources that is loaded into a top-level directory such as
the `framework`, `cms` or `mysite` folders. The only thing that identifies a folder as a SilverStripe module is the
existence of a `_config` directory or `_config.php` at the top level of the directory.
mysite/
|
+-- _config/
+-- code/
+-- ..
|
my_custom_module/
|
+-- _config/
+-- ...
SilverStripe will automatically include any PHP classes and templates from within your module when you next flush your
cache.
<div class="info" markdown="1">
In a default SilverStripe installation, even resources in `framework` and `mysite` are treated in exactly the same as
every other module. Order of priority is usually alphabetical unless stated.
</div>
Creating a module is a good way to re-use abstract code and templates across multiple projects. SilverStripe already
has certain modules included, for example the `cms` module and core functionality such as commenting and spam protection
are also abstracted into modules allowing developers the freedom to choose what they want.
## Finding Modules
* [Official module list on silverstripe.org](http://addons.silverstripe.org/)
* [Packagist.org "silverstripe" tag](https://packagist.org/search/?tags=silverstripe)
* [Github.com "silverstripe" search](https://github.com/search?q=silverstripe&ref=commandbar)
## Installation
Modules should exist in the root folder of your SilverStripe installation.
<div class="info" markdown="1">
The root directory is the one containing the *framework* and *mysite* subdirectories. If your site is installed under
`/Users/sam.minnee/Sites/website/` your modules will go in the `/Users/sam.minnee/Sites/website/` directory.
</div>
<div class="notice" markdown="1">
After you add or remove modules make sure you rebuild the database by going to http://yoursite.com/dev/build?flush=1
</div>
### From Composer
Our preferred way to manage module dependencies is through the [Composer](http://getcomposer.org) package manager. It
enables you to install modules from specific versions, checking for compatibilities between modules and even allowing
to track development branches of them. To install modules using this method, you will first need to setup SilverStripe
with [Composer](../../getting_started/composer).
Each module has a unique identifier, consisting of a vendor prefix and name. For example, the "blog" module has the
identifier `silverstripe/blog` as it is published by *silverstripe*. To install, use the following command executed in
the root folder:
:::bash
composer require "silverstripe/blog" "*@stable"
This will fetch the latest compatible stable version of the module. To install a specific version of the module give the
tag name.
:::bash
composer require "silverstripe/blog" "1.1.0"
<div class="info" markdown="1">
To lock down to a specific version, branch or commit, read up on
[Composer "lock" files](http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file).
</div>
## From an Archive Download
<div class="alert" markdown="1">
Some modules might not work at all with this approach since they rely on the
Composer [autoloader](http://getcomposer.org/doc/01-basic-usage.md#autoloading), additional modules or post-install
hooks, so we recommend using Composer.
</div>
Alternatively, you can download the archive file from the [modules page](http://www.silverstripe.org/modules) and
extract it to the root folder mentioned above.
<div class="notice" markdown="1">
The main folder extracted from the archive might contain the version number or additional "container" folders above the
actual module codebase. You need to make sure the folder name is the correct name of the module (e.g. "blog/" rather
than "silverstripe-blog/"). This folder should contain a `_config/` directory. While the module might register and
operate in other structures, paths to static files such as CSS or JavaScript won't work.
</div>
## Publishing your own SilverStripe module
See the [How to Publish a SilverStripe Module](how_tos/publish_a_module) for details on how to publish your SilverStripe
modules with the community
## Related
* [How to Publish a SilverStripe Module](how_tos/publish_a_module)

View File

@ -0,0 +1,283 @@
title: Extensions
summary: Extensions and DataExtensions let you modify and augment objects transparently.
# Extensions and DataExtensions
An [api:Extension] allows for adding additional functionality to a [api:Object] or modifying existing functionality
without the hassle of creating a subclass. Developers can add Extensions to any [api:Object] subclass within core, modules
or even their own code to make it more reusable.
Extensions are defined as subclasses of either [api:DataExtension] for extending a [api:DataObject] subclass or
the [api:Extension] class for non DataObject subclasses (such as [api:Controllers])
**mysite/code/extensions/MyMemberExtension.php**
:::php
<?php
class MyMemberExtension extends DataExtension {
private static $db = array(
'DateOfBirth' => 'SS_Datetime'
);
public function SayHi() {
// $this->owner refers to the original instance. In this case a `Member`.
return "Hi ". $this->owner->Name;
}
}
<div class="info" markdown="1">
Convention is for extension class names to end in `Extension`. This isn't a requirement but makes it clearer
</div>
After this class has been created, it does not yet apply it to any object. We need to tell SilverStripe what classes
we want to add the `MyMemberExtension` too. To activate this extension, add the following via the [Configuration API](../configuration).
**mysite/_config/app.yml**
:::yml
Member:
extensions:
- MyMemberExtension
Alternatively, we can add extensions through PHP code (in the `_config.php` file).
:::php
Member::add_extension('MyMemberExtension');
This class now defines a `MyMemberExtension` that applies to all `Member` instances on the website. It will have
transformed the original `Member` class in two ways:
* Added a new [api:SS_Datetime] for the users date of birth, and;
* Added a `SayHi` method to output `Hi <User>`
From within the extension we can add more functions, database fields, relations or other properties and have them added
to the underlying `DataObject` just as if they were added to the original `Member` class but without the need to edit
that file directly.
### Adding Database Fields
Extra database fields can be added with a extension in the same manner as if they 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.
**mysite/code/extensions/MyMemberExtension.php**
:::php
<?php
class MyMemberExtension extends DataExtension {
private static $db = array(
'Position' => 'Varchar',
);
private static $has_one = array(
'Image' => 'Image',
);
public function SayHi() {
// $this->owner refers to the original instance. In this case a `Member`.
return "Hi ". $this->owner->Name;
}
}
**mysite/templates/Page.ss**
:::ss
$CurrentMember.Position
$CurrentMember.Image
## Adding Methods
Methods that have a unique name will be called as part of the `__call` method on [api:Object]. In the previous example
we added a `SayHi` method which is unique to our extension.
**mysite/templates/Page.ss**
:::ss
<p>$CurrentMember.SayHi</p>
// "Hi Sam"
**mysite/code/Page.php**
:::php
$member = Member::currentUser();
echo $member->SayHi;
// "Hi Sam"
## Modifying Existing Methods
If the `Extension` needs to modify an existing method it's a little tricker. It requires that the method you want to
customize has provided an *Extension Hook* in the place where you want to modify the data. An *Extension Hook* is done
through the `[api:Object->extend]` method.
**framework/security/Member.php**
:::php
public function getValidator() {
// ..
$this->extend('updateValidator', $validator);
// ..
}
Extension Hooks can be located anywhere in the method and provide a point for any `Extension` instances to modify the
variables at that given point. In this case, the core function `getValidator` on the `Member` class provides an
`updateValidator` hook for developers to modify the core method. The `MyMemberExtension` would modify the core member's
validator by defining the `updateValidator` method.
**mysite/code/extensions/MyMemberExtension.php**
:::php
<?php
class MyMemberExtension extends DataExtension {
// ..
public function updateValidator($validator) {
// we want to make date of birth required for each member
$validator->addRequiredField('DateOfBirth');
}
}
<div class="info" markdown="1">
The `$validator` parameter is passed by reference, as it is an object.
</div>
Another common example of when you will want to modify a method is to update the default CMS fields for an object in an
extension. The `CMS` provides a `updateCMSFields` Extension Hook to tie into.
:::php
<?php
class MyMemberExtension extends DataExtension {
private static $db = array(
'Position' => 'Varchar',
);
private static $has_one = array(
'Image' => 'Image',
);
public function updateCMSFields(FieldList $fields) {
$fields->push(new TextField('Position'));
$fields->push(new UploadField('Image', 'Profile Image'));
}
}
<div class="notice" markdown="1">
If you're providing a module or working on code that may need to be extended by other code, it should provide a *hook*
which allows an Extension to modify the results.
</div>
:::php
public function Foo() {
$foo = // ..
$this->extend('updateFoo', $foo);
return $foo;
}
The convention for extension hooks is to provide an `update{$Function}` hook at the end before you return the result. If
you need to provide extension hooks at the beginning of the method use `before{..}`.
## Owner
In your [api:Extension] class you can only refer to the source object through the `owner` property on the class as
`$this` will refer to your `Extension` instance.
:::php
<?php
class MyMemberExtension extends DataExtension {
public function updateFoo($foo) {
// outputs the original class
var_dump($this->owner);
}
}
## Checking to see if an Object has an Extension
To see what extensions are currently enabled on an object, use [api:Object->getExtensionInstances] and
[api:Object->hasExtension]
:::php
$member = Member::currentUser();
print_r($member->getExtensionInstances());
if($member->hasExtension('MyCustomMemberExtension')) {
// ..
}
## Object extension injection points
`Object` has two additional methods, `beforeExtending` and `afterExtending`, each of which takes a method name and a
callback to be executed immediately before and after `Object::extend()` is called on extensions.
This is useful in many cases where working with modules such as `Translatable` which operate on `DataObject` fields
that must exist in the `FieldList` at the time that `$this->extend('UpdateCMSFields')` is called.
<div class="notice" markdown='1'>
Please note that each callback is only ever called once, and then cleared, so multiple extensions to the same function
require that a callback is registered each time, if necessary.
</div>
Example: A class that wants to control default values during object initialization. The code needs to assign a value
if not specified in `self::$defaults`, but before extensions have been called:
:::php
function __construct() {
$self = $this;
$this->beforeExtending('populateDefaults', function() use ($self) {
if(empty($self->MyField)) {
$self->MyField = 'Value we want as a default if not specified in $defaults, but set before extensions';
}
});
parent::__construct();
}
Example 2: User code can intervene in the process of extending cms fields.
<div class="notice" markdown="1">
This method is preferred to disabling, enabling, and calling field extensions manually.
</div>
:::php
public function getCMSFields() {
$this->beforeUpdateCMSFields(function($fields) {
// Include field which must be present when updateCMSFields is called on extensions
$fields->addFieldToTab("Root.Main", new TextField('Detail', 'Details', null, 255));
});
$fields = parent::getCMSFields();
// ... additional fields here
return $fields;
}
## Related Documentaion
* [Injector](injector/)
* [api:Object::useCustomClass]
## API Documentation
* [api:Extension]
* [api:DataExtension]

View File

@ -0,0 +1,224 @@
title: Shortcodes
summary: Flexible content embedding
# Shortcodes
The [api:ShortcodeParser] API is simple parser that allows you to map specifically formatted content to a callback to
transform them into something else. You might know this concept from forum software which don't allow you to insert
direct HTML, instead resorting to a custom syntax.
In the CMS, authors often want to insert content elements which go beyond standard formatting, at an arbitrary position
in their WYSIWYG editor. Shortcodes are a semi-technical solution for this. A good example would be embedding a 3D file
viewer or a Google Map at a certain location.
:::php
$text = "<h1>My Map</h1>[map]"
// Will output
// <h1>My Map</h1><iframe ..></iframe>
Here's some syntax variations:
:::php
[my_shortcode]
#
[my_shortcode /]
#
[my_shortcode,myparameter="value"]
#
[my_shortcode,myparameter="value"]Enclosed Content[/my_shortcode]
Shortcodes are automatically parsed on any database field which is declared as [api:HTMLValue] or [api:HTMLText],
when rendered into a template. This means you can use shortcodes on common fields like `SiteTree.Content`, and any
other `[api:DataObject::$db]` definitions of these types.
Other fields can be manually parsed with shortcodes through the `parse` method.
:::php
$text = "My awesome [my_shortcode] is here.";
ShortcodeParser::get_active()->parse($text);
## Defining Custom Shortcodes
First we need to define a callback for the shortcode.
**mysite/code/Page.php**
:::php
<?php
class Page extends SiteTree {
private static $casting = array(
'MyShortCodeMethod' => 'HTMLText'
);
public function MyShortCodeMethod($arguments, $content = null, $parser = null, $tagName) {
return str_replace($content, "<em>$content</em>", $this->Content);
}
}
These parameters are passed to the `MyShortCodeMethod` callback:
- Any parameters attached to the shortcode as an associative array (keys are lower-case).
- Any content enclosed within the shortcode (if it is an enclosing shortcode). Note that any content within this
will not have been parsed, and can optionally be fed back into the parser.
- The ShortcodeParser instance used to parse the content.
- The shortcode tag name that was matched within the parsed content.
- An associative array of extra information about the shortcode being parsed. For example, if the shortcode is
is inside an attribute, the `element` key contains a reference to the parent `DOMElement`, and the `node`
key the attribute's `DOMNode`.
To register a shortcode you call the following.
**mysite/_config.php**
:::php
// ShortcodeParser::get('default')->register($shortcode, $callback);
ShortcodeParser::get('default')->register('my_shortcode', array('Page', 'MyShortCodeMethod'));
## Built-in Shortcodes
SilverStripe comes with several shortcode parsers already.
### Links
Internal page links keep references to their database IDs rather than the URL, in order to make these links resilient
against moving the target page to a different location in the page tree. This is done through the `[sitetree_link]`
shortcode, which takes an `id` parameter.
:::php
<a href="[sitetree_link,id=99]">
Links to internal `File` database records work exactly the same, but with the `[file_link]` shortcode.
:::php
<a href="[file_link,id=99]">
### Media (Photo, Video and Rich Content)
Many media formats can be embedded into websites through the `<object>` tag, but some require plugins like Flash or
special markup and attributes. OEmbed is a standard to discover these formats based on a simple URL, for example a
Youtube link pasted into the "Insert Media" form of the CMS.
Since TinyMCE can't represent all these variations, we're showing a placeholder instead, and storing the URL with a
custom `[embed]` shortcode.
[embed width=480 height=270 class=left thumbnail=http://i1.ytimg.com/vi/lmWeD-vZAMY/hqdefault.jpg?r=8767]
http://www.youtube.com/watch?v=lmWeD-vZAMY
[/embed]
### Attribute and element scope
HTML with unprocessed shortcodes in it is still valid HTML. As a result, shortcodes can be in two places in HTML:
- In an attribute value, like so: `<a title="[title]">link</a>`
- In an element's text, like so: `<p>Some text [shortcode] more text</p>`
The first is called "element scope" use, the second "attribute scope"
You may not use shortcodes in any other location. Specifically, you can not use shortcodes to generate attributes or
change the name of a tag. These usages are forbidden:
<[paragraph]>Some test</[paragraph]>
<a [titleattribute]>link</a>
You may need to escape text inside attributes `>` becomes `&gt;`, You can include HTML tags inside a shortcode tag, but
you need to be careful of nesting to ensure you don't break the output.
:::ss
<!-- Good -->
<div>
[shortcode]
<p>Caption</p>
[/shortcode]
</div>
<!-- Bad: -->
<div>
[shortcode]
</div>
<p>
[/shortcode]
</p>
### Location
Element scoped shortcodes have a special ability to move the location they are inserted at to comply with HTML lexical
rules. Take for example this basic paragraph tag:
<p><a href="#">Head [figure,src="assets/a.jpg",caption="caption"] Tail</a></p>
When converted naively would become:
<p><a href="#">Head <figure><img src="assets/a.jpg" /><figcaption>caption</figcaption></figure> Tail</a></p>
However this is not valid HTML - P elements can not contain other block level elements.
To fix this you can specify a "location" attribute on a shortcode. When the location attribute is "left" or "right"
the inserted content will be moved to immediately before the block tag. The result is this:
<figure><img src="assets/a.jpg" /><figcaption>caption</figcaption></figure><p><a href="#">Head Tail</a></p>
When the location attribute is "leftAlone" or "center" then the DOM is split around the element. The result is this:
<p><a href="#">Head </a></p><figure><img src="assets/a.jpg" /><figcaption>caption</figcaption></figure><p><a href="#"> Tail</a></p>
### Parameter values
Here is a summary of the callback parameter values based on some example shortcodes.
:::php
public function MyCustomShortCode($arguments, $content = null, $parser = null, $tagName) {
// ..
}
[my_shortcode]
$attributes => array();
$content => null;
$parser => ShortcodeParser instance,
$tagName => 'myshortcode')
[my_shortcode,attribute="foo",other="bar"]
$attributes => array ('attribute' => 'foo', 'other' => 'bar')
$enclosedContent => null
$parser => ShortcodeParser instance
$tagName => 'my_shortcode'
[my_shortcode,attribute="foo"]content[/my_shortcode]
$attributes => array('attribute' => 'foo')
$enclosedContent => 'content'
$parser => ShortcodeParser instance
$tagName => 'my_shortcode'
## Limitations
Since the shortcode parser is based on a simple regular expression it cannot properly handle nested shortcodes. For
example the below code will not work as expected:
[shortcode]
[shortcode][/shortcode]
[/shortcode]
The parser will raise an error if it can not find a matching opening tag for any particular closing tag
## Related Documentation
* [Wordpress Implementation](http://codex.wordpress.org/Shortcode_API)
* [How to Create a Google Maps Shortcode](how_tos/create_a_google_maps_shortcode)
## API Documentation
* [api:ShortcodeParser]

View File

@ -0,0 +1,246 @@
title: Injector
summary: Introduction to using Dependency Injection within SilverStripe.
# Injector
The [api:Injector] class is the central manager of inter-class dependencies in SilverStripe. It offers developers the
ability to declare the dependencies a class type has, or to change the nature of the dependencies defined by other
developers.
Some of the goals of dependency injection are:
* Simplified instantiation of objects
* Providing a uniform way of declaring and managing inter-object dependencies
* Making class dependencies configurable
* Simplifying the process of overriding or replacing core behaviour
* Improve testability of code
* Promoting abstraction of logic
The following sums up the simplest usage of the `Injector` it creates a new object of type `ClassName` through `create`
:::php
$object = Injector::inst()->create('MyClassName');
The benefit of constructing objects through this syntax is `ClassName` can be swapped out using the
[Configuration API](../configuration) by developers.
**mysite/_config/app.yml**
:::yml
Injector:
MyClassName:
class: MyBetterClassName
Repeated calls to `create()` create a new class each time.
:::php
$object = Injector::inst()->create('MyClassName');
$object2 = Injector::inst()->create('MyClassName');
echo $object !== $object2;
// returns true;
## Singleton Pattern
The `Injector` API can be used for the singleton pattern through `get()`. Subsequent calls to `get` return the same
object instance as the first call.
:::php
// sets up MyClassName as a singleton
$object = Injector::inst()->get('MyClassName');
$object2 = Injector::inst()->get('MyClassName');
echo ($object === $object2);
// returns true;
## Dependencies
The `Injector` API can be used to define the types of `$dependancies` that an object requires.
:::php
<?php
class MyController extends Controller {
// both of these properties will be automatically
// set by the injector on object creation
public $permissions;
public $textProperty;
// we declare the types for each of the properties on the object. Anything we pass in via the Injector API must
// match these data types.
static $dependencies = array(
'textProperty' => 'a string value',
'permissions' => '%$PermissionService',
);
}
When creating a new instance of `MyController` the dependencies on that class will be met.
:::php
$object = Injector::inst()->get('MyController');
echo ($object->permissions instanceof PermissionService);
// returns true;
echo (is_string($object->textProperty));
// returns true;
The [Configuration YAML](../configuration) does the hard work of configuring those `$dependancies` for us.
**mysite/_config/app.yml**
:::yml
Injector:
PermissionService:
class: MyCustomPermissionService
MyController
properties:
textProperty: 'My Text Value'
Now the dependencies will be replaced with our configuration.
:::php
$object = Injector::inst()->get('MyController');
echo ($object->permissions instanceof MyCustomPermissionService);
// returns true;
echo ($object->textProperty == 'My Text Value');
// returns true;
## Factories
Some services require non-trivial construction which means they must be created by a factory class. To do this, create
a factory class which implements the [api:SilverStripe\Framework\Injector\Factory] interface. You can then specify
the `factory` key in the service definition, and the factory service will be used.
An example using the `MyFactory` service to create instances of the `MyService` service is shown below:
**mysite/_config/app.yml**
:::yml
Injector:
MyService:
factory: MyFactory
MyFactory:
class: MyFactoryImplementation
**mysite/code/MyFactoryImplementation.php**
:::php
<?php
class MyFactoryImplementation implements SilverStripe\Framework\Injector\Factory {
public function create($service, array $params = array()) {
return new MyServiceImplementation();
}
}
// Will use MyFactoryImplementation::create() to create the service instance.
$instance = Injector::inst()->get('MyService');
## Dependency overrides
To override the `$dependency` declaration for a class, define the following configuration file.
**mysite/_config/app.yml**
MyController:
dependencies:
textProperty: a string value
permissions: %$PermissionService
## Managed objects
Simple dependencies can be specified by the `$dependencies`, but more complex configurations are possible by specifying
constructor arguments, or by specifying more complex properties such as lists.
These more complex configurations are defined in `Injector` configuration blocks and are read by the `Injector` at
runtime.
Assuming a class structure such as
:::php
<?php
class RestrictivePermissionService {
private $database;
public function setDatabase($d) {
$this->database = $d;
}
}
class MySQLDatabase {
private $username;
private $password;
public function __construct($username, $password) {
$this->username = $username;
$this->password = $password;
}
}
And the following configuration..
:::yml
name: MyController
---
MyController:
dependencies:
permissions: %$PermissionService
Injector:
PermissionService:
class: RestrictivePermissionService
properties:
database: %$MySQLDatabase
MySQLDatabase
constructor:
0: 'dbusername'
1: 'dbpassword'
Calling..
:::php
// sets up ClassName as a singleton
$controller = Injector::inst()->get('MyController');
Would setup the following
* Create an object of type `MyController`
* Look through the **dependencies** and call get('PermissionService')
* Load the configuration for PermissionService, and create an object of type `RestrictivePermissionService`
* Look at the properties to be injected and look for the config for `MySQLDatabase`
* Create a MySQLDatabase class, passing dbusername and dbpassword as the parameters to the constructor.
## Testing with Injector
In situations where injector states must be temporarily overridden, it is possible to create nested Injector instances
which may be later discarded, reverting the application to the original state. This is done through `nest` and `unnest`.
This is useful when writing test cases, as certain services may be necessary to override for a single method call.
:::php
// Setup default service
Injector::inst()->registerService(new LiveService(), 'ServiceName');
// Test substitute service temporarily
Injector::nest();
Injector::inst()->registerService(new TestingService(), 'ServiceName');
$service = Injector::inst()->get('ServiceName');
// ... do something with $service
// revert changes
Injector::unnest();
## API Documentation
* [api:Injector]
* [api:Factory]

View File

@ -0,0 +1,197 @@
title: Aspects
summary: Introduction to using aspect-oriented programming with SilverStripe.
# Aspects
Aspect oriented programming is the idea that some logic abstractions can be applied across various type hierarchies
"after the fact", altering the behavior of the system without altering the code structures that are already in place.
> In computing, aspect-oriented programming (AOP) is a programming paradigm which isolates secondary or supporting
> functions from the main program's business logic. It aims to increase modularity by allowing the separation of
> cross-cutting concerns, forming a basis for aspect-oriented software development.
<div class="notice" markdown="1">
[Wikipedia](http://en.wikipedia.org/wiki/Aspect-oriented_programming) provides a much more in-depth explanation.
</div>
In the context of the SilverStripe [Dependency Injector](injector), Aspects are achieved thanks to PHP's `__call` magic
method combined with the `Proxy` Design Pattern.
Assume an existing service declaration exists called `MyService`. An `AopProxyService` class instance is created, and
the existing `MyService` object is bound in as a member variable of the `AopProxyService` class.
Objects are added to the `AopProxyService` instance's "beforeCall" and "afterCall" lists; each of these implements
either the beforeCall or afterCall method.
When client code declares a `$dependency` on MyService, it is actually passed in the `AopProxyService` instance.
Client code calls a method `MyMethod` that it knows exists on `MyService` - this doesn't exist on `AopProxyService`, so
__call is triggered.
All classes bound to the `beforeCall` list are executed; if any explicitly returns 'false', `myMethod` is not executed.
Otherwise, `myMethod` is executed.
All classes bound to the `afterCall` list are executed.
To provide some context, imagine a situation where we want to direct all `write` queries made in the system to a
specific database server, whereas all read queries can be handled by slave servers.
A simplified implementation might look like the following.
<div class="notice" markdown="1">
This doesn't cover all cases used by SilverStripe so is not a complete solution, more just a guide to how it would be
used.
</div>
**mysite/code/MySQLWriteDbAspect.php**
:::php
<?php
class MySQLWriteDbAspect implements BeforeCallAspect {
/**
* @var MySQLDatabase
*/
public $writeDb;
public $writeQueries = array(
'insert','update','delete','replace'
);
public function beforeCall($proxied, $method, $args, &$alternateReturn) {
if (isset($args[0])) {
$sql = $args[0];
$code = isset($args[1]) ? $args[1] : E_USER_ERROR;
if (in_array(strtolower(substr($sql,0,strpos($sql,' '))), $this->writeQueries)) {
$alternateReturn = $this->writeDb->query($sql, $code);
return false;
}
}
}
}
To actually make use of this class, a few different objects need to be configured. First up, define the `writeDb`
object that's made use of above.
**mysite/_config/app.yml**
:::yml
WriteMySQLDatabase:
class: MySQLDatabase
constructor:
- type: MySQLDatabase
server: write.hostname.db
username: user
password: pass
database: write_database
This means that whenever something asks the [api:Injector] for the `WriteMySQLDatabase` object, it'll receive an object
of type `MySQLDatabase`, configured to point at the 'write_database'.
Next, this should be bound into an instance of the `Aspect` class
**mysite/_config/app.yml**
:::yml
MySQLWriteDbAspect:
properties:
writeDb: %$WriteMySQLDatabase
Next, we need to define the database connection that will be used for all non-write queries
**mysite/_config/app.yml**
:::yml
ReadMySQLDatabase:
class: MySQLDatabase
constructor:
- type: MySQLDatabase
server: slavecluster.hostname.db
username: user
password: pass
database: read_database
The final piece that ties everything together is the [api:AopProxyService] instance that will be used as the replacement
object when the framework creates the database connection.
**mysite/_config/app.yml**
:::yml
MySQLDatabase:
class: AopProxyService
properties:
proxied: %$ReadMySQLDatabase
beforeCall:
query:
- %$MySQLWriteDbAspect
The two important parts here are in the `properties` declared for the object.
- **proxied** : This is the 'read' database connection that all queries should be initially directed through.
- **beforeCall** : A hash of method\_name => array containing objects that are to be evaluated _before_ a call to the
defined method\_name
Overall configuration for this would look as follows
**mysite/_config/app.yml**
:::yml
Injector:
ReadMySQLDatabase:
class: MySQLDatabase
constructor:
- type: MySQLDatabase
server: slavecluster.hostname.db
username: user
password: pass
database: read_database
MySQLWriteDbAspect:
properties:
writeDb: %$WriteMySQLDatabase
WriteMySQLDatabase:
class: MySQLDatabase
constructor:
- type: MySQLDatabase
server: write.hostname.db
username: user
password: pass
database: write_database
MySQLDatabase:
class: AopProxyService
properties:
proxied: %$ReadMySQLDatabase
beforeCall:
query:
- %$MySQLWriteDbAspect
## Changing what a method returns
One major feature of an `Aspect` is the ability to modify what is returned from the client's call to the proxied method.
As seen in the above example, the `beforeCall` method modifies the `&$alternateReturn` variable, and returns `false`
after doing so.
:::php
$alternateReturn = $this->writeDb->query($sql, $code);
return false;
By returning `false` from the `beforeCall()` method, the wrapping proxy class will_not_ call any additional `beforeCall`
handlers defined for the called method. Assigning the `$alternateReturn` variable also indicates to return that value
to the caller of the method.
Similarly the `afterCall()` aspect can be used to manipulate the value to be returned to the calling code. All the
`afterCall()` method needs to do is return a non-null value, and that value will be returned to the original calling
code instead of the actual return value of the called method.
## API Documentation
* [api:AopProxyService]
* [api:BeforeCallAspect]
* [api:AfterCallAspect]
* [api:Injector]

View File

@ -0,0 +1,10 @@
title: Custom Templates
summary: Override templates from core and modules in your application
# Custom Templates
See [Template Inheritance](../templates).
## Form Templates
See [Form Templates](../forms/form_templates).

View File

@ -0,0 +1,77 @@
title: How to Publish a SilverStripe module
# How to Publish a SilverStripe module.
If you wish to submit your module to our public directory, you take responsibility for a certain level of code quality,
adherence to conventions, writing documentation, and releasing updates.
SilverStripe uses [Composer](../../getting_started/composer/) to manage module releases and dependencies between
modules. If you plan on releasing your module to the public, ensure that you provide a `composer.json` file in the root
of your module containing the meta-data about your module.
For more information about what your `composer.json` file should include, consult the
[Composer Documentation](http://getcomposer.org/doc/01-basic-usage.md).
A basic usage of a module for 3.1 that requires the CMS would look similar to
this:
**mycustommodule/composer.json**
:::js
{
"name": "your-vendor-name/module-name",
"description": "One-liner describing your module",
"type": "silverstripe-module",
"homepage": "http://github.com/your-vendor-name/module-name",
"keywords": ["silverstripe", "some-tag", "some-other-tag"],
"license": "BSD-3-Clause",
"authors": [
{"name": "Your Name","email": "your@email.com"}
],
"support": {
"issues": "http://github.com/your-vendor-name/module-name/issues"
},
"require": {
"silverstripe/cms": "~3.1",
"silverstripe/framework": "~3.1"
},
"extra": {
"installer-name": "module-name",
"screenshots": [
"relative/path/screenshot1.png",
"http://myhost.com/screenshot2.png"
]
}
}
Once your module is published online with a service like Github.com or Bitbucket.com, submit the repository to
[Packagist](https://packagist.org/) to have the module accessible to developers. It'll automatically get picked
up by [addons.silverstripe.org](http://addons.silverstripe.org/) website.
## Releasing versions
Over time you may have to release new versions of your module to continue to work with newer versions of SilverStripe.
By using Composer, this is made easy for developers by allowing them to specify what version they want to use. Each
version of your module should be a separate branch in your version control and each branch should have a `composer.json`
file explicitly defining what versions of SilverStripe you support.
Say you have a module which supports SilverStripe 3.0. A new release of this module takes advantage of new features
in SilverStripe 3.1. In this case, you would create a new branch for the 3.0 compatible code base of your module. This
allows you to continue fixing bugs on this older release branch.
<div class="info" markdown="1">
As a convention, the `master` branch of your module should always work with the `master` branch of SilverStripe.
</div>
Other branches should be created on your module as needed if they're required to support specific SilverStripe releases.
You can have an overlap in supported versions, e.g two branches in your module both support SilverStripe 3.1. In this
case, you should explain the differences in your `README.md` file.
Here's some common values for your `require` section
(see [getcomposer.org](http://getcomposer.org/doc/01-basic-usage.md#package-versions) for details):
* `3.0.*`: Version `3.0`, including `3.0.1`, `3.0.2` etc, excluding `3.1`
* `~3.0`: Version `3.0` or higher, including `3.0.1` and `3.1` etc, excluding `4.0`
* `~3.0,<3.2`: Version `3.0` or higher, up until `3.2`, which is excluded
* `~3.0,>3.0.4`: Version `3.0` or higher, starting with `3.0.4`

View File

@ -0,0 +1,33 @@
title: How to Create a Google Maps Shortcode
# How to Create a Google Maps Shortcode
To demonstrate how easy it is to build custom shortcodes, we'll build one to display a Google Map based on a provided
address. We want our CMS authors to be able to embed the map using the following code:
:::php
[googlemap,width=500,height=300]97-99 Courtenay Place, Wellington, New Zealand[/googlemap]
So we've got the address as "content" of our new `googlemap` shortcode tags, plus some `width` and `height` arguments.
We'll add defaults to those in our shortcode parser so they're optional.
**mysite/_config.php**
:::php
ShortcodeParser::get('default')->register('googlemap', function($arguments, $address, $parser, $shortcode) {
$iframeUrl = sprintf(
'http://maps.google.com/maps?q=%s&amp;hnear=%s&amp;ie=UTF8&hq=&amp;t=m&amp;z=14&amp;output=embed',
urlencode($address),
urlencode($address)
);
$width = (isset($arguments['width']) && $arguments['width']) ? $arguments['width'] : 400;
$height = (isset($arguments['height']) && $arguments['height']) ? $arguments['height'] : 300;
return sprintf(
'<iframe width="%d" height="%d" src="%s" frameborder="0" scrolling="no" marginheight="0" marginwidth="0"></iframe>',
$width,
$height,
$iframeUrl
);
});

View File

@ -0,0 +1,17 @@
title: Extending SilverStripe
summary: Understand the ways to modify the built-in functionality through Extensions, Subclassing and Dependency Injection.
introduction: SilverStripe is easily extensible to meet custom application requirements. This guide covers the wide range of API's to modify built-in functionality and make your own code easily extensible.
No two applications are ever going to be the same and SilverStripe is built with this in mind. The core framework
includes common functionality and default behaviors easily complemented with add-ons such as modules, widgets and
themes.
SilverStripe includes a myriad of extension API's such as *Extension Hooks* and support for programming patterns
such Dependency Injection. Allowing developers to tailor the framework to their needs without modifying the core
framework.
[CHILDREN Exclude="How_Tos"]
## How to's
[CHILDREN Folder="How_Tos"]

View File

@ -0,0 +1,269 @@
title: Unit and Integration Testing
summary: Test models, database logic and your object methods.
# Unit and Integration Testing
A Unit Test is an automated piece of code that invokes a unit of work in the application and then checks the behavior
to ensure that it works as it should. A simple example would be to test the result of a PHP method.
**mysite/code/Page.php**
:::php
<?php
class Page extends SiteTree {
public static function MyMethod() {
return (1 + 1);
}
}
**mysite/tests/PageTest.php**
:::php
<?php
class PageTest extends SapphireTest {
public function testMyMethod() {
$this->assertEquals(2, Page::MyMethod());
}
}
<div class="info" markdown="1">
Tests for your application should be stored in the `mysite/tests` directory. Test cases for add-ons should be stored in
the `(modulename)/tests` directory.
Test case classes should end with `Test` (e.g PageTest) and test methods must start with `test` (e.g testMyMethod).
</div>
A SilverStripe unit test is created by extending one of two classes, [api:SapphireTest] or [api:FunctionalTest].
[api:SapphireTest] is used to test your model logic (such as a `DataObject`), and [api:FunctionalTest] is used when
you want to test a `Controller`, `Form` or anything that requires a web page.
<div class="info" markdown="1">
`FunctionalTest` is a subclass of `SapphireTest` so will inherit all of the behaviors. By subclassing `FunctionalTest`
you gain the ability to load and test web pages on the site.
`SapphireTest` in turn, extends `PHPUnit_Framework_TestCase`. For more information on `PHPUnit_Framework_TestCase` see
the [PHPUnit](http://www.phpunit.de) documentation. It provides a lot of fundamental concepts that we build on in this
documentation.
</div>
## Running Tests
### PHPUnit Binary
The `phpunit` binary should be used from the root directory of your website.
:::bash
phpunit
# Runs all tests
phpunit framework/tests/
# Run all tests of a specific module
phpunit framework/tests/filesystem
# Run specific tests within a specific module
phpunit framework/tests/filesystem/FolderTest.php
# Run a specific test
phpunit framework/tests '' flush=all
# Run tests with optional `$_GET` parameters (you need an empty second argument)
<div class="alert" markdown="1">
If phpunit is not installed globally on your machine, you may need to replace the above usage of `phpunit` with the full
path (e.g `vendor/bin/phpunit framework/tests`)
</div>
<div class="info" markdown="1">
All command-line arguments are documented on [phpunit.de](http://www.phpunit.de/manual/current/en/textui.html).
</div>
### Via a Web Browser
Executing tests from the command line is recommended, since it most closely reflects test runs in any automated testing
environments. If for some reason you don't have access to the command line, you can also run tests through the browser.
http://yoursite.com/dev/tests
### Via the CLI
The [sake](../cli) executable that comes with SilverStripe can trigger a customized `[api:TestRunner]` class that
handles the PHPUnit configuration and output formatting. While the custom test runner a handy tool, it's also more
limited than using `phpunit` directly, particularly around formatting test output.
:::bash
sake dev/tests/all
# Run all tests
sake dev/tests/module/framework,cms
# Run all tests of a specific module (comma-separated)
sake dev/tests/FolderTest,OtherTest
# Run specific tests (comma-separated)
sake dev/tests/all "flush=all&foo=bar"
# Run tests with optional `$_GET` parameters
sake dev/tests/all SkipTests=MySkippedTest
# Skip some tests
## Test Databases and Fixtures
SilverStripe tests create their own database when the test starts. New `ss_tmp` databases are created using the same
connection details you provide for the main website. The new `ss_tmp` database does not copy what is currently in your
application database. To provide seed data use a [Fixture](fixtures) file.
<div class="alert" markdown="1">
As the test runner will create new databases for the tests to run, the database user should have the appropriate
permissions to create new databases on your server.
</div>
<div class="notice" markdown="1">
The test database is rebuilt every time one of the test methods is run. Over time, you may have several hundred test
databases on your machine. To get rid of them is a call to `http://yoursite.com/dev/tests/cleanupdb`
</div>
## Custom PHPUnit Configuration
The `phpunit` executable can be configured by command line arguments or through an XML file. SilverStripe comes with a
default `phpunit.xml.dist` that you can use as a starting point. Copy the file into `phpunit.xml` and customize to your
needs.
**phpunit.xml**
:::xml
<phpunit bootstrap="framework/tests/bootstrap.php" colors="true">
<testsuite name="Default">
<directory>mysite/tests</directory>
<directory>cms/tests</directory>
<directory>framework/tests</directory>
</testsuite>
<listeners>
<listener class="SS_TestListener" file="framework/dev/TestListener.php" />
</listeners>
<groups>
<exclude>
<group>sanitychecks</group>
</exclude>
</groups>
</phpunit>
<div class="alert" markdown="1">
This configuration file doesn't apply for running tests through the "sake" wrapper
</div>
### setUp() and tearDown()
In addition to loading data through a [Fixture File](fixtures), a test case may require some additional setup work to be
run before each test method. For this, use the PHPUnit `setUp` and `tearDown` methods. These are run at the start and
end of each test.
:::php
<?php
class PageTest extends SapphireTest {
function setUp() {
parent::setUp();
// create 100 pages
for($i=0; $i<100; $i++) {
$page = new Page(array('Title' => "Page $i"));
$page->write();
$page->publish('Stage', 'Live');
}
// reset configuration for the test.
Config::nest();
Config::inst()->update('Foo', 'bar', 'Hello!');
}
public function tearDown() {
// restores the config variables
Config::unnest();
parent::tearDown();
}
public function testMyMethod() {
// ..
}
public function testMySecondMethod() {
// ..
}
}
`tearDownOnce` and `setUpOnce` can be used to run code just once for the file rather than before and after each
individual test case.
:::php
<?php
class PageTest extends SapphireTest {
function setUpOnce() {
parent::setUpOnce();
// ..
}
public function tearDownOnce() {
parent::tearDownOnce();
// ..
}
}
## Generating a Coverage Report
PHPUnit can generate a code coverage report ([docs](http://www.phpunit.de/manual/current/en/code-coverage-analysis.html))
by executing the following commands.
:::bash
phpunit --coverage-html assets/coverage-report
# Generate coverage report for the whole project
phpunit --coverage-html assets/coverage-report mysite/tests/
# Generate coverage report for the "mysite" module
<div class="notice" markdown="1">
These commands will output a report to the `assets/coverage-report/` folder. To view the report, open the `index.html`
file within a web browser.
</div>
Typically, only your own custom PHP code in your project should be regarded when producing these reports. To exclude
some `thirdparty/` directories add the following to the `phpunit.xml` configuration file.
:::xml
<filter>
<blacklist>
<directory suffix=".php">framework/dev/</directory>
<directory suffix=".php">framework/thirdparty/</directory>
<directory suffix=".php">cms/thirdparty/</directory>
<!-- Add your custom rules here -->
<directory suffix=".php">mysite/thirdparty/</directory>
</blacklist>
</filter>
## Related Documentation
* [How to Write a SapphireTest](how_tos/write_a_sapphiretest)
* [How to Write a FunctionalTest](how_tos/write_a_functionaltest)
* [Fixtures](fixtures)
## API Documentation
* [api:TestRunner]
* [api:SapphireTest]
* [api:FunctionalTest]

View File

@ -0,0 +1,106 @@
title: Functional Testing
summary: Test controllers, forms and HTTP responses.
# Functional Testing
[api:FunctionalTest] test your applications `Controller` logic and anything else which requires a web request. The
core idea of these tests is the same as `SapphireTest` unit tests but `FunctionalTest` adds several methods for
creating [api:SS_HTTPRequest], receiving [api:SS_HTTPResponse] objects and modifying the current user session.
## Get
:::php
$page = $this->get($url);
Performs a GET request on $url and retrieves the [api:SS_HTTPResponse]. This also changes the current page to the value
of the response.
## Post
:::php
$page = $this->post($url);
Performs a POST request on $url and retrieves the [api:SS_HTTPResponse]. This also changes the current page to the value
of the response.
## Submit
:::php
$submit = $this->submitForm($formID, $button = null, $data = array());
Submits the given form (`#ContactForm`) on the current page and returns the [api:SS_HTTPResponse].
## LogInAs
:::php
$this->logInAs($member);
Logs a given user in, sets the current session. To log all users out pass `null` to the method.
:::php
$this->logInAs(null);
## Assertions
The `FunctionalTest` class also provides additional asserts to validate your tests.
### assertPartialMatchBySelector
:::php
$this->assertPartialMatchBySelector('p.good',array(
'Test save was successful'
));
Asserts that the most recently queried page contains a number of content tags specified by a CSS selector. The given CSS
selector will be applied to the HTML of the most recent page. The content of every matching tag will be examined. The
assertion fails if one of the expectedMatches fails to appear.
### assertExactMatchBySelector
:::php
$this->assertExactMatchBySelector("#MyForm_ID p.error", array(
"That email address is invalid."
));
Asserts that the most recently queried page contains a number of content tags specified by a CSS selector. The given CSS
selector will be applied to the HTML of the most recent page. The full HTML of every matching tag will be examined. The
assertion fails if one of the expectedMatches fails to appear.
### assertPartialHTMLMatchBySelector
:::php
$this->assertPartialHTMLMatchBySelector("#MyForm_ID p.error", array(
"That email address is invalid."
));
Assert that the most recently queried page contains a number of content tags specified by a CSS selector. The given CSS
selector will be applied to the HTML of the most recent page. The content of every matching tag will be examined. The
assertion fails if one of the expectedMatches fails to appear.
<div class="notice" markdown="1">
`&amp;nbsp;` characters are stripped from the content; make sure that your assertions take this into account.
</div>
### assertExactHTMLMatchBySelector
:::php
$this->assertExactHTMLMatchBySelector("#MyForm_ID p.error", array(
"That email address is invalid."
));
Assert that the most recently queried page contains a number of content tags specified by a CSS selector. The given CSS
selector will be applied to the HTML of the most recent page. The full HTML of every matching tag will be examined. The
assertion fails if one of the expectedMatches fails to appear.
<div class="notice" markdown="1">
`&amp;nbsp;` characters are stripped from the content; make sure that your assertions take this into account.
</div>
## Related Documentation
* [How to write a FunctionalTest](how_tos/write_a_functionaltest)
## API Documentation
* [api:FunctionalTest]

View File

@ -0,0 +1,7 @@
title: Behavior Testing
summary: Describe how your application should behave in plain text and run tests in a browser.
# Behavior Testing
For behavior testing in SilverStripe, check out
[SilverStripe Behat Documentation](https://github.com/silverstripe-labs/silverstripe-behat-extension/).

View File

@ -0,0 +1,318 @@
title: Fixtures
summary: Populate test databases with fake seed data.
# Fixtures
To test functionality correctly, we must use consistent data. If we are testing our code with the same data each
time, we can trust our tests to yield reliable results and to identify when the logic changes. Each test run in
SilverStripe starts with a fresh database containing no records. `Fixtures` provide a way to describe the initial data
to load into the database. The `[api:SapphireTest]` class takes care of populating a test database with data from
fixtures - all we have to do is define them.
Fixtures are defined in `YAML`. `YAML` is a markup language which is deliberately simple and easy to read, so it is
ideal for fixture generation. Say we have the following two DataObjects:
:::php
<?php
class Player extends DataObject {
private static $db = array (
'Name' => 'Varchar(255)'
);
private static $has_one = array(
'Team' => 'Team'
);
}
class Team extends DataObject {
private static $db = array (
'Name' => 'Varchar(255)',
'Origin' => 'Varchar(255)'
);
private static $has_many = array(
'Players' => 'Player'
);
}
We can represent multiple instances of them in `YAML` as follows:
**mysite/tests/fixtures.yml**
:::yml
Player:
john:
Name: John
Team: =>Team.hurricanes
joe:
Name: Joe
Team: =>Team.crusaders
jack:
Name: Jack
Team: =>Team.crusaders
Team:
hurricanes:
Name: The Hurricanes
Origin: Wellington
crusaders:
Name: The Crusaders
Origin: Bay of Plenty
This `YAML` is broken up into three levels, signified by the indentation of each line. In the first level of
indentation, `Player` and `Team`, represent the class names of the objects we want to be created.
The second level, `john`/`joe`/`jack` & `hurricanes`/`crusaders`, are **identifiers**. Each identifier you specify
represents a new object and can be referenced in the PHP using `objFromFixture`
:::php
$player = $this->objFromFixture('Player', 'jack');
The third and final level represents each individual object's fields.
A field can either be provided with raw data (such as the names for our Players), or we can define a relationship, as
seen by the fields prefixed with `=>`.
Each one of our Players has a relationship to a Team, this is shown with the `Team` field for each `Player` being set
to `=>Team.` followed by a team name.
<div class="info" markdown="1">
Take the player John in our example YAML, his team is the Hurricanes which is represented by `=>Team.hurricanes`. This
sets the `has_one` relationship for John with with the `Team` object `hurricanes`.
</div>
<div class="hint" markdown='1'>
Note that we use the name of the relationship (Team), and not the name of the
database field (TeamID).
</div>
This style of relationship declaration can be used for any type of relationship (i.e `has_one`, `has_many`, `many_many`).
We can also declare the relationships conversely. Another way we could write the previous example is:
:::yml
Player:
john:
Name: John
joe:
Name: Joe
jack:
Name: Jack
Team:
hurricanes:
Name: Hurricanes
Origin: Wellington
Players: =>Player.john
crusaders:
Name: Crusaders
Origin: Bay of Plenty
Players: =>Player.joe,=>Player.jack
The database is populated by instantiating `DataObject` objects and setting the fields declared in the `YAML`, then
calling `write()` on those objects. Take for instance the `hurricances` record in the `YAML`. It is equivalent to
writing:
:::php
$team = new Team(array(
'Name' => 'Hurricanes',
'Origin' => 'Wellington'
));
$team->write();
$team->Players()->add($john);
<div class="notice" markdown="1">
As the YAML fixtures will call `write`, any `onBeforeWrite()` or default value logic will be executed as part of the
test.
</div>
### Defining many_many_extraFields
`many_many` relations can have additional database fields attached to the relationship. For example we may want to
declare the role each player has in the team.
:::php
class Player extends DataObject {
private static $db = array (
'Name' => 'Varchar(255)'
);
private static $belongs_many_many = array(
'Teams' => 'Team'
);
}
class Team extends DataObject {
private static $db = array (
'Name' => 'Varchar(255)'
);
private static $many_many = array(
'Players' => 'Player'
);
private static $many_many_extraFields = array(
"Players" => array(
"Role" => "Varchar"
)
);
}
To provide the value for the `many_many_extraField` use the YAML list syntax.
:::yml
Player:
john:
Name: John
joe:
Name: Joe
jack:
Name: Jack
Team:
hurricanes:
Name: The Hurricanes
Players:
- =>Player.john:
Role: Captain
crusaders:
Name: The Crusaders
Players:
- =>Player.joe:
Role: Captain
- =>Player.jack:
Role: Winger
## Fixture Factories
While manually defined fixtures provide full flexibility, they offer very little in terms of structure and convention.
Alternatively, you can use the `[api:FixtureFactory]` class, which allows you to set default values, callbacks on object
creation, and dynamic/lazy value setting.
<div class="hint" markdown='1'>
SapphireTest uses FixtureFactory under the hood when it is provided with YAML based fixtures.
</div>
The idea is that rather than instantiating objects directly, we'll have a factory class for them. This factory can have
*blueprints* defined on it, which tells the factory how to instantiate an object of a specific type. Blueprints need a
name, which is usually set to the class it creates such as `Member` or `Page`.
Blueprints are auto-created for all available DataObject subclasses, you only need to instantiate a factory to start
using them.
:::php
$factory = Injector::inst()->create('FixtureFactory');
$obj = $factory->createObject('Team', 'hurricanes');
In order to create an object with certain properties, just add a third argument:
:::php
$obj = $factory->createObject('Team', 'hurricanes', array(
'Name' => 'My Value'
));
<div class="warning" markdown="1">
It is important to remember that fixtures are referenced by arbitrary identifiers ('hurricanes'). These are internally
mapped to their database identifiers.
</div>
After we've created this object in the factory, `getId` is used to retrieve it by the identifier.
:::php
$databaseId = $factory->getId('Team', 'hurricanes');
### Default Properties
Blueprints can be overwritten in order to customize their behavior. For example, if a Fixture does not provide a Team
name, we can set the default to be `Unknown Team`.
:::php
$factory->define('Team', array(
'Name' => 'Unknown Team'
));
### Dependent Properties
Values can be set on demand through anonymous functions, which can either generate random defaults, or create composite
values based on other fixture data.
:::php
$factory->define('Member', array(
'Email' => function($obj, $data, $fixtures) {
if(isset($data['FirstName']) {
$obj->Email = strtolower($data['FirstName']) . '@example.org';
}
},
'Score' => function($obj, $data, $fixtures) {
$obj->Score = rand(0,10);
}
));
### Relations
Model relations can be expressed through the same notation as in the YAML fixture format described earlier, through the
`=>` prefix on data values.
:::php
$obj = $factory->createObject('Team', 'hurricanes', array(
'MyHasManyRelation' => '=>Player.john,=>Player.joe'
));
#### Callbacks
Sometimes new model instances need to be modified in ways which can't be expressed in their properties, for example to
publish a page, which requires a method call.
:::php
$blueprint = Injector::inst()->create('FixtureBlueprint', 'Member');
$blueprint->addCallback('afterCreate', function($obj, $identifier, $data, $fixtures) {
$obj->publish('Stage', 'Live');
});
$page = $factory->define('Page', $blueprint);
Available callbacks:
* `beforeCreate($identifier, $data, $fixtures)`
* `afterCreate($obj, $identifier, $data, $fixtures)`
### Multiple Blueprints
Data of the same type can have variations, for example forum members vs. CMS admins could both inherit from the `Member`
class, but have completely different properties. This is where named blueprints come in. By default, blueprint names
equal the class names they manage.
:::php
$memberBlueprint = Injector::inst()->create('FixtureBlueprint', 'Member', 'Member');
$adminBlueprint = Injector::inst()->create('FixtureBlueprint', 'AdminMember', 'Member');
$adminBlueprint->addCallback('afterCreate', function($obj, $identifier, $data, $fixtures) {
if(isset($fixtures['Group']['admin'])) {
$adminGroup = Group::get()->byId($fixtures['Group']['admin']);
$obj->Groups()->add($adminGroup);
}
});
$member = $factory->createObject('Member'); // not in admin group
$admin = $factory->createObject('AdminMember'); // in admin group
## Related Documentation
* [How to use a FixtureFactory](how_to/fixturefactories/)
## API Documentation
* [api:FixtureFactory]
* [api:FixtureBlueprint]

View File

@ -0,0 +1,82 @@
title: How to write a SapphireTest
# How to write a SapphireTest
Here is an example of a test which extends [api:SapphireTest] to test the URL generation of the page. It also showcases
how you can load default records into the test database.
**mysite/tests/PageTest.php**
:::php
<?php
class PageTest extends SapphireTest {
/**
* Defines the fixture file to use for this test class
*
/
protected static $fixture_file = 'SiteTreeTest.yml';
/**
* Test generation of the URLSegment values.
*
* Makes sure to:
* - Turn things into lowercase-hyphen-format
* - Generates from Title by default, unless URLSegment is explicitly set
* - Resolves duplicates by appending a number
*/
public function testURLGeneration() {
$expectedURLs = array(
'home' => 'home',
'staff' => 'my-staff',
'about' => 'about-us',
'staffduplicate' => 'my-staff-2'
);
foreach($expectedURLs as $fixture => $urlSegment) {
$obj = $this->objFromFixture('Page', $fixture);
$this->assertEquals($urlSegment, $obj->URLSegment);
}
}
}
Firstly we define a static `$fixture_file`, this should point to a file that represents the data we want to test,
represented as a YAML [Fixture](../fixtures). When our test is run, the data from this file will be loaded into a test
database and discarded at the end of the test.
<div class="notice" markdown="1">
The `fixture_file` property can be path to a file, or an array of strings pointing to many files. The path must be
absolute from your website's root folder.
</div>
The second part of our class is the `testURLGeneration` method. This method is our test. When the test is executed,
methods prefixed with the word `test` will be run.
<div class="notice" markdown="1">
The test database is rebuilt every time one of these methods is run.
</div>
Inside our test method is the `objFromFixture` method that will generate an object for us based on data from our fixture
file. To identify to the object, we provide a class name and an identifier. The identifier is specified in the YAML file
but not saved in the database anywhere, `objFromFixture` looks the `[api:DataObject]` up in memory rather than using the
database. This means that you can use it to test the functions responsible for looking up content in the database.
The final part of our test is an assertion command, `assertEquals`. An assertion command allows us to test for something
in our test methods (in this case we are testing if two values are equal). A test method can have more than one
assertion command, and if any one of these assertions fail, so will the test method.
<div class="info" markdown="1">
For more information on PHPUnit's assertions see the [PHPUnit manual](http://www.phpunit.de/manual/current/en/api.html#api.assert).
</div>
## Related Documentation
* [Unit Testing](../unit_testing)
* [Fixtures](../fixtures)
## API Documentation
* [api:SapphireTest]
* [api:FunctionalTest]

View File

@ -0,0 +1,56 @@
title: How to write a FunctionalTest
# How to Write a FunctionalTest
[api:FunctionalTest] test your applications `Controller` instances and anything else which requires a web request. The
core of these tests are the same as `SapphireTest` unit tests but add several methods for creating [api:SS_HTTPRequest]
and receiving [api:SS_HTTPResponse] objects. In this How To, we'll see how to write a test to query a page, check the
response and modify the session within a test.
**mysite/tests/HomePageTest.php**
:::php
<?php
class HomePageTest extends SapphireTest {
/**
* Test generation of the view
*/
public function testViewHomePage() {
$page = $this->get('home/');
// Home page should load..
$this->assertEquals(200, $page->getStatusCode());
// We should see a login form
$login = $this->submitForm("#LoginForm", null, array(
'Email' => 'test@test.com',
'Password' => 'wrongpassword'
));
// wrong details, should now see an error message
$this->assertExactHTMLMatchBySelector("#LoginForm p.error", array(
"That email address is invalid."
));
// If we login as a user we should see a welcome message
$me = Member::get()->first();
$this->logInAs($me);
$page = $this->get('home/');
$this->assertExactHTMLMatchBySelector("#Welcome", array(
'Welcome Back'
));
}
}
## Related Documentation
* [Functional Testing](../functional_testing)
* [Unit Testing](../unit_testing)
## API Documentation
* [api:FunctionalTest]

View File

@ -0,0 +1,50 @@
title: How to use a FixtureFactory
# How to use a FixtureFactory
The [api:FixtureFactory] is used to manually create data structures for use with tests. For more information on fixtures
see the [Fixtures](../fixtures) documentation.
In this how to we'll use a `FixtureFactory` and a custom blue print for giving us a shortcut for creating new objects
with information that we need.
:::php
class MyObjectTest extends SapphireTest {
protected $factory;
function __construct() {
parent::__construct();
$factory = Injector::inst()->create('FixtureFactory');
// Defines a "blueprint" for new objects
$factory->define('MyObject', array(
'MyProperty' => 'My Default Value'
));
$this->factory = $factory;
}
function testSomething() {
$MyObjectObj = $this->factory->createObject(
'MyObject',
array('MyOtherProperty' => 'My Custom Value')
);
echo $MyObjectObj->MyProperty;
// returns "My Default Value"
echo $myPageObj->MyOtherProperty;
// returns "My Custom Value"
}
}
## Related Documentation
* [Fixtures](../fixtures)
## API Documentation
* [api:FixtureFactory]
* [api:FixtureBlueprint]

View File

@ -0,0 +1,40 @@
title: How to test emails within unit tests
# Testing Email within Unit Tests
SilverStripe's test system has built-in support for testing emails sent using the `[api:Email]` class. If you are
running a `[api:SapphireTest]` test, then it holds off actually sending the email, and instead lets you assert that an
email was sent using this method.
:::php
public function MyMethod() {
$e = new Email();
$e->To = "someone@example.com";
$e->Subject = "Hi there";
$e->Body = "I just really wanted to email you and say hi.";
$e->send();
}
To test that `MyMethod` sends the correct email, use the [api:Email::assertEmailSent] method.
:::php
$this->assertEmailSend($to, $from, $subject, $body);
// to assert that the email is sent to the correct person
$this->assertEmailSent("someone@example.com", null, "/th.*e$/");
Each of the arguments (`$to`, `$from`, `$subject` and `$body`) can be either one of the following.
* A string: match exactly that string
* `null/false`: match anything
* A PERL regular expression (starting with '/')
## Related Documentation
* [Email](../../email)
## API Documentation
* [api:Email]

View File

@ -1,3 +1,5 @@
summary: Deploy robust applications by bundling Unit and Behavior tests with your application code and modules.
# Unit and Integration Testing
For behaviour testing in SilverStripe, check out [SilverStripe Behat Documentation](https://github.com/silverstripe-labs/silverstripe-behat-extension/).

View File

@ -0,0 +1,88 @@
title: Environment Types
summary: Configure your SilverStripe environment to define how your web application behaves.
# Environment Types
SilverStripe knows three different environment types (or "modes"). Each of the modes gives you different tools
and behaviors. The environment is managed either through a [YML configuration file](../configuration) or in a
[environment configuration file](../../getting_started/environment_management).
The definition of setting an environment type in a `mysite/_config/app.yml` looks like
:::yml
Director:
environment_type: 'dev'
The definition of setting an environment type in a `_ss_environment.php` file looks like
:::php
define('SS_ENVIRONMENT_TYPE', 'dev');
The three environment types you can set are `dev`, `test` and `live`.
### Dev
When developing your websites, adding page types or installing modules you should run your site in `dev`. In this mode
you will see full error back traces and view the development tools without having to be logged in as an administrator
user.
<div class="alert" markdown="1">
**dev mode should not be enabled long term on live sites for security reasons**. In dev mode by outputting back traces
of function calls a hacker can gain information about your environment (including passwords) so you should use dev mode
on a public server very carefully.
</div>
### Test Mode
Test mode is designed for staging environments or other private collaboration sites before deploying a site live.
In this mode error messages are hidden from the user and SilverStripe includes `[api:BasicAuth]` integration if you
want to password protect the site. You can enable that but adding this to your `mysite/_config/app.yml` file:
:::yml
---
Only:
environment: 'test'
---
BasicAuth:
entire_site_protected: true
### Live Mode
All error messages are suppressed from the user and the application is in it's most *secure* state.
<div class="alert">
Live sites should always run in live mode. You should not run production websites in dev mode.
</div>
## Checking Environment Type
You can check for the current environment type in [config files](../configuration) through the `environment` variant.
**mysite/_config/app.yml**
---
Only:
environment: 'live'
---
MyClass:
myvar: live_value
---
Only:
environment: 'test'
---
MyClass:
myvar: test_value
Checking for what environment you're running in can also be done in PHP. Your application code may disable or enable
certain functionality depending on the environment type.
:::php
if(Director::isLive()) {
// is in live
} else if(Director::isTest()) {
// is in test mode
} else if(Director::isDev()) {
// is in dev mode
}

View File

@ -0,0 +1,73 @@
title: Error Handling
summary: Trap, fire and report user exceptions, warnings and errors.
# Error Handling
SilverStripe has its own error trapping and handling support. On development sites, SilverStripe will deal harshly with
any warnings or errors: a full call-stack is shown and execution stops for anything, giving you early warning of a
potential issue to handle.
## Triggering the error handler.
You should use [user_error](http://www.php.net/user_error) to throw errors where appropriate.
:::php
if(true == false) {
user_error("I have an error problem", E_USER_ERROR);
}
if(0 / 0) {
user_error("This time I am warning you", E_USER_WARNING);
}
## Error Levels
* **E_USER_WARNING:** Err on the side of over-reporting warnings. Throwing warnings provides a means of ensuring that
developers know:
* Deprecated functions / usage patterns
* Strange data formats
* Things that will prevent an internal function from continuing. Throw a warning and return null.
* **E_USER_ERROR:** Throwing one of these errors is going to take down the production site. So you should only throw
E_USER_ERROR if it's going to be **dangerous** or **impossible** to continue with the request.
## Filesystem Logs
You can indicate a log file relative to the site root.
**mysite/_config.php**
:::php
if(!Director::isDev()) {
// log errors and warnings
SS_Log::add_writer(new SS_LogFileWriter('/my/logfile/path'), SS_Log::WARN, '<=');
// or just errors
SS_Log::add_writer(new SS_LogFileWriter('/my/logfile/path'), SS_Log::ERR);
}
<div class="info" markdown="1">
In addition to SilverStripe-integrated logging, it is advisable to fall back to PHPs native logging functionality. A
script might terminate before it reaches the SilverStripe error handling, for example in the case of a fatal error. Make
sure `log_errors` and `error_log` in your PHP ini file are configured.
</div>
## Email Logs
You can send both fatal errors and warnings in your code to a specified email-address.
**mysite/_config.php**
:::php
if(!Director::isDev()) {
// log errors and warnings
SS_Log::add_writer(new SS_LogEmailWriter('admin@domain.com'), SS_Log::WARN, '<=');
// or just errors
SS_Log::add_writer(new SS_LogEmailWriter('admin@domain.com'), SS_Log::ERR);
}
## API Documentation
* [api:SS_Log]

View File

@ -0,0 +1,34 @@
summary: Learn how to identify errors in your application and best practice for logging application errors.
# Debugging
SilverStripe can be a large and complex framework to debug, but there are ways to make debugging less painful. In this
guide we show the basics on defining the correct [Environment Type](environment_type) for your application and other
built-in helpers for dealing with application errors.
[CHILDREN]
## Performance
See the [Profiling](../performance/profiling) documentation for more information on profiling SilverStripe to track down
bottle-necks and identify slow moving parts of your application chain.
## Debugging Utilities
The [api:Debug] class contains a number of static utility methods for more advanced debugging.
:::php
Debug::show($myVariable);
// similar to print_r($myVariable) but shows it in a more useful format.
Debug::message("Wow, that's great");
// prints a short debugging message.
SS_Backtrace::backtrace();
// prints a calls-stack
## API Documentation
* [api:SS_Log]
* [api:SS_Backtrace]
* [api:Debug]

View File

@ -1,64 +1,49 @@
title: Partial Caching
summary: Cache SilverStripe templates to reduce database queries.
# Partial Caching
## Introduction
Partial caching is a feature that allows the caching of just a portion of a page.
As opposed to static publishing, which avoids the SilverStripe controller layer on cached pages, partial caching allows
caching for pages that contain a mix of moderately static & user specific data, and still provide full access control
and permission enforcement.
The trade-off is that it does not provide as much performance improvement as static publishing, although for data heavy
pages the speed increases can be significant.
## Basics
The way you mark a section of the template as being cached is to wrap that section in a cached tag, like so:
Partial caching is a feature that allows the caching of just a portion of a page.
:::ss
<% cached %>
<% cached 'CacheKey' %>
$DataTable
...
<% end_cached %>
Each cache block has a cache key - an unlimited number of comma separated variables (in the same form as `if` and
`loop`/`with` tag variables) and quoted strings.
Every time the cache key returns a different result, the contents of the block are recalculated. If the cache key is the
same as a previous render, the cached value stored last time is used.
Each cache block has a cache key. A cache key is an unlimited number of comma separated variables and quoted strings.
Every time the cache key returns a different result, the contents of the block are recalculated. If the cache key is
the same as a previous render, the cached value stored last time is used.
Since the above example contains just one argument as the cache key, a string (which will be the same every render) it
will invalidate the cache after the TTL has expired (default 10 minutes)
will invalidate the cache after a given amount of time has expired (default 10 minutes).
Here are some more complex examples:
From a block that updates every time the Page subclass it's the template for updates
:::ss
<% cached 'database', LastEdited %>
From a block that shows a login block if not logged in, or a homepage link if logged in, depending on the current member
:::ss
<% cached 'database', LastEdited %>
<!-- that updates every time the record changes. -->
<% end_cached %>
<% cached 'loginblock', CurrentMember.ID %>
<!-- cached unique to the user. i.e for user 2, they will see a different cache to user 1 -->
<% end_cached %>
From a block that shows a summary of the page edits if administrator, nothing if not
:::ss
<% cached 'loginblock', LastEdited, CurrentMember.isAdmin %>
<!-- recached when block object changes, and if the user is admin -->
<% end_cached %>
An additional global key is incorporated in the cache lookup. The default value for this is
`$CurrentReadingMode, $CurrentUser.ID`, which ensures that the current `[api:Versioned]` state and user ID are
used. This may be configured by changing the config value of `SSViewer.global_key`. It is also necessary
to flush the template caching when modifying this config, as this key is cached within the template itself.
An additional global key is incorporated in the cache lookup. The default value for this is
`$CurrentReadingMode, $CurrentUser.ID`. This ensures that the current `[api:Versioned]` state and user ID are used.
This may be configured by changing the config value of `SSViewer.global_key`. It is also necessary to flush the
template caching when modifying this config, as this key is cached within the template itself.
For example, to ensure that the cache is configured to respect another variable, and if the current logged in
user does not influence your template content, you can update this key as below;
**mysite/_config/app.yml**
:::yaml
SSViewer:
global_key: '$CurrentReadingMode, $Locale'
@ -66,39 +51,45 @@ user does not influence your template content, you can update this key as below;
## Aggregates
Often you want to invalidate a cache when any in a set of objects change, or when the objects in a relationship change.
To help do this, SilverStripe introduces the concept of Aggregates. These calculate and return SQL aggregates
on sets of `[api:DataObject]`s - the most useful for us being the Max aggregate.
Often you want to invalidate a cache when any object in a set of objects change, or when the objects in a relationship
change. To do this, SilverStripe introduces the concept of Aggregates. These calculate and return SQL aggregates
on sets of [api:DataObject]s - the most useful for us being the `Max` aggregate.
For example, if we have a menu, we want that menu to update whenever _any_ page is edited, but would like to cache it
otherwise. By using aggregates, we can do that like this:
otherwise. By using aggregates, we do that like this:
:::ss
<% cached 'navigation', List(SiteTree).max(LastEdited), List(SiteTree).count() %>
If we have a block that shows a list of categories, we can make sure the cache updates every time a category is added or
edited
The cache for this will update whenever a page is added, removed or edited.
If we have a block that shows a list of categories, we can make sure the cache updates every time a category is added
or edited
:::ss
<% cached 'categorylist', List(Category).max(LastEdited), List(Category).count() %>
Note the use of both .max(LastEdited) and .count() - this takes care of both the case where an object has been edited
since the cache was last built, and also when an object has been deleted/un-linked since the cache was last built.
<div class="notice" markdown="1">
Note the use of both `.max(LastEdited)` and `.count()` - this takes care of both the case where an object has been
edited since the cache was last built, and also when an object has been deleted since the cache was last built.
</div>
We can also calculate aggregates on relationships. A block that shows the current member's favourites needs to update
whenever the relationship Member::$has_many = array('Favourites' => Favourite') changes.
We can also calculate aggregates on relationships. A block that shows the current member's favorites needs to update
whenever the relationship `Member::$has_many = array('Favourites' => Favourite')` changes.
:::ss
<% cached 'favourites', CurrentMember.ID, CurrentMember.Favourites.max(LastEdited) %>
## Cache key calculated in controller
That last example is a bit large, and is complicating our template up with icky logic. Better would be to extract that
logic into the controller
In the previous example the cache key is getting a bit large, and is complicating our template up. Better would be to
extract that logic into the controller.
:::php
public function FavouriteCacheKey() {
$member = Member::currentUser();
return implode('_', array(
'favourites',
$member->ID,
@ -106,8 +97,7 @@ logic into the controller
));
}
and then using that function in the cache key
Then using that function in the cache key:
:::ss
<% cached FavouriteCacheKey %>
@ -159,29 +149,28 @@ heavy load:
<% cached 'blogstatistics', Blog.ID if HighLoad %>
By adding a HighLoad function to your page controller, you could enable or disable caching dynamically.
By adding a `HighLoad` function to your `Page_Controller`, you could enable or disable caching dynamically.
To cache the contents of a page for all anonymous users, but dynamically calculate the contents for logged in members,
you could use something like:
use something like:
:::ss
<% cached unless CurrentUser %>
## Uncached
As a shortcut, the template tag 'uncached' can be used - it is the exact equivilent of a cached block with an if
condition that always returns false. The key and conditionals in an uncached tag are ignored, so you can easily
temporarily disable a particular cache block by changing just the tag, leaving the key and conditional intact.
Yhe template tag 'uncached' can be used - it is the exact equivalent of a cached block with an if condition that always
returns false. The key and conditionals in an uncached tag are ignored, so you can easily temporarily disable a
particular cache block by changing just the tag, leaving the key and conditional intact.
:::ss
<% uncached %>
## Nested cacheblocks
## Nested cache blocks
You can also nest independent cache blocks (with one important rule, discussed later).
Any nested cache blocks are calculated independently from their containing block, regardless of the cached state of that
container.
You can also nest independent cache blocks Any nested cache blocks are calculated independently from their containing
block, regardless of the cached state of that container.
This allows you to wrap an entire page in a cache block on the page's LastEdited value, but still keep a member-specific
portion dynamic, without having to include any member info in the page's cache key.
@ -217,11 +206,10 @@ could also write the last example as:
$ASlowCalculation
<% end_cached %>
## The important rule
<div class="warning" markdown="1">
Currently cached blocks can not be contained within if or loop blocks. The template engine will throw an error
letting you know if you've done this. You can often get around this using aggregates.
</div>
Failing example:
@ -236,8 +224,6 @@ Failing example:
<% end_cached %>
Can be re-written as:
:::ss
@ -249,4 +235,4 @@ Can be re-written as:
<% end_loop %>
<% end_cached %>
<% end_cached %>
<% end_cached %>

View File

@ -0,0 +1,33 @@
title: HTTP Cache Headers
summary: Set the correct HTTP cache headers for your responses.
# Caching Headers
By default, PHP adds caching headers that make the page appear purely dynamic. This isn't usually appropriate for most
sites, even ones that are updated reasonably frequently. SilverStripe overrides the default settings with the following
headers:
* The `Last-Modified` date is set to be most recent modification date of any database record queried in the generation
of the page.
* The `Expiry` date is set by taking the age of the page and adding that to the current time.
* `Cache-Control` is set to `max-age=86400, must-revalidate`
* Since a visitor cookie is set, the site won't be cached by proxies.
* Ajax requests are never cached.
## Customizing Cache Headers
### HTTP::set_cache_age
:::php
HTTP::set_cache_age(0);
Used to set the max-age component of the cache-control line, in seconds. Set it to 0 to disable caching; the "no-cache"
clause in `Cache-Control` and `Pragma` will be included.
### HTTP::register_modification_date
:::php
HTTP::register_modification_date('2014-10-10');
Used to set the modification date to something more recent than the default. [api:DataObject::__construct] calls
[api:HTTP::register_modification_date(] whenever a record comes from the database ensuring the newest date is present.

View File

@ -0,0 +1,13 @@
title: Profiling
summary: Identify bottlenecks within your application.
# Profiling
Profiling is the best way to identify bottle necks and other slow moving parts of your application prime for
optimization.
SilverStripe does not include any profiling tools out of the box, but we recommend the use of existing tools such as
[XHProf](https://github.com/facebook/xhprof/) and [XDebug](http://xdebug.org/).
* [Profiling with XHProf](http://techportal.inviqa.com/2009/12/01/profiling-with-xhprof/)
* [Profiling PHP Applications With xdebug](http://devzone.zend.com/1139/profiling-php-applications-with-xdebug/)

View File

@ -0,0 +1,20 @@
title: Static Publishing
summary: Export your web pages as static HTML and serve the web like it's 1999.
# Static Publishing
One of the best ways to get the top performance out of SilverStripe is to bypass it completely. This saves on any loading
time, connecting to the database and formatting your templates. This is only appropriate approach on web pages that
have completely static content.
<div class="info" markdown="1">
If you want to cache part of a page, or your site has interactive elements such as forms, then
[Partial Caching](partial_caching) is more suitable.
</div>
By publishing the page as HTML it's possible to run SilverStripe from behind a corporate firewall, on a low performance
server or serve millions of hits an hour without expensive hardware.
This functionality is available through the [StaticPublisher](https://github.com/silverstripe-labs/silverstripe-staticpublisher)
module. The module provides hooks for developers to generate static HTML files for the whole application or publish key
pages (e.g a web applications home page) as HTML to reduce load on the server.

View File

@ -0,0 +1,34 @@
title: Resource Usage
summary: Manage SilverStripe's memory footprint and CPU usage.
# Resource Usage
SilverStripe tries to keep its resource usage within the documented limits
(see the [server requirements](../../getting_started/server_requirements)).
These limits are defined through `memory_limit` and `max_execution_time` in the PHP configuration. They can be
overwritten through `ini_set()`, unless PHP is running with the [Suhoshin Patches](http://www.hardened-php.net/)
or in "[safe mode](http://php.net/manual/en/features.safe-mode.php)".
<div class="alert" markdown="1">
Most shared hosting providers will have maximum values that can't be altered.
</div>
For certain tasks like synchronizing a large `assets/` folder with all file and folder entries in the database, more
resources are required temporarily. In general, we recommend running resource intensive tasks through the
[command line](../cli), where configuration defaults for these settings are higher or even unlimited.
<div class="info" markdown="1">
SilverStripe can request more resources through `increase_memory_limit_to()` and `increase_time_limit_to()` functions.
</div>
:::php
function myBigFunction() {
increase_time_limit_to(400);
// or..
set_increase_time_limit_max();
// ..
}

View File

@ -0,0 +1,14 @@
title: Performance
summary: Make your applications faster by learning how to write more scalable code and ways to cache your important information.
introduction: Make your applications faster by learning how to write more scalable code and ways to cache your important information.
The following guide describes the common ways to speed your SilverStripe website up. The general rules for getting
the best performance out of SilverStripe include running the latest versions of PHP alongside a
[opcode](http://en.wikipedia.org/wiki/Opcode) cache such as [XCache](http://xcache.lighttpd.net/) or
[APC](http://nz2.php.net/manual/en/intro.apc.php).
If you're running shared hosting, make sure your host meets the minimum system requirements and has activated one of the
PHP opcode caches to achieve the best results for your application. Once your hardware is performing it's best, dig
into the guides below to see what you can do.
[CHILDREN Exclude=How_Tos]

View File

@ -1,3 +1,5 @@
title: Members
# Member
## Introduction

View File

@ -1,6 +1,9 @@
title: Authentication
summary: Explains SilverStripe's Authentication options and custom authenticators.
# Authentication
By default, silverstripe provides a `[api:MemberAuthenticator]` class which hooks into its own internal
By default, SilverStripe provides a `[api:MemberAuthenticator]` class which hooks into its own internal
authentication system.
The main login system uses these controllers to handle the various security requests:
@ -39,7 +42,7 @@ following base classes:
## Default Admin
When a new silverstripe site is created for the first time, it may be necessary to create a default admin to provide
When a new SilverStripe site is created for the first time, it may be necessary to create a default admin to provide
CMS access for the first time. SilverStripe provides a default admin configuration system, which allows a username
and password to be configured for a single special user outside of the normal membership system.

View File

@ -406,17 +406,19 @@ file in the assets directory. This requires PHP to be loaded as an Apache modul
php_flag engine off
Options -ExecCGI -Includes -Indexes
### Don't allow access to .yml files
### Don't allow access to YAML files
Yaml files are often used to store sensitive or semi-sensitive data for use by SilverStripe framework (for instance,
configuration and test fixtures).
You should therefore block access to all yaml files (extension .yml) by default, and white list only yaml files
you need to serve directly.
See [Apache](/installation/webserver) and [Nginx](/installation/nginx) installation documentation for details
specific to your web server
YAML files are often used to store sensitive or semi-sensitive data for use by
SilverStripe, such as configuration files. We block access to any files
with a `.yml` or `.yaml` extension through the default web server rewriting rules.
If you need users to access files with this extension,
you can bypass the rules for a specific directory.
Here's an example for a `.htaccess` file used by the Apache web server:
<Files *.yml>
Order allow,deny
Allow from all
</Files>
## Passwords

View File

@ -0,0 +1,12 @@
summary: This guide covers user authentication, the permission system and how to secure your code against malicious behaviors
# Security and User Authentication
This guide covers using and extending the user authentication in SilverStripe, permissions, user groups and roles, and
how to secure your code against malicious behaviors of both your users and hackers.
[CHILDREN Exclude=How_to]
## How to's
[CHILDREN Folder=How_To]

View File

@ -0,0 +1,217 @@
summary: Send HTML and plain text email from your SilverStripe application.
# Email
Creating and sending email in SilverStripe is done through the [api:Email] and [api:Mailer] classes. This document
covers how to create an `Email` instance, customize it with a HTML template, then send it through a custom `Mailer`.
## Configuration
Out of the box, SilverStripe will use the built-in PHP `mail()` command. If you are not running an SMTP server, you
will need to either configure PHP's SMTP settings (see [PHP documentation](http://php.net/mail) to include your mail
server configuration or use one of the third party SMTP services like [Mandrill](https://github.com/lekoala/silverstripe-mandrill)
and [Postmark](https://github.com/fullscreeninteractive/silverstripe-postmarkmailer).
## Usage
### Sending plain text only
:::php
$email = new Email($from, $to, $subject, $body);
$email->sendPlain();
### Sending combined HTML and plain text
By default, emails are sent in both HTML and Plaintext format. A plaintext representation is automatically generated
from the system by stripping HTML markup, or transforming it where possible (e.g. `<strong>text</strong>` is converted
to `*text*`).
:::php
$email = new Email($from, $to, $subject, $body);
$email->send();
<div class="info" markdown="1">
The default HTML template for emails is named `GenericEmail` and is located in `framework/templates/email/`. To
customize this template, copy it to the `mysite/templates/Email/` folder or use `setTemplate` when you create the
`Email` instance.
</div>
### Templates
HTML emails can use custom templates using the same template language as your website template. You can also pass the
email object additional information using the `populateTemplate` method.
**mysite/templates/Email/MyCustomEmail.ss**
:::ss
<h1>Hi $Member.FirstName</h1>
<p>You can go to $Link.</p>
The PHP Logic..
:::php
$email = new Email();
$email
->setFrom($from)
->setTo($to)
->setSubject($subject)
->setTemplate('MyCustomEmail')
->populateTemplate(new ArrayData(array(
'Member' => Member::currentUser(),
'Link' => $link
)));
$email->send();
<div class="alert" markdown="1">
As we've added a new template file (`MyCustomEmail`) make sure you clear the SilverStripe cache for your changes to
take affect.
</div>
## Sub classing
To keep your application code clean and your internal API clear, a better approach to generating an email is to create
a new subclass of `Email` which takes the required dependencies and handles setting the properties itself.
**mysite/code/MyCustomEmail.php**
:::php
<?php
class MyEmail extends Email {
protected $ss_template = "MyEmail";
public function __construct($member) {
$from = 'no-reply@mysite.com';
$to = $member->Email;
$subject = "Welcome to our site.";
$link = Director::absoluteBaseUrl();
parent::__construct($from, $to, $subject);
$this->populateTemplate(new ArrayData(array(
'Member' => $member->Email,
'Link' => $link
)));
}
}
Then within your application, usage of the email is much clearer to follow.
:::php
<?php
$member = Member::currentUser();
$email = new MyEmail($member);
$email->send();
## Administrator Emails
You can set the default sender address of emails through the `Email.admin_email` [configuration setting](/developer_guides/configuration).
**mysite/_config/app.yml**
:::yaml
Email:
admin_email: support@silverstripe.org
<div class="alert" markdown="1">
Remember, setting a `from` address that doesn't come from your domain (such as the users email) will likely see your
email marked as spam. If you want to send from another address think about using the `replyTo` method.
</div>
## Redirecting Emails
There are several other [configuration settings](/developer_guides/configuration) to manipulate the email server.
* `Email.send_all_emails_to` will redirect all emails sent to the given address. This is useful for testing and staging
servers where you do not wish to send emails out.
* `Email.cc_all_emails_to` and `Email.bcc_all_emails_to` will add an additional recipient in the BCC / CC header.
These are good for monitoring system-generated correspondence on the live systems.
Configuration of those properties looks like the following:
**mysite/_config.php**
:::php
if(Director::isLive()) {
Config::inst()->update('Email', 'bcc_all_emails_to', "client@example.com");
} else {
Config::inst()->update('Email', 'send_all_emails_to', "developer@example.com");
}
### Setting custom replyTo
:::php
$email = new Email(..);
$email->replyTo('me@address.com');
### Setting Custom Headers
For email headers which do not have getters or setters (like setTo(), setFrom()) you can use **addCustomHeader($header,
$value)**
:::php
$email = new Email(...);
$email->addCustomHeader('HeaderName', 'HeaderValue');
..
<div class="info" markdown="1">
See this [Wikipedia](http://en.wikipedia.org/wiki/E-mail#Message_header) entry for a list of header names.
</div>
## Newsletters
The [newsletter module](http://silverstripe.org/newsletter-module) provides a UI and logic to send batch emails.
## Custom Mailers
SilverStripe supports changing out the underlying web server SMTP mailer service through the `Email::set_mailer()`
function. A `Mailer` subclass will commonly override the `sendPlain` and `sendHTML` methods to send emails through curl
or some other process that isn't the built in `mail()` command.
<div class="info" markdown="1">
There are a number of custom mailer add-ons available like [Mandrill](https://github.com/lekoala/silverstripe-mandrill)
and [Postmark](https://github.com/fullscreeninteractive/silverstripe-postmarkmailer).
</div>
In this example, `LocalMailer` will take any email's going while the site is in Development mode and save it to the
assets folder instead.
**mysite/code/LocalMailer.php**
:::php
<?php
class LocalMailer extends Mailer {
function sendHTML($to, $from, $subject, $htmlContent, $attachedFiles = false, $customheaders = false, $plainContent = false, $inlineImages = false) {
$file = ASSETS_PATH . '/_mail_'. urlencode(sprintf("%s_%s", $subject, $to));
file_put_contents($file, $htmlContent);
}
function sendPlain($to, $from, $subject, $htmlContent, $attachedFiles = false, $customheaders = false, $plainContent = false, $inlineImages = false) {
$file = ASSETS_PATH . '/_mail_'. urlencode(sprintf("%s_%s", $subject, $to));
file_put_contents($file, $htmlContent);
}
}
**mysite/_config.php**
:::php
if(Director::isLive()) {
Email::set_mailer(new PostmarkMailer());
} else {
Email::set_mailer(new LocalMailer());
}
## API Documentation
* [api:Email]

View File

@ -9,7 +9,7 @@ be customized to fit your data.
## The CsvBulkLoader class
The [api:CsvBulkLoader] class facilitate complex CSV-imports by defining column-mappings and custom converters.
The [api:CsvBulkLoader] class facilitate complex CSV-imports by defining column-mappings and custom converters.
It uses PHP's built-in `fgetcsv()` function to process CSV input, and accepts a file handle as an input.
Feature overview:
@ -52,7 +52,7 @@ The simplest way to use [api:CsvBulkLoader] is through a [api:ModelAdmin] interf
'Player'
);
private static $model_importers = array(
'Player' => 'PlayerCsvBulkLoader',
'Player' => 'PlayerCsvBulkLoader',
);
private static $url_segment = 'players';
}
@ -70,13 +70,13 @@ You can have more customized logic and interface feedback through a custom contr
class MyController extends Controller {
private static $allowed_actions = array('Form');
protected $template = "BlankPage";
public function Link($action = null) {
return Controller::join_links('MyController', $action);
}
public function Form() {
$form = new Form(
$this,
@ -91,7 +91,7 @@ You can have more customized logic and interface feedback through a custom contr
);
return $form;
}
public function doUpload($data, $form) {
$loader = new CsvBulkLoader('MyDataObject');
$results = $loader->load($_FILES['CsvFile']['tmp_name']);
@ -101,7 +101,7 @@ You can have more customized logic and interface feedback through a custom contr
if($results->DeletedCount()) $messages[] = sprintf('Deleted %d items', $results->DeletedCount());
if(!$messages) $messages[] = 'No changes';
$form->sessionMessage(implode(', ', $messages), 'good');
return $this->redirectBack();
}
}
@ -128,9 +128,9 @@ Datamodel for Player
class Player extends DataObject {
private static $db = array(
'PlayerNumber' => 'Int',
'FirstName' => 'Text',
'LastName' => 'Text',
'Birthday' => 'Date',
'FirstName' => 'Text',
'LastName' => 'Text',
'Birthday' => 'Date',
);
private static $has_one = array(
'Team' => 'FootballTeam'
@ -145,7 +145,7 @@ Datamodel for FootballTeam:
<?php
class FootballTeam extends DataObject {
private static $db = array(
'Title' => 'Text',
'Title' => 'Text',
);
private static $has_many = array(
'Players' => 'Player'

View File

@ -0,0 +1,176 @@
summary: Consume external data through their RESTFul interfaces.
# Restful Service
`[api:RestfulService]` is used to enable connections to remote web services through PHP's `curl` command. It provides an
interface and utility functions for generating a valid request and parsing the response returned from the web service.
<div class="alert" markdown="1">
RestfulService currently only supports XML. It has no JSON support at this stage.
</div>
## Examples
### Creating a new RestfulObject
`getWellingtonWeather` queries the Yahoo Weather API for an XML file of the latest weather information. We pass a query
string parameter `q` with the search query and then convert the resulting XML data to an `ArrayList` object to display
in the template.
**mysite/code/Page.php**
:::php
public function getWellingtonWeather() {
$fetch = new RestfulService(
'https://query.yahooapis.com/v1/public/yql'
);
$fetch->setQueryString(array(
'q' => 'select * from weather.forecast where woeid in (select woeid from geo.places(1) where text="Wellington, NZ")'
));
// perform the query
$conn = $fetch->request();
// parse the XML body
$msgs = $fetch->getValues($conn->getBody(), "results");
// generate an object our templates can read
$output = new ArrayList();
if($msgs) {
foreach($msgs as $msg) {
$output->push(new ArrayData(array(
'Description' => Convert::xml2raw($msg->channel_item_description)
)));
}
}
return $output;
}
## Features
### Basic Authenication
:::php
$service = new RestfulService("http://example.harvestapp.com");
$service->basicAuth('username', 'password');
### Make multiple requests
:::php
$service = new RestfulService("http://example.harvestapp.com");
$peopleXML = $service->request('/people');
$people = $service->getValues($peopleXML, 'user');
// ...
$taskXML = $service->request('/tasks');
$tasks = $service->getValues($taskXML, 'task');
### Caching
To set the cache interval you can pass it as the 2nd argument to constructor.
:::php
$expiry = 60 * 60; // 1 hour;
$request = new RestfulService("http://example.harvestapp.com", $expiry );
### Getting Values & Attributes
You can traverse through document tree to get the values or attribute of a particular node using XPath. Take for example
the following XML that is returned.
:::xml
<entries>
<entry id='12'>Sally</entry>
<entry id='15'>Ted</entry>
<entry id='30'>Matt</entry>
<entry id='22'>John</entry>
</entries>
To extract the id attributes of the entries use:
:::php
$this->getAttributes($xml, "entries", "entry");
// array(array('id' => 12), array('id' => '15'), ..)
To extract the values (the names) of the entries use:
:::php
$this->getValues($xml, "entries", "entry");
// array('Sally', 'Ted', 'Matt', 'John')
### Searching for Values & Attributes
If you don't know the exact position of DOM tree where the node will appear you can use xpath to search for the node.
<div class="note">
This is the recommended method for retrieving values of name spaced nodes.
</div>
:::xml
<media:guide>
<media:entry id="2030">video</media:entry>
</media:guide>
To get the value of entry node with the namespace media, use:
:::php
$this->searchValue($response, "//media:guide/media:entry");
// array('video');
## Best Practices
### Handling Errors
If the web service returned an error (for example, API key not available or inadequate parameters),
`[api:RestfulService]` can delegate the error handling to it's descendant class. To handle the errors, subclass
`RestfulService and define a function called errorCatch.
:::php
<?php
class MyRestfulService extends RestfulService {
public function errorCatch($response) {
$err_msg = $response;
if(strpos($err_msg, '<') === false) {
user_error("YouTube Service Error : $err_msg", E_USER_ERROR);
}
return $response;
}
}
If you want to bypass error handling, define `checkErrors` in the constructor for `RestfulService`
:::php
<?php
class MyRestfulService extends RestfulService {
public function __construct($expiry = NULL) {
parent::__construct('http://www.flickr.com/services/rest/', $expiry);
$this->checkErrors = false;
}
}
## How to's
* [Embed an RSS Feed](how_to/embed_rss)
## API Documentation
* `[api:RestfulService]`

View File

@ -0,0 +1,196 @@
title: RSS Feed
summary: Output records from your database as an RSS Feed.
# RSS Feed
Generating RSS / Atom-feeds is a matter of rendering a `[api:SS_List]` instance through the `[api:RSSFeed]` class.
The `[api:RSSFeed]` class doesn't limit you to generating article based feeds, it is just as easy to create a feed of
your current staff members, comments or any other custom `[api:DataObject]` subclasses you have defined. The only
logical limitation here is that every item in the RSS-feed should be accessible through a URL on your website, so it's
advisable to just create feeds from subclasses of `[api:SiteTree]`.
<div class="warning" markdown="1">
If you wish to generate an RSS feed that contains a `[api:DataObject]`, ensure you define a `AbsoluteLink` method on
the object.
</div>
## Usage
Including an RSS feed has two steps. First, a `Controller` action which responses with the `XML` and secondly, the other
web pages need to link to the URL to notify users that the RSS feed is available and where it is.
An outline of step one looks like:
:::php
$feed = new RSSFeed(
$list,
$link,
$title,
$description,
$titleField,
$descriptionField,
$authorField,
$lastModifiedTime,
$etag
);
$feed->outputToBrowser();
To achieve step two include the following code where ever you want to include the `<link>` tag to the RSS Feed. This
will normally go in your `Controllers` `init` method.
:::php
RSSFeed::linkToFeed($link, $title);
## Examples
### Showing the 10 most recently updated pages
You can use `[api:RSSFeed]` to easily create a feed showing your latest Page updates. The following example adds a page
`/home/rss/` which displays an XML file the latest updated pages.
**mysite/code/Page.php**
:::php
<?php
..
class Page_Controller extends ContentController {
private static $allowed_actions = array(
'rss'
);
public function init() {
parent::init();
RSSFeed::linkToFeed($this->Link() . "rss", "10 Most Recently Updated Pages");
}
public function rss() {
$rss = new RSSFeed(
$this->LatestUpdates(),
$this->Link(),
"10 Most Recently Updated Pages",
"Shows a list of the 10 most recently updated pages."
);
return $rss->outputToBrowser();
}
public function LatestUpdates() {
return Page::get()->sort("LastEdited", "DESC")->limit(10);
}
}
### Rendering DataObjects in a RSSFeed
DataObjects can be rendered in the feed as well, however, since they aren't explicitly `[api:SiteTree]` subclasses we
need to include a function `AbsoluteLink` to allow the RSS feed to link through to the item.
<div class="info">
If the items are all displayed on a single page you may simply hard code the link to point to a particular page.
</div>
Take an example, we want to create an RSS feed of all the `Players` objects in our site. We make sure the `AbsoluteLink`
method is defined and returns a string to the full website URL.
:::php
<?php
class Player extends DataObject {
public function AbsoluteLink() {
// assumes players can be accessed at yoursite.com/players/2
return Controller::join_links(
Director::absoluteBaseUrl(),
'players',
$this->ID
);
}
}
Then in our controller, we add a new action which returns a the XML list of `Players`.
:::php
<?php
class Page_Controller extends ContentController {
private static $allowed_actions = array(
'players'
);
public function init() {
parent::init();
RSSFeed::linkToFeed($this->Link("players"), "Players");
}
public function players() {
$rss = new RSSFeed(
Player::get(),
$this->Link("players"),
"Players"
);
return $rss->outputToBrowser();
}
}
### Customizing the RSS Feed template
The default template used for XML view is `framework/templates/RSSFeed.ss`. This template displays titles and links to
the object. To customize the XML produced use `setTemplate`.
Say from that last example we want to include the Players Team in the XML feed we might create the following XML file.
**mysite/templates/PlayersRss.ss**
:::xml
<?xml version="1.0"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>$Title</title>
<link>$Link</link>
<atom:link href="$Link" rel="self" type="application/rss+xml" />
<description>$Description.XML</description>
<% loop $Entries %>
<item>
<title>$Title.XML</title>
<team>$Team.Title</team>
</item>
<% end_loop %>
</channel>
</rss>
`setTemplate` can then be used to tell RSSFeed to use that new template.
**mysite/code/Page.php**
:::php
public function players() {
$rss = new RSSFeed(
Player::get(),
$this->Link("players"),
"Players"
);
$rss->setTemplate('PlayersRss');
return $rss->outputToBrowser();
}
<div class="warning">
As we've added a new template (PlayersRss.ss) make sure you clear your SilverStripe cache.
</div>
## API Documentation
* `[api:RSSFeed]`

View File

@ -0,0 +1,69 @@
title: Import CSV Data through a Controller
# Import CSV Data through a Controller
You can have more customized logic and interface feedback through a custom controller. Let's create a simple upload
form (which is used for `MyDataObject` instances). You can access it through
`http://yoursite.com/MyController/?flush=all`.
:::php
<?php
class MyController extends Controller {
private static $allowed_actions = array(
'Form'
);
protected $template = "BlankPage";
public function Link($action = null) {
return Controller::join_links('MyController', $action);
}
public function Form() {
$form = new Form(
$this,
'Form',
new FieldList(
new FileField('CsvFile', false)
),
new FieldList(
new FormAction('doUpload', 'Upload')
),
new RequiredFields()
);
return $form;
}
public function doUpload($data, $form) {
$loader = new CsvBulkLoader('MyDataObject');
$results = $loader->load($_FILES['CsvFile']['tmp_name']);
$messages = array();
if($results->CreatedCount()) {
$messages[] = sprintf('Imported %d items', $results->CreatedCount());
}
if($results->UpdatedCount()) {
$messages[] = sprintf('Updated %d items', $results->UpdatedCount());
}
if($results->DeletedCount()) {
$messages[] = sprintf('Deleted %d items', $results->DeletedCount());
}
if(!$messages) {
$messages[] = 'No changes';
}
$form->sessionMessage(implode(', ', $messages), 'good');
return $this->redirectBack();
}
}
<div class="alert" markdown="1">
This interface is not secured, consider using [api:Permission::check()] to limit the controller to users with certain
access rights.
</div>

View File

@ -0,0 +1,103 @@
title: A custom CSVBulkLoader instance
# How to: A custom CSVBulkLoader instance
A an implementation of a custom `CSVBulkLoader` loader. In this example. we're provided with a unique CSV file
containing a list of football players and the team they play for. The file we have is in the format like below.
"SpielerNummer", "Name", "Geburtsdatum", "Gruppe"
11, "John Doe", 1982-05-12,"FC Bayern"
12, "Jane Johnson", 1982-05-12,"FC Bayern"
13, "Jimmy Dole",,"Schalke 04"
This data needs to be imported into our application. For this, we have two `DataObjects` setup. `Player` contains
information about the individual player and a relation set up for managing the `Team`.
**mysite/code/Player.php**.
:::php
<?php
class Player extends DataObject {
private static $db = array(
'PlayerNumber' => 'Int',
'FirstName' => 'Text',
'LastName' => 'Text',
'Birthday' => 'Date'
);
private static $has_one = array(
'Team' => 'FootballTeam'
);
}
**mysite/code/FootballTeam.php**
:::php
<?php
class FootballTeam extends DataObject {
private static $db = array(
'Title' => 'Text'
);
private static $has_many = array(
'Players' => 'Player'
);
}
Now going back to look at the CSV, we can see that what we're provided with does not match what our data model looks
like, so we have to create a sub class of `CsvBulkLoader` to handle the unique file. Things we need to consider with
the custom importer are:
* Convert property names (e.g Number to PlayerNumber) through providing a `$columnMap`.
* Split a combined "Name" field into `FirstName` and `LastName` by calling `importFirstAndLastName` on the `Name`
column
* Prevent duplicate imports by a custom `$duplicateChecks` definition.
* Create a `Team` automatically based on the `Gruppe` column and a entry for `$relationCallbacks`
Our final import looks like this.
**mysite/code/PlayerCsvBulkLoader.php**
:::php
<?php
class PlayerCsvBulkLoader extends CsvBulkLoader {
public $columnMap = array(
'Number' => 'PlayerNumber',
'Name' => '->importFirstAndLastName',
'Geburtsdatum' => 'Birthday',
'Gruppe' => 'Team.Title',
);
public $duplicateChecks = array(
'SpielerNummer' => 'PlayerNumber'
);
public $relationCallbacks = array(
'Team.Title' => array(
'relationname' => 'Team',
'callback' => 'getTeamByTitle'
)
);
public static function importFirstAndLastName(&$obj, $val, $record) {
$parts = explode(' ', $val);
if(count($parts) != 2) return false;
$obj->FirstName = $parts[0];
$obj->LastName = $parts[1];
}
public static function getTeamByTitle(&$obj, $val, $record) {
return FootballTeam::get()->filter('Title', $val)->First();
}
}
## Related
* [api:CsvParser]
* [api:ModelAdmin]

View File

@ -0,0 +1,57 @@
title: Embed an RSS Feed
# Embed an RSS Feed
`[api:RestfulService]` can be used to easily embed an RSS feed from a site. In this How to we'll embed the latest
weather information from the Yahoo Weather API.
First, we write the code to query the API feed.
**mysite/code/Page.php**
:::php
public function getWellingtonWeather() {
$fetch = new RestfulService(
'https://query.yahooapis.com/v1/public/yql'
);
$fetch->setQueryString(array(
'q' => 'select * from weather.forecast where woeid in (select woeid from geo.places(1) where text="Wellington, NZ")'
));
// perform the query
$conn = $fetch->request();
// parse the XML body
$msgs = $fetch->getValues($conn->getBody(), "results");
// generate an object our templates can read
$output = new ArrayList();
if($msgs) {
foreach($msgs as $msg) {
$output->push(new ArrayData(array(
'Description' => Convert::xml2raw($msg->channel_item_description)
)));
}
}
return $output;
}
This will provide our `Page` template with a new `WellingtonWeather` variable (an [api:ArrayList]). Each item has a
single field `Description`.
**mysite/templates/Page.ss**
:::ss
<% if WellingtonWeather %>
<% loop WellingtonWeather %>
$Description
<% end_loop %>
<% end_if %>
## Related
* [RestfulService Documentation](../restfulservice)
* `[api:RestfulService]`

View File

@ -0,0 +1,5 @@
summary: Integrate other web services within your application or make your SilverStripe data available.
introduction: Integrate other web services within your application or make your SilverStripe data available.
title: Integration and Web Services
[CHILDREN]

View File

@ -0,0 +1,120 @@
title: Scaffolding with SearchContext
summary: Configure the search form within ModelAdmin using the SearchContext class.
# SearchContext
[api:SearchContext] manages searching of properties on one or more [api:DataObject] types, based on a given set of
input parameters. [api:SearchContext] is intentionally decoupled from any controller-logic, it just receives a set of
search parameters and an object class it acts on.
The default output of a [api:SearchContext] is either a [api:SQLQuery] object for further refinement, or a
[api:DataObject] instance.
<div class="notice" markdown="1">
[api:SearchContext] is mainly used by [ModelAdmin](../customising_the_cms/modeladmin).
</div>
## Usage
Defining search-able fields on your DataObject.
:::php
<?php
class MyDataObject extends DataObject {
private static $searchable_fields = array(
'Name',
'ProductCode'
);
}
## Customizing fields and filters
In this example we're defining three attributes on our MyDataObject subclass: `PublicProperty`, `HiddenProperty`
and `MyDate`. The attribute `HiddenProperty` should not be searchable, and `MyDate` should only search for dates
*after* the search entry (with a `GreaterThanFilter`).
:::php
<?php
class MyDataObject extends DataObject {
private static $db = array(
'PublicProperty' => 'Text'
'HiddenProperty' => 'Text',
'MyDate' => 'Date'
);
public function getCustomSearchContext() {
$fields = $this->scaffoldSearchFields(array(
'restrictFields' => array('PublicProperty','MyDate')
));
$filters = array(
'PublicProperty' => new PartialMatchFilter('PublicProperty'),
'MyDate' => new GreaterThanFilter('MyDate')
);
return new SearchContext(
$this->class,
$fields,
$filters
);
}
}
<div class="notice" markdown="1">
See the [SearchFilter](../model/searchfilters) documentation for more information about filters to use such as the
`GreaterThanFilter`.
</div>
<div class="notice" markdown="1">
In case you need multiple contexts, consider name-spacing your request parameters by using `FieldList->namespace()` on
the `$fields` constructor parameter.
</div>
### Generating a search form from the context
:::php
<?php
..
class Page_Controller extends ContentController {
public function SearchForm() {
$context = singleton('MyDataObject')->getCustomSearchContext();
$fields = $context->getSearchFields();
$form = new Form($this, "SearchForm",
$fields,
new FieldList(
new FormAction('doSearch')
)
);
return $form;
}
public function doSearch($data, $form) {
$context = singleton('MyDataObject')->getCustomSearchContext();
$results = $context->getResults($data);
return $this->customise(array(
'Results' => $results
))->renderWith('Page_results');
}
}
## Related Documentation
* [ModelAdmin](../customising_the_cms/modeladmin)
* [Tutorial: Site Search](/tutorials/site_search)
## API Documentation
* [api:SearchContext]
* [api:DataObject]

View File

@ -0,0 +1,44 @@
title: Fulltext Search
summary: Fulltext search allows sophisticated searching on text content.
# FulltextSearchable
Fulltext search allows advanced search criteria for searching words within a text based data column. While basic
Fulltext search can be achieved using the built-in [api:MySQLDatabase] class a more powerful wrapper for Fulltext
search is provided through a module.
<div class="notice" markdown="1">
See the [FulltextSearch Module](https://github.com/silverstripe-labs/silverstripe-fulltextsearch/). This module provides
a high level wrapper for running advanced search services such as Solr, Lucene or Sphinx in the backend rather than
`MySQL` search.
</div>
## Adding Fulltext Support to MySQLDatabase
The [api:MySQLDatabase] class defaults to creating tables using the InnoDB storage engine. As Fulltext search in MySQL
requires the MyISAM storage engine, any DataObject you wish to use with Fulltext search must be changed to use MyISAM
storage engine.
You can do so by adding this static variable to your class definition:
:::php
<?php
class MyDataObject extends DataObject {
private static $create_table_options = array(
'MySQLDatabase' => 'ENGINE=MyISAM'
);
}
The [api:FulltextSearchable] extension will add the correct `Fulltext` indexes to the data model.
<div class="alert" markdown="1">
The [api:SearchForm] and [api:FulltextSearchable] API's are currently hard coded to be specific to `Page` and `File`
records and cannot easily be adapted to include custom `DataObject` instances. To include your custom objects in the
default site search, have a look at those extensions and modify as required.
</div>
## API Documentation
* [api:FulltextSearchable]

View File

@ -0,0 +1,8 @@
title: Search
summary: Provide your users with advanced search functionality.
introduction: Give users the ability to search your applications. Fulltext search for Page Content (and other attributes like "Title") can be easily added to SilverStripe.
See the [Site Search Tutorial](/tutorials/site_search) for a detailed walk through of adding basic Search to your
website.
[CHILDREN]

View File

@ -1,46 +1,41 @@
# i18n
title: i18n
summary: Display templates and PHP code in different languages based on the preferences of your website users.
## Introduction
# i18n
The i18n class (short for "internationalization") in SilverStripe enables you to display templates and PHP code in
different languages based on your global settings and the preferences of your website users. This process is also known
as l10n (short for "localization").
For translating any content managed through the CMS or stored in the database,
please use the "[translatable](http://github.com/silverstripe/silverstripe-translatable)" module.
For translating any content managed through the CMS or stored in the database, please use the
[translatable](http://github.com/silverstripe/silverstripe-translatable) module.
This page aims to describe the low-level functionality of the i18n-API. It targets developers who:
* are involved in creating templates in different languages
* want to build their own modules with i18n capabilities
* want to make their PHP-code (e.g. form labels) i18n-ready
Please note that this project scope currently **doesn't include full support for format conversion in dates or
currencies**. Check our [roadmap](http://open.silverstripe.com/roadmap).
This page aims to describe the low-level functionality of the i18n API. It targets developers who:
* Are involved in creating templates in different languages.
* Want to build their own modules with i18n capabilities.
* Want to make their PHP-code (e.g. form labels) i18n-ready
## Usage
### Enabling i18n
The i18n class is enabled by default.
### Setting the locale
To set the locale you just need to call `[api:i18n::set_locale()]` passing, as a parameter, the name of the locale that you
want to set.
To set the locale you just need to call `[api:i18n::set_locale()]` passing, as a parameter, the name of the locale that
you want to set.
:::php
//Example 1: setting the locale
i18n::set_locale('de_DE'); //Setting the locale to German (Germany)
i18n::set_locale('ca_AD'); //Setting to Catalan (Andorra)
// mysite/_config.php
i18n::set_locale('de_DE'); // Setting the locale to German (Germany)
i18n::set_locale('ca_AD'); // Setting to Catalan (Andorra)
Once we set a locale, all the calls to the translator function will return strings according to the set locale value, if
these translations are available. See
[unicode.org](http://unicode.org/cldr/data/diff/supplemental/languages_and_territories.html) for a complete listing of
available locales.
these translations are available. See [unicode.org](http://unicode.org/cldr/data/diff/supplemental/languages_and_territories.html)
for a complete listing of available locales.
### Getting the locale
@ -53,15 +48,15 @@ To let browsers know which language they're displaying a document in, you can de
:::html
//'Page.ss' (HTML)
<html lang="$ContentLocale">
//'Page.ss' (XHTML)
<html lang="$ContentLocale" xml:lang="$ContentLocale" xmlns="http://www.w3.org/1999/xhtml">
Setting the '<html>' attribute is the most commonly used technique. There are other ways to specify content languages
Setting the `<html>` attribute is the most commonly used technique. There are other ways to specify content languages
(meta tags, HTTP headers), explained in this [w3.org article](http://www.w3.org/International/tutorials/language-decl/).
You can also set the [script direction](http://www.w3.org/International/questions/qa-scripts),
You can also set the [script direction](http://www.w3.org/International/questions/qa-scripts),
which is determined by the current locale, in order to indicate the preferred flow of characters
and default alignment of paragraphs and tables to browsers.
@ -70,7 +65,7 @@ and default alignment of paragraphs and tables to browsers.
### Date and time formats
Formats can be set globally in the i18n class. These settings are currently only picked up by the CMS, you'll need
Formats can be set globally in the i18n class. These settings are currently only picked up by the CMS, you'll need
to write your own logic for any frontend output.
:::php
@ -97,7 +92,7 @@ In order to add a value, add the following to your `config.yml`:
native: Kölsch
Similarly, to change an existing language label, you can overwrite one of these keys:
:::yml
i18n:
common_locales:
@ -106,11 +101,11 @@ Similarly, to change an existing language label, you can overwrite one of these
### i18n in URLs
By default, URLs for pages in SilverStripe (the `SiteTree->URLSegment` property)
are automatically reduced to the allowed allowed subset of ASCII characters.
By default, URLs for pages in SilverStripe (the `SiteTree->URLSegment` property)
are automatically reduced to the allowed allowed subset of ASCII characters.
If characters outside this subset are added, they are either removed or (if possible) "transliterated".
This describes the process of converting from one character set to another
while keeping characters recognizeable. For example, vowels with french accents
while keeping characters recognizeable. For example, vowels with french accents
are replaced with their base characters, `pâté` becomes `pate`.
In order to allow for so called "multibyte" characters outside of the ASCII subset,
@ -128,21 +123,21 @@ Date- and time related form fields support i18n ([api:DateField], [api:TimeField
$field = new DateField(); // will automatically set date format defaults for 'ca_AD'
$field->setLocale('de_DE'); // will not update the date formats
$field->setConfig('dateformat', 'dd. MMMM YYYY'); // sets typical 'de_DE' date format, shows as "23. Juni 1982"
Defaults can be applied globally for all field instances through the `DateField.default_config`
and `TimeField.default_config` [configuration arrays](/topics/configuration).
and `TimeField.default_config` [configuration arrays](/topics/configuration).
If no 'locale' default is set on the field, [api:i18n::get_locale()] will be used.
**Important:** Form fields in the CMS are automatically configured according to the profile settings for the logged-in user (`Member->Locale`, `Member->DateFormat` and `Member->TimeFormat`). This means that in most cases,
fields created through [api:DataObject::getCMSFields()] will get their i18n settings from a specific member
The [api:DateField] API can be enhanced by JavaScript, and comes with
The [api:DateField] API can be enhanced by JavaScript, and comes with
[jQuery UI datepicker](http://jqueryui.com/demos/datepicker/) capabilities built-in.
The field tries to translate the date formats and locales into a format compatible with jQuery UI
(see [api:DateField_View_JQuery::$locale_map_] and [api:DateField_View_JQuery::convert_iso_to_jquery_format()]).
:::php
$field = new DateField();
$field = new DateField();
$field->setLocale('de_AT'); // set Austrian/German locale
$field->setConfig('showcalendar', true);
$field->setConfig('jslocale', 'de'); // jQuery UI only has a generic German localization
@ -164,10 +159,10 @@ All strings passed through the `_t()` function will be collected in a separate l
### The _t() function
The `_t()` function is the main gateway to localized text, and takes four parameters, all but the first being optional.
The `_t()` function is the main gateway to localized text, and takes four parameters, all but the first being optional.
It can be used to translate strings in both PHP files and template files. The usage for each case is described below.
* **$entity:** Unique identifier, composed by a namespace and an entity name, with a dot separating them. Both are arbitrary names, although by convention we use the name of the containing class or template. Use this identifier to reference the same translation elsewhere in your code.
* **$entity:** Unique identifier, composed by a namespace and an entity name, with a dot separating them. Both are arbitrary names, although by convention we use the name of the containing class or template. Use this identifier to reference the same translation elsewhere in your code.
* **$string:** (optional) The original language string to be translated. Only needs to be declared once, and gets picked up the [text collector](#collecting-text).
* **$string:** (optional) Natural language comment (particularly short phrases and individual words)
are very context dependent. This parameter allows the developer to convey this information
@ -177,13 +172,13 @@ to the translator.
#### Usage in PHP Files
:::php
// Simple string translation
_t('LeftAndMain.FILESIMAGES','Files & Images');
// Using the natural languate comment parameter to supply additional context information to translators
_t('LeftAndMain.HELLO','Site content','Menu title');
// Using injection to add variables into the translated strings.
_t('CMSMain.RESTORED',
"Restored {value} successfully",
@ -207,18 +202,19 @@ the PHP version of the function.
:::ss
// Simple string translation
<%t Namespace.Entity "String to translate" %>
// Using the natural languate comment parameter to supply additional context information to translators
<%t SearchResults.NoResult "There are no results matching your query." is "A message displayed to users when the search produces no results." %>
// Using injection to add variables into the translated strings (note that $Name and $Greeting must be available in the current template scope).
<%t Header.Greeting "Hello {name} {greeting}" name=$Name greeting=$Greeting %>
#### Caching in Template Files with locale switching
When caching a `<% loop %>` or `<% with %>` with `<%t params %>`. It is important to add the Locale to the cache key otherwise it won't pick up locale changes.
When caching a `<% loop %>` or `<% with %>` with `<%t params %>`. It is important to add the Locale to the cache key
otherwise it won't pick up locale changes.
::::ss
:::ss
<% cached 'MyIdentifier', $CurrentLocale %>
<% loop $Students %>
$Name
@ -227,15 +223,12 @@ When caching a `<% loop %>` or `<% with %>` with `<%t params %>`. It is importan
## Collecting text
To collect all the text in code and template files we have just to visit:
`http://localhost/dev/tasks/i18nTextCollectorTask`
To collect all the text in code and template files we have just to visit: `http://localhost/dev/tasks/i18nTextCollectorTask`
Text collector will then read the files, build the master string table for each module where it finds calls to the
underscore function, and tell you about the created files and any possible entity redeclaration.
If you want to run the text collector for just one module you can use the 'module' parameter:
If you want to run the text collector for just one module you can use the 'module' parameter:
`http://localhost/dev/tasks/i18nTextCollectorTask/?module=cms`
<div class="hint" markdown='1'>
@ -256,7 +249,7 @@ By default, the language files are loaded from modules in this order:
This default order is configured in `framework/_config/i18n.yml`. This file specifies two blocks of module ordering: `basei18n`, listing admin, and framework, and `defaulti18n` listing all other modules.
To create a custom module order, you need to specify a config fragment that inserts itself either after or before those items. For example, you may have a number of modules that have to come after the framework/admin, but before anyhting else. To do that, you would use this
To create a custom module order, you need to specify a config fragment that inserts itself either after or before those items. For example, you may have a number of modules that have to come after the framework/admin, but before anyhting else. To do that, you would use this
---
Name: customi18n
@ -282,7 +275,7 @@ Each module can have one language table per locale, stored by convention in the
The translation is powered by [Zend_Translate](http://framework.zend.com/manual/en/zend.translate.html),
which supports different translation adapters, dealing with different storage formats.
By default, SilverStripe 3.x uses a YAML format (through the [Zend_Translate_RailsYAML adapter](https://github.com/chillu/zend_translate_railsyaml)).
By default, SilverStripe 3.x uses a YAML format (through the [Zend_Translate_RailsYAML adapter](https://github.com/chillu/zend_translate_railsyaml)).
Example: framework/lang/en.yml (extract)
@ -305,7 +298,7 @@ The cache can be cleared through the `?flush=1` query parameter,
or explicitly through `Zend_Translate::getCache()->clean(Zend_Cache::CLEANING_MODE_ALL)`.
<div class="hint" markdown='1'>
The format of language definitions has changed significantly in since version 2.x.
The format of language definitions has changed significantly in since version 2.x.
</div>
In order to enable usage of [version 2.x style language definitions](http://doc.silverstripe.org/framework/en/2.4/topics/i18n#language-tables-in-php) in 3.x, you need to register a legacy adapter
@ -330,10 +323,10 @@ Unlike the PHP logic, these files aren't auto-discovered and have to be included
### Requirements
Each language has its own language table in a separate file.
Each language has its own language table in a separate file.
To save bandwidth, only two files are actually loaded by
the browser: The current locale, and the default locale as a fallback.
The `Requirements` class has a special method to determine these includes:
the browser: The current locale, and the default locale as a fallback.
The `Requirements` class has a special method to determine these includes:
Just point it to a directory instead of a file, and the class will figure out the includes.
:::php
@ -364,8 +357,8 @@ Example Translation Table (`<my-module-dir>/javascript/lang/de.js`)
'MYMODULE.MYENTITY' : "Artikel wirklich löschen?"
});
For most core modules, these files are generated by a
[build task](https://github.com/silverstripe/silverstripe-buildtools/blob/master/src/GenerateJavascriptI18nTask.php),
For most core modules, these files are generated by a
[build task](https://github.com/silverstripe/silverstripe-buildtools/blob/master/src/GenerateJavascriptI18nTask.php),
with the actual source files in a JSON
format which can be processed more easily by external translation providers (see `javascript/lang/src`).

View File

@ -1,3 +1,5 @@
summary: Learn how to deal with File and Image records
# Files, Images and Folders
## Files, Images and Folders as database records
@ -19,19 +21,19 @@ All files, images and folders in the 'assets' directory are stored in the databa
If you have the CMS module installed, you can manage files, folders and images in the "Files" section of the CMS. Inside this section, you will see a list of files and folders like below:
![](_images/assets.png)
![](//_images/assets.png)
You can click on any file to edit it, or click on any folder to open it. To delete a file or a folder, simply click the red 'X' symbol next to it. If you click to open a folder, you can go back up one level by clicking the 'up' arrow above the folder name (highlighted below):
![](_images/assets_up.png)
![](//_images/assets_up.png)
Once you click to edit a file, you will see a form similar to the one below, in which you can edit the file's title, filename, owner, or even change which folder the file is located in:
![](_images/assets_editform.png)
![](//_images/assets_editform.png)
You may also notice the 'Sync files' button (highlighted below). This button allows CMS users to 'synchronise' the database (remember, all files/folders are stored as database records) with the filesystem. This is particularly useful if someone has uploaded or removed files/folders via FTP, for example.
![](_images/assets_sync.png)
![](//_images/assets_sync.png)
## Upload

View File

@ -0,0 +1,333 @@
title: ModelAdmin
summary: Create admin UI's for managing your data records.
# ModelAdmin
[api:ModelAdmin] provides a simple way to utilize the SilverStripe Admin UI with your own data models. It can create
searchables list and edit views of [api:DataObject] subclasses, and even provides import and export of your data.
It uses the framework's knowledge about the model to provide sensible defaults, allowing you to get started in a couple
of lines of code, while still providing a solid base for customization.
<div class="info" markdown="1">
The interface is mainly powered by the [api:GridField] class ([documentation](../forms/fields/gridfield)), which can
also be used in other areas of your application.
</div>
Let's assume we want to manage a simple product listing as a sample data model: A product can have a name, price, and
a category.
**mysite/code/Product.php**
:::php
<?php
class Product extends DataObject {
private static $db = array(
'Name' => 'Varchar',
'ProductCode' => 'Varchar',
'Price' => 'Currency'
);
private static $has_one = array(
'Category' => 'Category'
);
}
**mysite/code/Category.php**
:::php
<?php
class Category extends DataObject {
private static $db = array(
'Title' => 'Text'
);
private static $has_many = array(
'Products' => 'Product'
);
}
To create your own `ModelAdmin`, simply extend the base class, and edit the `$managed_models` property with the list of
DataObject's you want to scaffold an interface for. The class can manage multiple models in parallel, if required.
We'll name it `MyAdmin`, but the class name can be anything you want.
**mysite/code/MyAdmin.php**
:::php
<?php
class MyAdmin extends ModelAdmin {
private static $managed_models = array(
'Product',
'Category'
);
private static $url_segment = 'products';
private static $menu_title = 'My Product Admin';
}
This will automatically add a new menu entry to the SilverStripe Admin UI entitled `My Product Admin` and logged in
users will be able to upload and manage `Product` and `Category` instances through http://yoursite.com/admin/products.
<div class="alert" markdown="1">
After defining these classes, make sure you have rebuilt your SilverStripe database and flushed your cache.
</div>
## Permissions
Each new `ModelAdmin` subclass creates its' own [permission code](../security), for the example above this would be
`CMS_ACCESS_MyAdmin`. Users with access to the Admin UI will need to have this permission assigned through
`admin/security/` or have the `ADMIN` permission code in order to gain access to the controller.
<div class="notice" markdown="1">
For more information on the security and permission system see the [Security Documentation](../security)
</div>
The [api:DataObject] API has more granular permission control, which is enforced in [api:ModelAdmin] by default.
Available checks are `canEdit()`, `canCreate()`, `canView()` and `canDelete()`. Models check for administrator
permissions by default. For most cases, less restrictive checks make sense, e.g. checking for general CMS access rights.
**mysite/code/Category.php**
:::php
<?php
class Category extends DataObject {
// ...
public function canView($member = null) {
return Permission::check('CMS_ACCESS_MyAdmin', 'any', $member);
}
public function canEdit($member = null) {
return Permission::check('CMS_ACCESS_MyAdmin', 'any', $member);
}
public function canDelete($member = null) {
return Permission::check('CMS_ACCESS_MyAdmin', 'any', $member);
}
public function canCreate($member = null) {
return Permission::check('CMS_ACCESS_MyAdmin', 'any', $member);
}
## Searching Records
[api:ModelAdmin] uses the [SearchContext](../search/searchcontext) class to provide a search form, as well as get the
searched results. Every [api:DataObject] can have its own context, based on the fields which should be searchable. The
class makes a guess at how those fields should be searched, e.g. showing a checkbox for any boolean fields in your
`$db` definition.
To remove, add or modify searchable fields, define a new `[api:DataObject::$searchable_fields]` static on your model
class (see [SearchContext](../search/searchcontext) docs for details).
**mysite/code/Product.php**
:::php
<?php
class Product extends DataObject {
private static $searchable_fields = array(
'Name',
'ProductCode'
);
}
<div class="hint" markdown="1">
[SearchContext](../search/searchcontext) documentation has more information on providing the search functionality.
</div>
## Displaying Results
The results are shown in a tabular listing, powered by the [GridField](../forms/fields/gridfield), more specifically
the [api:GridFieldDataColumns] component. This component looks for a [api:DataObject::$summary_fields] static on your
model class, where you can add or remove columns. To change the title, use [api:DataObject::$field_labels].
**mysite/code/Page.php**
:::php
<?php
class Product extends DataObject {
private static $field_labels = array(
'Price' => 'Cost' // renames the column to "Cost"
);
private static $summary_fields = array(
'Name',
'Price'
);
}
The results list are retrieved from [api:SearchContext->getResults], based on the parameters passed through the search
form. If no search parameters are given, the results will show every record. Results are a [api:DataList] instance, so
can be customized by additional SQL filters, joins.
For example, we might want to exclude all products without prices in our sample `MyAdmin` implementation.
**mysite/code/MyAdmin.php**
:::php
<?php
class MyAdmin extends ModelAdmin {
public function getList() {
$list = parent::getList();
// Always limit by model class, in case you're managing multiple
if($this->modelClass == 'Product') {
$list = $list->exclude('Price', '0');
}
return $list;
}
}
You can also customize the search behavior directly on your `ModelAdmin` instance. For example, we might want to have a
checkbox which limits search results to expensive products (over $100).
**mysite/code/MyAdmin.php**
:::php
<?php
class MyAdmin extends ModelAdmin {
public function getSearchContext() {
$context = parent::getSearchContext();
if($this->modelClass == 'Product') {
$context->getFields()->push(new CheckboxField('q[ExpensiveOnly]', 'Only expensive stuff'));
}
return $context;
}
public function getList() {
$list = parent::getList();
$params = $this->request->requestVar('q'); // use this to access search parameters
if($this->modelClass == 'Product' && isset($params['ExpensiveOnly']) && $params['ExpensiveOnly']) {
$list = $list->exclude('Price:LessThan', '100');
}
return $list;
}
}
To alter how the results are displayed (via `[api:GridField]`), you can also overload the `getEditForm()` method. For
example, to add a new component.
**mysite/code/MyAdmin.php**
:::php
<?php
class MyAdmin extends ModelAdmin {
private static $managed_models = array(
'Product',
'Category'
);
// ...
public function getEditForm($id = null, $fields = null) {
$form = parent::getEditForm($id, $fields);
// $gridFieldName is generated from the ModelClass, eg if the Class 'Product'
// is managed by this ModelAdmin, the GridField for it will also be named 'Product'
$gridFieldName = $this->sanitiseClassName($this->modelClass);
$gridField = $form->Fields()->fieldByName($gridFieldName);
// modify the list view.
$gridField->getConfig()->addComponent(new GridFieldFilterHeader());
return $form;
}
}
The above example will add the component to all `GridField`s (of all managed models). Alternatively we can also add it
to only one specific `GridField`:
**mysite/code/MyAdmin.php**
:::php
<?php
class MyAdmin extends ModelAdmin {
private static $managed_models = array(
'Product',
'Category'
);
public function getEditForm($id = null, $fields = null) {
$form = parent::getEditForm($id, $fields);
$gridFieldName = 'Product';
$gridField = $form->Fields()->fieldByName($gridFieldName);
if ($gridField) {
$gridField->getConfig()->addComponent(new GridFieldFilterHeader());
}
return $form;
}
}
## Data Import
The `ModelAdmin` class provides import of CSV files through the [api:CsvBulkLoader] API. which has support for column
mapping, updating existing records, and identifying relationships - so its a powerful tool to get your data into a
SilverStripe database.
By default, each model management interface allows uploading a CSV file with all columns auto detected. To override
with a more specific importer implementation, use the [api:ModelAdmin::$model_importers] static.
## Data Export
Export is available as a CSV format through a button at the end of a results list. You can also export search results.
This is handled through the [api:GridFieldExportButton] component.
To customize the exported columns, create a new method called `getExportFields` in your `ModelAdmin`:
:::php
<?php
class MyAdmin extends ModelAdmin {
// ...
public function getExportFields() {
return array(
'Name' => 'Name',
'ProductCode' => 'Product Code',
'Category.Title' => 'Category'
);
}
}
## Related Documentation
* [GridField](../forms/fields/gridfield)
* [Permissions](../security/permissions)
* [SeachContext](../search/seachcontext)
## API Documentation
* [api:ModelAdmin]
* [api:LeftAndMain]
* [api:GridField]
* [api:DataList]
* [api:CsvBulkLoader]

View File

@ -1,6 +1,6 @@
# CMS layout
title: Admin Layout
## Overview
# CMS layout
The CMS markup is structured into "panels", which are the base units containing interface components (or other panels),
as declared by the class `cms-panel`. Panels can be made collapsible, and get the ability to be resized and aligned with
@ -8,7 +8,7 @@ a layout manager, in our case [jLayout](http://www.bramstein.com/projects/jlayou
declarations (mostly dimensions and positioning) via JavaScript.
We've established a convention for a `redraw` method on each panel and UI component that need to update their content as
a result of changes to their position, size or visibility. This method would usually be invoked by the parent container.
a result of changes to their position, size or visibility. This method would usually be invoked by the parent container.
The layout manager does not dynamically track changes to panel sizes - we have to trigger laying out manually each time
we need an update to happen (for example from `window::onresize` event, or panel toggling). It then cascades through the
@ -21,7 +21,7 @@ The easiest way to update the layout of the CMS is to call `redraw` on the top-l
This causes the framework to:
* reset the _threeColumnCompressor_ algorithm with the current layout options (that can be set via
* reset the _threeColumnCompressor_ algorithm with the current layout options (that can be set via
`updateLayoutOptions`)
* trigger `layout` which cascades into all children resizing and positioning subordinate elements (this is internal
to the layout manager)
@ -39,7 +39,7 @@ example, the tab panels have be applied in the CMS form before the form itself i
avoid incorrect dimensions.
</div>
![Layout variations](_images/cms-architecture.png)
![Layout variations](/_images/cms-architecture.png)
## Layout API
@ -56,9 +56,9 @@ Layout manager will automatically apply algorithms to the children of `.cms-cont
`data-layout-type` attribute. Let's take the content toolbar as an example of a second-level layout application:
:::html
<div class="cms-content-tools west cms-panel cms-panel-layout"
data-expandOnClick="true"
data-layout-type="border"
<div class="cms-content-tools west cms-panel cms-panel-layout"
data-expandOnClick="true"
data-layout-type="border"
id="cms-content-tools-CMSMain">
<%-- content utilising border's north, south, east, west and center classes --%>
</div>
@ -67,15 +67,15 @@ For detailed discussion on available algorithms refer to
[jLayout algorithms](https://github.com/bramstein/jlayout#layout-algorithms).
Our [Howto: Extend the CMS Interface](../howto/extend-cms-interface) has a practical example on how to add a bottom
panel to the CMS UI.
panel to the CMS UI.
### Methods
The following methods are available as an interface to underlying _threeColumnCompressor_ algorithm on the
The following methods are available as an interface to underlying _threeColumnCompressor_ algorithm on the
`.cms-container` entwine:
* **getLayoutOptions**: get currently used _threeColumnCompressor_ options.
* **updateLayoutOptions**: change specified options and trigger the laying out:
* **updateLayoutOptions**: change specified options and trigger the laying out:
`$('.cms-container').updateLayoutOptions({mode: 'split'});`
* **splitViewMode**: enable side by side editing.
* **contentViewMode**: only menu and content areas are shown.
@ -91,7 +91,7 @@ You might have noticed that the top-level `.cms-container` has the `data-layout-
_threeColumnCompressor_ algorithm for the layout of the menu, content and preview columns of the CMS. The annotated code
for this algorithm can be found in `LeftAndMain.Layout.js`.
Since the layout-type for the element is set to `custom` and will be ignored by the layout manager, we apply the
Since the layout-type for the element is set to `custom` and will be ignored by the layout manager, we apply the
_threeColumnCompressor_ explicitly `LeftAndMain::redraw`. This way we also get a chance to provide options expected
by the algorithm that are initially taken from the `LeftAndMain::LayoutOptions` entwine variable.

View File

@ -2,41 +2,41 @@
## Overview
With the addition of side-by-side editing, the preview has the ability to appear
within the CMS window when editing content in the _Pages_ section of the CMS.
The site is rendered into an iframe. It will update itself whenever the content
is saved, and relevant pages will be loaded for editing when the user navigates
With the addition of side-by-side editing, the preview has the ability to appear
within the CMS window when editing content in the _Pages_ section of the CMS.
The site is rendered into an iframe. It will update itself whenever the content
is saved, and relevant pages will be loaded for editing when the user navigates
around in the preview.
The root element for preview is `.cms-preview` which maintains the internal
states necessary for rendering within the entwine properties. It provides
function calls for transitioning between these states and has the ability to
The root element for preview is `.cms-preview` which maintains the internal
states necessary for rendering within the entwine properties. It provides
function calls for transitioning between these states and has the ability to
update the appearance of the option selectors.
In terms of backend support, it relies on `SilverStripeNavigator` to be rendered
into the `.cms-edit-form`. _LeftAndMain_ will automatically take care of
In terms of backend support, it relies on `SilverStripeNavigator` to be rendered
into the `.cms-edit-form`. _LeftAndMain_ will automatically take care of
generating it as long as the `*_SilverStripeNavigator` template is found -
first segment has to match current _LeftAndMain_-derived class (e.g.
first segment has to match current _LeftAndMain_-derived class (e.g.
`LeftAndMain_SilverStripeNavigator`).
We use `ss.preview` entwine namespace for all preview-related entwines.
<div class="notice" markdown='1'>
Caveat: `SilverStripeNavigator` and `CMSPreviewable` interface currently only
support SiteTree objects that are _Versioned_. They are not general enough for
using on any other DataObject. That pretty much limits the extendability of the
Caveat: `SilverStripeNavigator` and `CMSPreviewable` interface currently only
support SiteTree objects that are _Versioned_. They are not general enough for
using on any other DataObject. That pretty much limits the extendability of the
feature.
</div>
## Configuration and Defaults
Like most of the CMS, the preview UI is powered by
[jQuery entwine](https://github.com/hafriedlander/jquery.entwine). This means
Like most of the CMS, the preview UI is powered by
[jQuery entwine](https://github.com/hafriedlander/jquery.entwine). This means
its defaults are configured through JavaScript, by setting entwine properties.
In order to achieve this, create a new file `mysite/javascript/MyLeftAndMain.Preview.js`.
In the following example we configure three aspects:
* Set the default mode from "split view" to a full "edit view"
* Make a wider mobile preview
* Increase minimum space required by preview before auto-hiding
@ -83,32 +83,32 @@ To understand how layouts are handled in the CMS UI, have a look at the
## Enabling preview
The frontend decides on the preview being enabled or disabled based on the
presence of the `.cms-previewable` class. If this class is not found the preview
The frontend decides on the preview being enabled or disabled based on the
presence of the `.cms-previewable` class. If this class is not found the preview
will remain hidden, and the layout will stay in the _content_ mode.
If the class is found, frontend looks for the `SilverStripeNavigator` structure
and moves it to the `.cms-preview-control` panel at the bottom of the preview.
If the class is found, frontend looks for the `SilverStripeNavigator` structure
and moves it to the `.cms-preview-control` panel at the bottom of the preview.
This structure supplies preview options such as state selector.
If the navigator is not found, the preview appears in the GUI, but is shown as
If the navigator is not found, the preview appears in the GUI, but is shown as
"blocked" - i.e. displaying the "preview unavailable" overlay.
The preview can be affected by calling `enablePreview` and `disablePreview`. You
can check if the preview is active by inspecting the `IsPreviewEnabled` entwine
The preview can be affected by calling `enablePreview` and `disablePreview`. You
can check if the preview is active by inspecting the `IsPreviewEnabled` entwine
property.
## Preview states
States are the site stages: _live_, _stage_ etc. Preview states are picked up
States are the site stages: _live_, _stage_ etc. Preview states are picked up
from the `SilverStripeNavigator`. You can invoke the state change by calling:
```js
$('.cms-preview').entwine('.ss.preview').changeState('StageLink');
```
Note the state names come from `SilverStripeNavigatorItems` class names - thus
the _Link_ in their names. This call will also redraw the state selector to fit
Note the state names come from `SilverStripeNavigatorItems` class names - thus
the _Link_ in their names. This call will also redraw the state selector to fit
with the internal state. See `AllowedStates` in `.cms-preview` entwine for the
list of supported states.
@ -120,8 +120,8 @@ You can get the current state by calling:
## Preview sizes
This selector defines how the preview iframe is rendered, and try to emulate
different device sizes. The options are hardcoded. The option names map directly
This selector defines how the preview iframe is rendered, and try to emulate
different device sizes. The options are hardcoded. The option names map directly
to CSS classes applied to the `.cms-preview` and are as follows:
* _auto_: responsive layout
@ -129,8 +129,8 @@ to CSS classes applied to the `.cms-preview` and are as follows:
* _tablet_
* _mobile_
You can switch between different types of display sizes programmatically, which
has the benefit of redrawing the related selector and maintaining a consistent
You can switch between different types of display sizes programmatically, which
has the benefit of redrawing the related selector and maintaining a consistent
internal state:
```js
@ -145,15 +145,15 @@ You can find out current size by calling:
## Preview modes
Preview modes map to the modes supported by the _threeColumnCompressor_ layout
algorithm, see [layout reference](../reference/layout) for more details. You
can change modes by calling:
Preview modes map to the modes supported by the _threeColumnCompressor_ layout
algorithm, see [layout reference](../reference/layout) for more details. You
can change modes by calling:
```js
$('.cms-preview').entwine('.ss.preview').changeMode('preview');
```
Currently active mode is stored on the `.cms-container` along with related
Currently active mode is stored on the `.cms-container` along with related
internal states of the layout. You can reach it by calling:
```js
@ -161,10 +161,10 @@ internal states of the layout. You can reach it by calling:
```
<div class="notice" markdown='1'>
Caveat: the `.preview-mode-selector` appears twice, once in the preview and
second time in the CMS actions area as `#preview-mode-dropdown-in-cms`. This is
Caveat: the `.preview-mode-selector` appears twice, once in the preview and
second time in the CMS actions area as `#preview-mode-dropdown-in-cms`. This is
done because the user should still have access to the mode selector even if
preview is not visible. Currently CMS Actions are a separate area to the preview
preview is not visible. Currently CMS Actions are a separate area to the preview
option selectors, even if they try to appear as one horizontal bar.
</div>

View File

@ -2,7 +2,7 @@
## Introduction
A lot can be achieved in SilverStripe by adding properties and form fields
A lot can be achieved in SilverStripe by adding properties and form fields
to your own page types (via `[api:SiteTree->getCMSFields()]`), as well as creating
your own data management interfaces through `[api:ModelAdmin]`. But sometimes
you'll want to go deeper and tailor the underlying interface to your needs as well.
@ -22,7 +22,7 @@ While SilverStripe is intended to work with JavaScript only,
we're following the principles of "[Progressive Enhancement](http://en.wikipedia.org/wiki/Progressive_enhancement)"
where feasible, relying on a comparatively light layer of JavaScript to enhance
forms and markup generated on the server. This allows seamless customization of
aspects like form fields. We're explaining this philosophy in more detail
aspects like form fields. We're explaining this philosophy in more detail
on our [blog](http://www.silverstripe.org/the-3-0-ui-a-better-framework-for-your-ideas/)).
All CSS in the CMS UI is written in the [SCSS language extensions](http://sass-lang.com/)
@ -30,7 +30,7 @@ and the [Compass framework](http://compass-style.org/), which helps
us maintain expressive and concise style declarations. The files are located in `framework/admin/scss`
(and if you have the `cms` module installed, in `cms/scss`), and are compiled to a `css` folder on the
same directory path. Changes to the SCSS files can be automatically converted by installing
the ["compass" module](https://github.com/silverstripe-labs/silverstripe-compass) for SilverStripe,
the ["compass" module](https://github.com/silverstripe-labs/silverstripe-compass) for SilverStripe,
although [installing the compass framework](http://compass-style.org/install/) directly works as well.
Each file describes its purpose at the top of the declarations. Note that you can write
plain CSS without SCSS for your custom CMS interfaces as well, we just mandate SCSS for core usage.
@ -44,7 +44,7 @@ As there's a whole lot of CSS driving the CMS, we have certain best practives ar
(which might change later on). A more structural name could be `cms-menu` (or `cms-tools-menu` for a more specific version)
* Class naming: Use the `cms-` class prefix for major components in the cms interface,
and the `ss-ui-` prefix for extensions to jQuery UI. Don't use the `ui-` class prefix, its reserved for jQuery UI built-in styles.
* Use jQuery UI's built-in styles where possible, e.g. `ui-widget` for a generic container, or `ui-state-highlight`
* Use jQuery UI's built-in styles where possible, e.g. `ui-widget` for a generic container, or `ui-state-highlight`
to highlight a specific component. See the [jQuery UI Theming API](http://jqueryui.com/docs/Theming/API) for a full list.
See our [system requirements](../installation/server-requirements) for a list of supported browsers.
@ -67,7 +67,7 @@ We can use this to create a different base template with `LeftAndMain.ss`
(which corresponds to the `LeftAndMain` PHP controller class).
In case you want to retain the main CMS structure (which is recommended),
just create your own "Content" template (e.g. `MyCMSController_Content.ss`),
which is in charge of rendering the main content area apart from the CMS menu.
which is in charge of rendering the main content area apart from the CMS menu.
Depending on the complexity of your layout, you'll also need to overload the
"EditForm" template (e.g. `MyCMSController_EditForm.ss`), e.g. to implement
@ -77,9 +77,9 @@ This requires manual assignment of the template to your form instance, see `[api
Often its useful to have a "tools" panel in between the menu and your content,
usually occupied by a search form or navigational helper.
In this case, you can either overload the full base template as described above.
To avoid duplicating all this template code, you can also use the special `[api:LeftAndMain->Tools()]` and
To avoid duplicating all this template code, you can also use the special `[api:LeftAndMain->Tools()]` and
`[api:LeftAndMain->EditFormTools()]` methods available in `LeftAndMain`.
These placeholders are populated by auto-detected templates,
These placeholders are populated by auto-detected templates,
with the naming convention of "<controller classname>_Tools.ss" and "<controller classname>_EditFormTools.ss".
So to add or "subclass" a tools panel, simply create this file and it's automatically picked up.
@ -101,7 +101,7 @@ e.g. after saving a record (which requires a form refresh), or switching the sec
Depending on where in the DOM hierarchy you want to use a form,
custom templates and additional CSS classes might be required for correct operation.
For example, the "EditForm" has specific view and logic JavaScript behaviour
which can be enabled via adding the "cms-edit-form" class.
which can be enabled via adding the "cms-edit-form" class.
In order to set the correct layout classes, we also need a custom template.
To obey the inheritance chain, we use `$this->getTemplatesWithSuffix('_EditForm')` for
selecting the most specific template (so `MyAdmin_EditForm.ss`, if it exists).
@ -115,7 +115,7 @@ Basic example form in a CMS controller subclass:
class MyAdmin extends LeftAndMain {
function getEditForm() {
return CMSForm::create(
$this,
$this,
'EditForm',
new FieldSet(
TabSet::create(
@ -139,14 +139,14 @@ Basic example form in a CMS controller subclass:
}
}
Note: Usually you don't need to worry about these settings,
Note: Usually you don't need to worry about these settings,
and will simply call `parent::getEditForm()` to modify an existing,
correctly configured form.
## JavaScript through jQuery.entwine
[jQuery.entwine](https://github.com/hafriedlander/jquery.entwine) is a thirdparty library
which allows us to attach behaviour to DOM elements in a flexible and structured mannger.
which allows us to attach behaviour to DOM elements in a flexible and structured mannger.
It replaces the `behaviour.js` library used in previous versions of the CMS interface.
See [Topics: JavaScript](../topics/javascript) for more information on how to use it.
In the CMS interface, all entwine rules should be placed in the "ss" entwine namespace.
@ -154,13 +154,13 @@ If you want to call methods defined within these rules outside of entwine logic,
you have to use this namespace, e.g. `$('.cms-menu').entwine('ss').collapse()`.
Note that only functionality that is custom to the CMS application needs to be built
in jQuery.entwine, we're trying to reuse library code wherever possible.
in jQuery.entwine, we're trying to reuse library code wherever possible.
The most prominent example of this is the usage of [jQuery UI](http://jqueryui.com) for
dialogs and buttons.
The CMS includes the jQuery.entwine inspector. Press Ctrl+` ("backtick") to bring down the inspector.
You can then click on any element in the CMS to see which entwine methods are bound to
any particular element.
any particular element.
## JavaScript and CSS dependencies via Requirements and Ajax
@ -181,23 +181,23 @@ so don't place a rule applying to all form buttons inside `ModelAdmin.js`.
The CMS relies heavily on Ajax-loading of interfaces, so each interface and the JavaScript
driving it have to assume its underlying DOM structure is appended via an Ajax callback
rather than being available when the browser window first loads.
rather than being available when the browser window first loads.
jQuery.entwine is effectively an advanced version of [jQuery.live](http://api.jquery.com/live/)
and [jQuery.delegate](http://api.jquery.com/delegate/), so takes care of dynamic event binding.
Most interfaces will require their own JavaScript and CSS files, so the Ajax loading has
to ensure they're loaded unless already present. A custom-built library called
to ensure they're loaded unless already present. A custom-built library called
`jQuery.ondemand` (located in `framework/thirdparty`) takes care of this transparently -
so as a developer just declare your dependencies through the `[api:Requirements]` API.
## Ajax Loading and Browser History
## Ajax Loading and Browser History
SilverStripe uses the HTML5 browser history to modify the URL without a complete window refresh,
and load its UI via Ajax by hooking into browser navigation events (through the
and load its UI via Ajax by hooking into browser navigation events (through the
[history.js](https://github.com/balupton/History.js/) wrapper library).
This technique has an impact on how any Ajax load needs to happen:
In order to support browser history (and change the URL state),
a CMS developer needs to fire a navigation event rather than invoking the Ajax call directly.
In order to support browser history (and change the URL state),
a CMS developer needs to fire a navigation event rather than invoking the Ajax call directly.
The main point of contact here is `$('.cms-container').loadPanel(<url>, <title>, <data>)`
in `LeftAndMain.js`. The `data` object can contain additional state which is required
@ -352,7 +352,7 @@ Note: To avoid double processing, the first response body is usually empty.
By loading mostly HTML responses, we don't have an easy way to communicate
information which can't be directly contained in the produced HTML.
For example, the currently used controller class might've changed due to a "redirect",
For example, the currently used controller class might've changed due to a "redirect",
which affects the currently active menu entry. We're using HTTP response headers to contain this data
without affecting the response body.
@ -388,7 +388,7 @@ SilverStripe automatically applies a [jQuery UI button style](http://jqueryui.co
to all elements with the class `.ss-ui-button`. We've extended the jQuery UI widget a bit
to support defining icons via HTML5 data attributes (see `ssui.core.js`).
These icon identifiers relate to icon files in `framework/admin/images/btn-icons`,
and are sprited into a single file through SCSS and the Compass framework
and are sprited into a single file through SCSS and the Compass framework
(see [tutorial](http://compass-style.org/help/tutorials/spriting/)).
Compass also creates the correct CSS classes to show those sprites via background images
(see `framework/admin/scss/_sprites.scss`).
@ -435,7 +435,7 @@ by the [jstree](http://jstree.com) library. It is configured through
HTML5 metadata generated on its container (see the `data-hints` attribute).
For more information, see the [Howto: Customize the CMS tree](../howto/customize-cms-tree).
Note that a similar tree logic is also used for the
Note that a similar tree logic is also used for the
form fields to select one or more entries from those hierarchies
(`[api:TreeDropdownField]` and `[api:TreeMultiselectField]`).
@ -487,7 +487,7 @@ Form template with custom tab navigation (trimmed down):
<% loop Fields %>$FieldHolder<% end_loop %>
</fieldset>
</div>
</form>
Tabset template without tab navigation (e.g. `CMSTabset.ss`)

View File

@ -0,0 +1,28 @@
title: WYSIWYG Styles
summary: Add custom CSS properties to the rich-text editor.
# WYSIWYG Styles
SilverStripe lets you customize the style of content in the CMS. This is done by setting up a CSS file called
`editor.css` in either your theme or in your `mysite` folder. This is set through
:::php
HtmlEditorConfig::get('cms')->setOption('ContentCSS', project() . '/css/editor.css');
Will load the `mysite/css/editor.css` file.
Any CSS classes within this file will be automatically added to the `WYSIWYG` editors 'style' dropdown. For instance, to
add the color 'red' as an option within the `WYSIWYG` add the following to the `editor.css`
:::css
.red {
color: red;
}
<div class="notice" markdown="1">
After you have defined the `editor.css` make sure you clear your SilverStripe cache for it to take effect.
</div>
## API Documentation
* [api:HtmlEditorConfig]

View File

@ -1,25 +1,15 @@
# JavaScript
title: Javascript Development
summary: Advanced documentation about writing and customizing javascript within SilverStripe.
**Important: Parts of this guide apply to the SilverStripe 2.4 release, particularly around the jQuery.entwine
library.**
# Javascript Development
This page describes best practices for developing with JavaScript in SilverStripe. This includes work in the CMS
interface, form widgets and custom project code. It is geared towards our "library of choice", jQuery, but most
practices can be applied to other libraries as well.
## File Inclusion
SilverStripe-driven code should use the `[api:Requirements]` class to manage clientside dependencies like CSS and JavaScript
files, rather than including `<script>` and `<link>` tags in your templates. This has the advantage that a registry
of requirements can be built up from different places outside of the main controller, for example included `[api:FormField]`
instances.
See [requirements](/reference/requirements) documentation.
The following document is an advanced guide on building rich javascript interactions within the SilverStripe CMS and
a list of our best practices for contributing and modifying the core javascript framework.
## jQuery, jQuery UI and jQuery.entwine: Our libraries of choice
We predominantly use [jQuery](http://jquery.com) as our abstraction library for DOM related programming, within the
SilverStripe CMS and certain framework aspects.
SilverStripe CMS and certain framework aspects.
For richer interactions such as drag'n'drop, and more complicated interface elements like tabs or accordions,
SilverStripe CMS uses [jQuery UI](http://ui.jquery.com) on top of jQuery.
@ -27,21 +17,13 @@ SilverStripe CMS uses [jQuery UI](http://ui.jquery.com) on top of jQuery.
For any custom code developed with jQuery, you have four choices to structure it: Custom jQuery Code, a jQuery Plugin, a
jQuery UI Widget, or a `jQuery.entwine` behaviour. We'll detail below where each solution is appropriate.
<div class="hint" markdown='1'>
**Important**: Historically we have been using [PrototypeJS](http://prototypejs.com), which is now discouraged. SilverStripe as a framework doesn't impose a choice of library. It
tries to generate meaningful markup which you can alter with other JavaScript libraries as well. Only the CMS itself and
certain form widgets require jQuery to function correctly. You can also use jQuery in parallel with other libraries, see
[here](http://docs.jquery.com/Using_jQuery_with_Other_Libraries).
</div>
## Custom jQuery Code
### Custom jQuery Code
jQuery allows you to write complex behaviour in a couple of lines of JavaScript. Smaller features which aren't likely to
jQuery allows you to write complex behavior in a couple of lines of JavaScript. Smaller features which aren't likely to
be reused can be custom code without further encapsulation. For example, a button rollover effect doesn't require a full
plugin. See "[How jQuery Works](http://docs.jquery.com/How_jQuery_Works)" for a good introduction.
You should write all your custom jQuery code in a closure. This will prevent jQuery from conflicting from any prototype
code or any other framework code.
You should write all your custom jQuery code in a closure.
:::javascript
(function($) {
@ -50,18 +32,10 @@ code or any other framework code.
})
})(jQuery);
### Custom jQuery/JavaScript in the CMS
## jQuery Plugins
To call additional Javascript or jQuery files in to the CMS, edit your mysite/config/config.yml file as follows:
:::javascript
LeftAndMain:
extra_requirements_javascript:
- '/path/to/file.js'
### jQuery Plugins
A jQuery Plugin is essentially a method call which can act on a collection of DOM elements. It is contained within the `jQuery.fn` namespace, and attaches itself automatically to all jQuery collections. The basics for are outlined in the
A jQuery Plugin is essentially a method call which can act on a collection of DOM elements. It is contained within the
`jQuery.fn` namespace, and attaches itself automatically to all jQuery collections. The basics for are outlined in the
official [jQuery Plugin Authoring](http://docs.jquery.com/Plugins/Authoring) documentation.
There a certain [documented patterns](http://www.learningjquery.com/2007/10/a-plugin-development-pattern) for plugin
@ -105,22 +79,22 @@ Example: A plugin to highlight a collection of elements with a configurable fore
})(jQuery);
Usage:
Usage:
:::js
(function($) {
// Highlight all buttons with default colours
jQuery(':button').highlight();
// Highlight all buttons with green background
jQuery(':button').highlight({background: "green"});
// Set all further highlight() calls to have a green background
$.fn.hilight.defaults.background = "green";
})(jQuery);
### jQuery UI Widgets
## jQuery UI Widgets
UI Widgets are jQuery Plugins with a bit more structure, targeted towards interactive elements. They require jQuery and
the core libraries in jQuery UI, so are generally more heavyweight if jQuery UI isn't already used elsewhere.
@ -140,20 +114,20 @@ Example: Highlighter
:::js
(function($) {
$.widget("ui.myHighlight", {
getBlink: function () {
return this._getData('blink');
getBlink: function () {
return this._getData('blink');
},
setBlink: function (blink) {
this._setData('blink', blink);
if(blink) this.element.wrapInner('<blink></blink>');
else this.element.html(this.element.children().html());
},
_init: function() {
_init: function() {
// grab the default value and use it
this.element.css('background',this.options.background);
this.element.css('color',this.options.foreground);
this.element.css('background',this.options.background);
this.element.css('color',this.options.foreground);
this.setBlink(this.options.blink);
}
}
});
// For demonstration purposes, this is also possible with jQuery.css()
$.ui.myHighlight.getter = "getBlink";
@ -171,22 +145,22 @@ Usage:
(function($) {
// call with default options
$(':button').myHighlight();
// call with custom options
$(':button').myHighlight({background: "green"});
// set defaults for all future instances
$.ui.myHighlight.defaults.background = "green";
// Adjust property after initialization
$(':button').myHighlight('setBlink', true);
// Get property
$(':button').myHighlight('getBlink');
})(jQuery);
### entwine: Defining Behaviour and Public APIs
### jQuery.Entwine
jQuery.entwine is a third-party plugin, from its documentation:
"A basic desire for jQuery programming is some sort of OO or other organisational method for code. For your
@ -218,10 +192,10 @@ Usage:
(function($) {
// call with default options
$(':button').entwine().highlight();
// set options for existing and new instances
$(':button').entwine().setBackground('green');
// get property
$(':button').entwine().getBackground();
})(jQuery);
@ -243,7 +217,7 @@ jQuery with a few lines of code. Your jQuery code will normally end up as a ser
### Don't claim global properties
Global properties are evil. They are accessible by other scripts, might be overwritten or misused. A popular case is the `$` shortcut in different libraries: in PrototypeJS it stands for `document.getElementByID()`, in jQuery for `jQuery()`.
Global properties are evil. They are accessible by other scripts, might be overwritten or misused. A popular case is the `$` shortcut in different libraries: in PrototypeJS it stands for `document.getElementByID()`, in jQuery for `jQuery()`.
:::js
// you can't rely on '$' being defined outside of the closure
@ -285,7 +259,7 @@ Example: Add a 'loading' classname to all pressed buttons
$('input[[type=submit]]').on('click', function() {
$(this).addClass('loading');
});
// binding, applies to any inserted elements as well
$('input[[type=submit]]').on(function() {
$(this).addClass('loading');
@ -300,7 +274,7 @@ request](http://docs.jquery.com/Frequently_Asked_Questions#Why_do_my_events_stop
jQuery is based around collections of DOM elements, the library functions typically handle multiple elements (where it
makes sense). Encapsulate your code by nesting your jQuery commands inside a `jQuery().each()` call.
Example: ComplexTableField implements a paginated table with a pop-up for displaying
Example: ComplexTableField implements a paginated table with a pop-up for displaying
:::js
$('div.ComplexTableField').each(function() {
@ -371,7 +345,7 @@ See [interactive example on jsbin.com](http://jsbin.com/axafa)
Ajax responses will sometimes need to update existing DOM elements, for example refresh a set of search results.
Returning plain HTML is generally a good default behaviour, as it allows you to keep template rendering in one place (in
SilverStripe PHP code), and is easy to deal with in JavaScript.
SilverStripe PHP code), and is easy to deal with in JavaScript.
If you need to process or inspect returned data, consider extracting it from the loaded HTML instead (through id/class
attributes, or the jQuery.metadata plugin). For returning status messages, please use the HTTP status-codes.
@ -400,10 +374,10 @@ PHP:
public function autocomplete($request) {
$results = Page::get()->filter("Title", $request->getVar('title'));
if(!$results) return new HTTPResponse("Not found", 404);
// Use HTTPResponse to pass custom status messages
$this->response->setStatusCode(200, "Found " . $results->Count() . " elements");
// render all results with a custom template
$vd = new ViewableData();
return $vd->customise(array(
@ -439,8 +413,8 @@ JavaScript:
function(data, status) {
resultsEl.show();
// get all record IDs from the new HTML
var ids = jQuery('.results').find('li').map(function() {
return $(this).attr('id').replace(/Record\-/,'');
var ids = jQuery('.results').find('li').map(function() {
return $(this).attr('id').replace(/Record\-/,'');
});
}
);
@ -475,7 +449,7 @@ Example: Trigger custom 'validationfailed' event on form submission for each emp
});
return false;
});
// listen to custom event on each <input> field
$('form :input').bind('validationfailed',function(e) {
// $(this) refers to input field
@ -525,30 +499,30 @@ Example: jQuery.entwine
:::js
/**
* Available Custom Events:
* <ul>
* <li>ajaxsubmit</li>
* <li>validate</li>
* <li>reloadeditform</li>
* </ul>
*
*
* @class Main LeftAndMain interface with some control panel and an edit form.
* @name ss.LeftAndMain
*/
$('.LeftAndMain').entwine('ss', function($){
return/** @lends ss.LeftAndMain */ {
/**
* Reference to some property
* @type Number
*/
MyProperty: 123,
/**
* Renders the provided data into an unordered list.
*
*
* @param {Object} data
* @param {String} status
* @return {String} HTML unordered list
@ -558,11 +532,11 @@ Example: jQuery.entwine
+ /...
+ '</ul>';
},
/**
* Won't show in documentation, but still worth documenting.
*
*
* @return {String} Something else.
*/
_privateMethod: function() {
@ -593,67 +567,19 @@ Example: QUnit test (from [jquery.com](http://docs.jquery.com/QUnit#Using_QUnit)
Example: JSpec Shopping cart test (from [visionmedia.github.com](http://visionmedia.github.com/jspec/))
describe 'ShoppingCart'
before_each
before_each
cart = new ShoppingCart
end
describe 'addProduct'
it 'should add a product'
cart.addProduct('cookie')
cart.addProduct('icecream')
cart.should.have 2, 'products'
cart.addProduct('cookie')
cart.addProduct('icecream')
cart.should.have 2, 'products'
end
end
end
### Javascript in the CMS {#javascript-cms}
The CMS has a number of Observer-pattern hooks you can access: (The elements which are notified are listed in brackets.)
* Close -- when 'folder' in SiteTree is closed. (form?)
* BeforeSave -- after user clicks 'Save', before AJAX save-request (#Form_EditForm)
* PageLoaded -- after new SiteTree page is loaded. (#Form_EditForm)
* PageSaved -- after AJAX save-request is successful (#Form_EditForm)
* SelectionChanged -- when new item is chosen from SiteTree (.cms-tree)
Here's an example of hooking the 'PageLoaded' and 'BeforeSave' methods:
:::javascript
/*
* Observe the SiteTree 'PageLoaded' event, called whenever a SiteTree page is
* opened or reloaded in the CMS.
*
* Also observe 'BeforeSave' which is called when the Save button is pressed,
* before the AJAX call to save the page is sent.
*/
Behaviour.register({
'#Form_EditForm' : {
initialize : function() {
this.observeMethod('PageLoaded', this.pageLoaded);
this.observeMethod('BeforeSave', this.beforeSave);
this.pageLoaded(); // call pageload initially too.
},
pageLoaded : function() {
alert("You loaded a page");
},
beforeSave: function() {
alert("You clicked save");
}
} // #Form_EditForm
});
See ['onload' javascript in the CMS](/reference/leftandmain#onload-javascript)
### Break the rules!
The guidelines are not intended to be hard and fast rules; they cover the most common cases but not everything. Don't be
afraid to experiment with using other approaches.
## Related
* [css](css)
* [Unobtrusive Javascript](http://www.onlinetools.org/articles/unobtrusivejavascript/chapter1.html)
* [Quirksmode: In-depth Javascript Resources](http://www.quirksmode.org/resources.html)
* [Quirksmode: In-depth Javascript Resources](http://www.quirksmode.org/resources.html)

View File

@ -59,7 +59,7 @@ Here we initialise the button based on the backend check, and assume that the bu
}
// ...
}
## Frontend support ##
As with the *Save* and *Save & publish* buttons, you might want to add some scripted reactions to user actions on the
@ -116,7 +116,7 @@ extras.
Continuing our example let's add a "constructive" style to our *Clean-up* button. First you need to be able to add
custom JS code into the CMS. You can do this by adding a new source file, here
`mysite/javascript/CMSMain.CustomActionsExtension.js`, and requiring it
`mysite/javascript/CMSMain.CustomActionsExtension.js`, and requiring it
through a YAML configuration value: `LeftAndMain.extra_requirements_javascript`.
Set it to 'mysite/javascript/CMSMain.CustomActionsExtension.js'.

View File

@ -14,7 +14,7 @@ at the last position within the field, and expects unescaped HTML content.
->setDescription('More <strong>detailed</strong> help');
To show the help text as a tooltip instead of inline,
add a `.cms-description-tooltip` class.
add a `.cms-description-tooltip` class.
:::php
TextField::create('MyText', 'My Text Label')

View File

@ -2,53 +2,53 @@
## Adding a administration panel
Every time you add a new extension of the `[api:LeftAndMain]` class to the CMS,
Every time you add a new extension of the `[api:LeftAndMain]` class to the CMS,
SilverStripe will automatically create a new `[api:CMSMenuItem]` for it
The most popular extension of LeftAndMain is a `[api:ModelAdmin]` class, so
The most popular extension of LeftAndMain is a `[api:ModelAdmin]` class, so
for a more detailed introduction to creating new `ModelAdmin` interfaces, read
the [ModelAdmin reference](../reference/modeladmin).
In this document we'll take the `ProductAdmin` class used in the
[ModelAdmin reference](../reference/modeladmin#setup) and so how we can change
the menu behaviour by using the `$menu_title` and `$menu_icon` statics to
In this document we'll take the `ProductAdmin` class used in the
[ModelAdmin reference](../reference/modeladmin#setup) and so how we can change
the menu behaviour by using the `$menu_title` and `$menu_icon` statics to
provide a custom title and icon.
### Defining a Custom Icon
First we'll need a custom icon. For this purpose SilverStripe uses 16x16
black-and-transparent PNG graphics. In this case we'll place the icon in
First we'll need a custom icon. For this purpose SilverStripe uses 16x16
black-and-transparent PNG graphics. In this case we'll place the icon in
`mysite/images`, but you are free to use any location.
:::php
class ProductAdmin extends ModelAdmin {
// ...
private static $menu_icon = 'mysite/images/product-icon.png';
private static $menu_icon = 'mysite/images/product-icon.png';
}
### Defining a Custom Title
The title of menu entries is configured through the `$menu_title` static.
If its not defined, the CMS falls back to using the class name of the
If its not defined, the CMS falls back to using the class name of the
controller, removing the "Admin" bit at the end.
:::php
class ProductAdmin extends ModelAdmin {
// ...
private static $menu_title = 'My Custom Admin';
private static $menu_title = 'My Custom Admin';
}
In order to localize the menu title in different languages, use the
`<classname>.MENUTITLE` entity name, which is automatically created when running
In order to localize the menu title in different languages, use the
`<classname>.MENUTITLE` entity name, which is automatically created when running
the i18n text collection.
For more information on language and translations, please refer to the
For more information on language and translations, please refer to the
[i18n](../reference/ii8n) docs.
## Adding an external link to the menu
On top of your administration windows, the menu can also have external links
(e.g. to external reference). In this example, we're going to add a link to
On top of your administration windows, the menu can also have external links
(e.g. to external reference). In this example, we're going to add a link to
Google to the menu.
First, we need to define a `[api:LeftAndMainExtension]` which will contain our
@ -69,7 +69,7 @@ button configuration.
// the link you want to item to go to
$link = 'http://google.com';
// priority controls the ordering of the link in the stack. The
// priority controls the ordering of the link in the stack. The
// lower the number, the lower in the list
$priority = -2;
@ -83,14 +83,14 @@ button configuration.
}
}
To have the link appear, make sure you add the extension to the `LeftAndMain`
class. For more information about configuring extensions see the
To have the link appear, make sure you add the extension to the `LeftAndMain`
class. For more information about configuring extensions see the
[DataExtension reference](../reference/dataextension).
:::php
LeftAndMain::add_extension('CustomLeftAndMain')
## Related
* [How to extend the CMS interface](extend-cms-interface)

View File

@ -39,11 +39,11 @@ code like this:
...
</ul>
...
By applying the proper style sheet, the snippet html above could produce the look of:
![Page Node Screenshot](../_images/tree_node.png "Page Node")
SiteTree is a `[api:DataObject]` which is versioned by `[api:Versioned]` extension.
By applying the proper style sheet, the snippet html above could produce the look of:
![Page Node Screenshot](..//_images/tree_node.png "Page Node")
SiteTree is a `[api:DataObject]` which is versioned by `[api:Versioned]` extension.
Each node can optionally have publication status flags, e.g. "Removed from draft".
Each flag has a unique identifier, which is also used as a CSS class for easier styling.
@ -66,7 +66,7 @@ __Example: using a subclass__
public function getScheduledToPublish(){
// return either true or false
}
public function getStatusFlags($cached = true) {
$flags = parent::getStatusFlags($cached);
$flags['scheduledtopublish'] = "Scheduled To Publish";
@ -76,6 +76,6 @@ __Example: using a subclass__
The above subclass of `[api:SiteTree]` will add a new flag for indicating its
__'Scheduled To Publish'__ status. The look of the page node will be changed
from ![Normal Page Node](../_images/page_node_normal.png) to ![Scheduled Page Node](../_images/page_node_scheduled.png). The getStatusFlags has an `updateStatusFlags()`
from ![Normal Page Node](..//_images/page_node_normal.png) to ![Scheduled Page Node](..//_images/page_node_scheduled.png). The getStatusFlags has an `updateStatusFlags()`
extension point, so the flags can be modified through `DataExtension` rather than
inheritance as well. Deleting existing flags works by simply unsetting the array key.

View File

@ -1,13 +1,15 @@
# Site Reports
title: Customise site reports
summary: Creating your own custom data or content reports.
# Customise site reports
## Introduction
A report is a little bit of functionally in the CMS designed to provide a report of your data or content. You can access
Reports are a useful feature in the CMS designed to provide a view of your data or content. You can access
the site reports by clicking *Reports* in the left hand side bar and selecting the report you wish to view.
![](_images/sitereport.png)
![](/_images/sitereport.png)
## Default Reports
## Default reports
By default the CMS ships with several basic reports:
@ -21,10 +23,10 @@ By default the CMS ships with several basic reports:
Modules may come with their own additional reports.
## Creating Custom Reports
## Creating custom reports
Custom reports can be created quickly and easily. A general knowledge of SilverStripe's
[Datamodel](/topics/datamodel) is useful before creating a custom report.
[datamodel and ORM](../../model/data_model_and_orm) is useful before creating a custom report.
Inside the *mysite/code* folder create a file called *CustomSideReport.php*. Inside this file we can add our site reports.
@ -68,5 +70,5 @@ More useful reports can be created by changing the `DataList` returned in the `s
* How to format and make advanced reports.
* More examples
## API Documentation
## API documentation
`[api:ReportAdmin]`

View File

@ -2,12 +2,12 @@
## Introduction ##
The CMS interface works just like any other part of your website: It consists of
PHP controllers, templates, CSS stylesheets and JavaScript. Because it uses the
same base elements, it is relatively easy to extend.
The CMS interface works just like any other part of your website: It consists of
PHP controllers, templates, CSS stylesheets and JavaScript. Because it uses the
same base elements, it is relatively easy to extend.
As an example, we're going to add a permanent "bookmarks" link list to popular pages
into the main CMS menu. A page can be bookmarked by a CMS author through a
As an example, we're going to add a permanent "bookmarks" link list to popular pages
into the main CMS menu. A page can be bookmarked by a CMS author through a
simple checkbox.
For a deeper introduction to the inner workings of the CMS, please refer to our
@ -15,8 +15,8 @@ guide on [CMS Architecture](../reference/cms-architecture).
## Overload a CMS template ##
If you place a template with an identical name into your application template
directory (usually `mysite/templates/`), it'll take priority over the built-in
If you place a template with an identical name into your application template
directory (usually `mysite/templates/`), it'll take priority over the built-in
one.
CMS templates are inherited based on their controllers, similar to subclasses of
@ -24,10 +24,10 @@ the common `Page` object (a new PHP class `MyPage` will look for a `MyPage.ss` t
We can use this to create a different base template with `LeftAndMain.ss`
(which corresponds to the `LeftAndMain` PHP controller class).
Copy the template markup of the base implementation at `framework/admin/templates/Includes/LeftAndMain_Menu.ss`
into `mysite/templates/Includes/LeftAndMain_Menu.ss`. It will automatically be picked up by
Copy the template markup of the base implementation at `framework/admin/templates/Includes/LeftAndMain_Menu.ss`
into `mysite/templates/Includes/LeftAndMain_Menu.ss`. It will automatically be picked up by
the CMS logic. Add a new section into the `<ul class="cms-menu-list">`
:::ss
...
<ul class="cms-menu-list">
@ -40,15 +40,15 @@ the CMS logic. Add a new section into the `<ul class="cms-menu-list">`
</li>
</ul>
...
Refresh the CMS interface with `admin/?flush=all`, and you should see those
hardcoded links underneath the left-hand menu. We'll make these dynamic further down.
hardcoded links underneath the left-hand menu. We'll make these dynamic further down.
## Include custom CSS in the CMS
In order to show the links a bit separated from the other menu entries,
we'll add some CSS, and get it to load
with the CMS interface. Paste the following content into a new file called
In order to show the links a bit separated from the other menu entries,
we'll add some CSS, and get it to load
with the CMS interface. Paste the following content into a new file called
`mysite/css/BookmarkedPages.css`:
:::css
@ -64,9 +64,9 @@ Load the new CSS file into the CMS, by setting the `LeftAndMain.extra_requiremen
## Create a "bookmark" flag on pages ##
Now we'll define which pages are actually bookmarked, a flag that is stored in
the database. For this we need to decorate the page record with a
`DataExtension`. Create a new file called `mysite/code/BookmarkedPageExtension.php`
Now we'll define which pages are actually bookmarked, a flag that is stored in
the database. For this we need to decorate the page record with a
`DataExtension`. Create a new file called `mysite/code/BookmarkedPageExtension.php`
and insert the following code.
:::php
@ -77,7 +77,7 @@ and insert the following code.
private static $db = array(
'IsBookmarked' => 'Boolean'
);
public function updateCMSFields(FieldList $fields) {
$fields->addFieldToTab('Root.Main',
new CheckboxField('IsBookmarked', "Show in CMS bookmarks?")
@ -98,8 +98,8 @@ Refresh the CMS, open a page for editing and you should see the new checkbox.
## Retrieve the list of bookmarks from the database
One piece in the puzzle is still missing: How do we get the list of bookmarked
pages from the database into the template we've already created (with hardcoded
links)? Again, we extend a core class: The main CMS controller called
pages from the database into the template we've already created (with hardcoded
links)? Again, we extend a core class: The main CMS controller called
`LeftAndMain`.
Add the following code to a new file `mysite/code/BookmarkedLeftAndMainExtension.php`;
@ -113,7 +113,7 @@ Add the following code to a new file `mysite/code/BookmarkedLeftAndMainExtension
return Page::get()->filter("IsBookmarked", 1);
}
}
Enable the extension in your [configuration file](/topics/configuration)
:::yml
@ -137,50 +137,50 @@ and replace it with the following:
## Extending the CMS actions
CMS actions follow a principle similar to the CMS fields: they are built in the
backend with the help of `FormFields` and `FormActions`, and the frontend is
CMS actions follow a principle similar to the CMS fields: they are built in the
backend with the help of `FormFields` and `FormActions`, and the frontend is
responsible for applying a consistent styling.
The following conventions apply:
* New actions can be added by redefining `getCMSActions`, or adding an extension
* New actions can be added by redefining `getCMSActions`, or adding an extension
with `updateCMSActions`.
* It is required the actions are contained in a `FieldSet` (`getCMSActions`
* It is required the actions are contained in a `FieldSet` (`getCMSActions`
returns this already).
* Standalone buttons are created by adding a top-level `FormAction` (no such
* Standalone buttons are created by adding a top-level `FormAction` (no such
button is added by default).
* Button groups are created by adding a top-level `CompositeField` with
* Button groups are created by adding a top-level `CompositeField` with
`FormActions` in it.
* A `MajorActions` button group is already provided as a default.
* Drop ups with additional actions that appear as links are created via a
* Drop ups with additional actions that appear as links are created via a
`TabSet` and `Tabs` with `FormActions` inside.
* A `ActionMenus.MoreOptions` tab is already provided as a default and contains
* A `ActionMenus.MoreOptions` tab is already provided as a default and contains
some minor actions.
* You can override the actions completely by providing your own
* You can override the actions completely by providing your own
`getAllCMSFields`.
Let's walk through a couple of examples of adding new CMS actions in `getCMSActions`.
First of all we can add a regular standalone button anywhere in the set. Here
we are inserting it in the front of all other actions. We could also add a
First of all we can add a regular standalone button anywhere in the set. Here
we are inserting it in the front of all other actions. We could also add a
button group (`CompositeField`) in a similar fashion.
:::php
$fields->unshift(FormAction::create('normal', 'Normal button'));
We can affect the existing button group by manipulating the `CompositeField`
We can affect the existing button group by manipulating the `CompositeField`
already present in the `FieldList`.
:::php
$fields->fieldByName('MajorActions')->push(FormAction::create('grouped', 'New group button'));
Another option is adding actions into the drop-up - best place for placing
Another option is adding actions into the drop-up - best place for placing
infrequently used minor actions.
:::php
$fields->addFieldToTab('ActionMenus.MoreOptions', FormAction::create('minor', 'Minor action'));
We can also easily create new drop-up menus by defining new tabs within the
We can also easily create new drop-up menus by defining new tabs within the
`TabSet`.
:::php
@ -190,12 +190,12 @@ We can also easily create new drop-up menus by defining new tabs within the
Empty tabs will be automatically removed from the `FieldList` to prevent clutter.
</div>
New actions will need associated controller handlers to work. You can use a
`LeftAndMainExtension` to provide one. Refer to [Controller documentation](../topics/controller)
New actions will need associated controller handlers to work. You can use a
`LeftAndMainExtension` to provide one. Refer to [Controller documentation](../topics/controller)
for instructions on setting up handlers.
To make the actions more user-friendly you can also use alternating buttons as
detailed in the [CMS Alternating Button](../reference/cms-alternating-button)
To make the actions more user-friendly you can also use alternating buttons as
detailed in the [CMS Alternating Button](../reference/cms-alternating-button)
how-to.
## Summary

View File

@ -0,0 +1,22 @@
## Extending existing ModelAdmin
Sometimes you'll work with ModelAdmins from other modules. To customize these interfaces, you can always subclass. But there's
also another tool at your disposal: The `[api:Extension]` API.
:::php
class MyAdminExtension extends Extension {
// ...
public function updateEditForm(&$form) {
$form->Fields()->push(/* ... */)
}
}
Now enable this extension through your `[config.yml](/topics/configuration)` file.
:::yml
MyAdmin:
extensions:
- MyAdminExtension
The following extension points are available: `updateEditForm()`, `updateSearchContext()`,
`updateSearchForm()`, `updateList()`, `updateImportForm`.

View File

@ -0,0 +1,13 @@
title: Customising the Admin Interface
summary: Extend the admin view to provide custom behavior or new features for CMS and admin users.
introduction: The Admin interface can be extended to provide additional functionality to users and custom interfaces for managing data.
The Admin interface is bundled within the SilverStripe Framework but is most commonly used in conjunction with the `CMS`
module. The main class for displaying the interface is a specialized [api:Controller] called [api:LeftAndMain], named
as it is designed around a left hand navigation and a main edit form.
[CHILDREN]
## How to's
[CHILDREN Folder="How_Tos"]

View File

@ -1,9 +1,11 @@
title: Flushable
summary: Allows a class to define it's own flush functionality.
# Flushable
## Introduction
Allows a class to define it's own flush functionality, which is triggered when `flush=1` is requested in the URL.
`[api:FlushRequestFilter]` is run before a request is made, calling `flush()` statically on all
implementors of `[api:Flushable]`.

View File

@ -0,0 +1,83 @@
title: Manifests
summary: Manage caches of file path maps and other expensive information
# Manifests
## Purpose
Manifests help to cache information which is too expensive to generate on each request.
Some manifests generate maps, e.g. class names to filesystem locations.
Others store aggregate information like nested configuration graphs.
## Storage
By default, manifests are stored on the local filesystem through PHP's `serialize()` method.
Combined with PHP opcode caching this provides fast access.
In order to share manifests between servers, or centralise cache management,
other storage adapters are available. These can be configured by a `SS_MANIFESTCACHE` constant,
placed in your `_ss_environment.php`.
* `ManifestCache_File`: The default adapter using PHP's `serialize()`
* `ManifestCache_File_PHP`: Using `var_export()`, which is faster when a PHP opcode cache is installed
* `ManifestCache_APC`: Use PHP's [APC object cache](http://php.net/manual/en/book.apc.php)
You can write your own adapters by implementing the `ManifestCache` interface.
## Traversing the Filesystem
Since manifests usually extract their information from files in the webroot,
they require a powerful traversal tool: `[api:SS_FileFinder]`.
The class provides filtering abilities for files and folders, as well as
callbacks for recursive traversal. Each manifest has its own implementation,
for example `[api:ManifestFileFinder]`, adding more domain specific filtering
like including themes, or excluding testss.
## PHP Class Manifest
The `[api:ClassManifest]` builds a manifest of all classes, interfaces and some
additional items present in a directory, and caches it.
It finds the following information:
* Class and interface names and paths
* All direct and indirect descendants of a class
* All implementors of an interface
* All module configuration files
The gathered information can be accessed through `[api:SS_ClassLoader::instance()]`,
as well as `[api:ClassInfo]`. Some useful commands of the `ClassInfo` API:
* `ClassInfo::subclassesFor($class)`: Returns a list of classes that inherit from the given class
* `ClassInfo::ancestry($class)`: Returns the passed class name along with all its parent class names
* `ClassInfo::implementorsOf($interfaceName)`: Returns all classes implementing the passed in interface
In the absence of a generic module API, it is also the primary way to identify
which modules are installed, through `[api:ClassManifest::getModules()]`.
A module is defined as a toplevel folder in the webroot which contains
either a `_config.php` file, or a `_config/` folder. Modules can be specifically
excluded from manifests by creating a blank `_manifest_exclude` file in the module folder.
By default, the finder implementation will exclude any classes stored in files within
a `tests/` folder, unless tests are executed.
## Template Manifest
The `[api:SS_TemplateManifest]` class builds a manifest of all templates present in a directory,
in both modules and themes. Templates in `tests/` folders are automatically excluded.
The chapter on [template inheritance](../templates/template-inheritance) provides more details
on its operation.
## Config Manifest
The `[api:SS_ConfigManifest]` loads builds a manifest of configuration items,
for both PHP and YAML. It also takes care of ordering and merging configuration fragments.
The chapter on [configuration](/topics/configuration) has more details.
## Flushing
If a `?flush=1` query parameter is added to a URL, a call to `flush()` will be triggered
on any classes that implement the [Flushable](/reference/flushable) interface.
This enables developers to clear [manifest caches](manifests),
for example when adding new templates or PHP classes.
Note that you need to be in [dev mode](/getting_started/environment_management)
or logged-in as an administrator for flushing to take effect.

View File

@ -0,0 +1,139 @@
summary: An overview of the steps involved in delivering a SilverStripe web page.
# Execution Pipeline
## Introduction
In order to transform a HTTP request or a commandline exeuction into a response,
SilverStripe needs to boot its core and run through several stages of processing.
## Request Rewriting
The first step in most environments is a rewrite of a request path into parameters passed to a PHP script.
This allows writing friendly URLs instead of linking directly to PHP files.
The implementation depends on your web server, we'll show you the most common one here:
Apache with [mod_rewrite](http://httpd.apache.org/docs/2.0/mod/mod_rewrite.html).
Check our [installation guides](/getting_started/installation) on how other web servers like IIS or nginx handle rewriting.
The standard SilverStripe project ships with a `.htaccess` file in your webroot for this purpose.
By default, requests will be passed through for files existing on the filesystem.
Some access control is in place to deny access to potentially sensitive files in the webroot, such as YAML configuration files.
If no file can be directly matched, control is handed off to `framework/main.php`.
### SILVERSTRIPE START ###
# Deny access to templates (but allow from localhost)
<Files *.ss>
Order deny,allow
Deny from all
Allow from 127.0.0.1
</Files>
# Deny access to IIS configuration
<Files web.config>
Order deny,allow
Deny from all
</Files>
# Deny access to YAML configuration files which might include sensitive information
<Files ~ "\.ya?ml$">
Order allow,deny
Deny from all
</Files>
# Route errors to static pages automatically generated by SilverStripe
ErrorDocument 404 /assets/error-404.html
ErrorDocument 500 /assets/error-500.html
<IfModule mod_rewrite.c>
SetEnv HTTP_MOD_REWRITE On
RewriteEngine On
# Deny access to potentially sensitive files and folders
RewriteRule ^vendor(/|$) - [F,L,NC]
RewriteRule silverstripe-cache(/|$) - [F,L,NC]
RewriteRule composer\.(json|lock) - [F,L,NC]
# Process through SilverStripe if no file with the requested name exists.
# Pass through the original path as a query parameter, and retain the existing parameters.
RewriteCond %{REQUEST_URI} ^(.*)$
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule .* framework/main.php?url=%1 [QSA]
# If requesting the main script directly, rewrite to the installer
RewriteCond %{REQUEST_URI} ^(.*)/framework/main.php$
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule . %1/install.php? [R,L]
</IfModule>
### SILVERSTRIPE END ###
SilverStripe can also operate without this level of rewriting, in which case all dynamic requests go
through an `index.php` script in the webroot.
<div class="notice" markdown="1">
Running SilverStripe without web server based rewriting is not recommended since it
can leave sensitive files exposed to public access (the `RewriteRule` conditions from above don't apply).
</div>
## Bootstrap
All requests go through `framework/main.php`, which sets up the execution environment:
* Tries to locate an `_ss_environment.php`
[configuration file](/getting_started/environment_management) in the webroot,
or the two levels above it (to allow sharing configuration between multiple webroots).
* Sets constants based on the filesystem structure (e.g. `BASE_URL`, `BASE_PATH` and `TEMP_FOLDER`)
* Normalizes the `url` parameter in preparation for handing it off to `Director`
* Connects to a database, based on information stored in the global `$databaseConfig` variable.
The configuration is either defined in your `_config.php`, or through `_ss_environment.php`
* Sets up [error handlers](../debugging/error_handling)
* Optionally continues a [session](../cookies_and_sessions/sessions) if the request already contains a session identifier
* Loads manifests for PHP classes, templates, as well as any [YAML configuration](../configuration).
* Optionally regenerates these manifests (if a ["flush" query parameter](flushable) is set)
* Executes all procedural configuration defined through `_config.php` in all discovered modules
* Loads the Composer PHP class autoloader
* Hands control over to `[api:Director]`
While you usually don't need to modify the bootstrap on this level, some deeper customizations like
adding your own manifests or a performance-optimized routing might require it.
An example of this can be found in the ["staticpublisher" module](https://github.com/silverstripe-labs/silverstripe-staticpublisher/blob/master/main.php).
The modules instructs web servers to route through its own `main.php` to determine which requests can be cached
before handing control off to SilverStripe's own `main.php`.
## Routing and Request Handling
The `main.php` script relies on `[api:Director]` to work out which [controller](../controllers) should handle this request. It parses the URL, matching it to one of a number of patterns,
and determines the controller, action and any argument to be used ([Routing](../controllers/routing)).
* Creates a `[api:SS_HTTPRequest]` object containing all request and environment information
* The [session](../cookies_and_sessions/sessions) holds an abstraction of PHP session
* Instantiates a [controller](../Controllers) object
* The `[api:Injector]` is first referenced, and asks the registered
[RequestFilter](../controller/request_filters)
to pre-process the request object (see below)
* The `Controller` executes the actual business logic and populates an `[api:SS_HTTPResponse]`
* The `Controller` can optionally hand off control to further nested controllers
* The `Controller` optionally renders a response body through `SSViewer` [templates](../templates)
* The `[api:RequestProcessor]` is called to post-process the request to allow
further filtering before content is sent to the end user
* The response is output to the client
## Request Preprocessing and Postprocessing
The framework provides the ability to hook into the request both before and
after it is handled to allow binding custom logic. This can be used
to transform or filter request data, instanciate helpers, execute global logic,
or even short-circuit execution (e.g. to enforce custom authentication schemes).
The ["Request Filters" documentation](../controller/request_filters) shows you how.
## Flushing Manifests
If a `?flush=1` query parameter is added to a URL, a call to `flush()` will be triggered
on any classes that implement the [Flushable](/reference/flushable) interface.
This enables developers to clear [manifest caches](manifests),
for example when adding new templates or PHP classes.
Note that you need to be in [dev mode](/getting_started/environment_management)
or logged-in as an administrator for flushing to take effect.
[CHILDREN]

View File

@ -0,0 +1,161 @@
title: Command Line Interface
summary: Automate SilverStripe, run Cron Jobs or sync with other platforms through the Command Line Interface.
introduction: Automate SilverStripe, run Cron Jobs or sync with other platforms through the Command Line Interface.
SilverStripe can call [Controllers](../controllers) through a command line interface (CLI) just as easily as through a
web browser. This functionality can be used to automate tasks with cron jobs, run unit tests, or anything else that
needs to interface over the command line.
The main entry point for any command line execution is `framework/cli-script.php`. For example, to run a database
rebuild from the command line, use this command:
:::bash
cd your-webroot/
php framework/cli-script.php dev/build
<div class="notice">
Your command line php version is likely to use a different configuration as your webserver (run `php -i` to find out
more). This can be a good thing, your CLI can be configured to use higher memory limits than you would want your website
to have.
</div>
## Sake - SilverStripe Make
Sake is a simple wrapper around `cli-script.php`. It also tries to detect which `php` executable to use if more than one
are available.
<div class="info" markdown='1'>
If you are using a Debian server: Check you have the php-cli package installed for sake to work. If you get an error
when running the command php -v, then you may not have php-cli installed so sake won't work.
</div>
### Installation
`sake` can be invoked using `./framework/sake`. For easier access, copy the `sake` file into `/usr/bin/sake`.
cd your-webroot/
sudo ./framework/sake installsake
<div class="warning">
This currently only works on UNIX like systems, not on Windows.
</div>
### Configuration
Sometimes SilverStripe needs to know the URL of your site. For example, when sending an email or generating static
files. When you're visiting the site in a web browser this is easy to work out, but when executing scripts on the
command line, it has no way of knowing. To work this out, add lines to your
[_ss_environment.php](/getting_started/environment_management) file.
:::php
global $_FILE_TO_URL_MAPPING;
$_FILE_TO_URL_MAPPING['/Users/sminnee/Sites'] = 'http://localhost';
The above statement tells SilverStripe that anything executed under the `/Users/sminnee/Sites` directory will have the
base URL `http://localhost`. The site `/Users/sminnee/Sites/my_silverstripe_project` will translate to the URL
`http://localhost/my_silverstripe_project`.
You can add multiple file to url mapping definitions. The most specific mapping will be used.
:::php
global $_FILE_TO_URL_MAPPING;
$_FILE_TO_URL_MAPPING['/Users/sminnee/Sites'] = 'http://localhost';
$_FILE_TO_URL_MAPPING['/Users/sminnee/Sites/my_silverstripe_project'] = 'http://project.localhost';
### Usage
Sake can run any controller by passing the relative URL to that controller.
:::bash
sake /
# returns the homepage
sake dev/
# shows a list of development operations
Sake is particularly useful for running build tasks.
:::bash
sake dev/build "flush=1"
Or running unit tests..
:::bash
sake dev/tests/all
It can also be handy if you have a long running script..
:::bash
sake dev/tasks/MyReallyLongTask
### Running processes
`sake` can be used to make daemon processes for your application.
Make a task or controller class that runs a loop. To avoid memory leaks, you should make the PHP process exit when it
hits some reasonable memory limit. Sake will automatically restart your process whenever it exits.
Include some appropriate sleep()s so that your process doesn't hog the system. The best thing to do is to have a short
sleep when the process is in the middle of doing things, and a long sleep when doesn't have anything to do.
This code provides a good template:
:::php
<?php
class MyProcess extends Controller {
private static $allowed_actions = array(
'index'
);
function index() {
set_time_limit(0);
while(memory_get_usage() < 32*1024*1024) {
if($this->somethingToDo()) {
$this->doSomething();
sleep(1)
} else {
sleep(300);
}
}
}
}
Then the process can be managed through `sake`
:::bash
sake -start MyProcess
sake -stop MyProcess
<div class="notice">
`sake` stores `pid` and log files in the site root directory.
</div>
## Arguments
Parameters can be added to the command. All parameters will be available in `$_GET` array on the server.
:::bash
cd your-webroot/
php framework/cli-script.php myurl myparam=1 myotherparam=2
Or if you're using `sake`
:::bash
sake myurl "myparam=1&myotherparam=2"
## Running Regular Tasks With Cron
On a UNIX machine, you can typically run a scheduled task with a [cron job](http://en.wikipedia.org/wiki/Cron). Run
`BuildTask` in SilverStripe as a cron job using `sake`.
The following will run `MyTask` every minute.
:::bash
* * * * * /your/site/folder/sake dev/tasks/MyTask

View File

@ -0,0 +1,103 @@
title: Cookies
summary: A set of static methods for manipulating PHP cookies.
# Cookies
Cookies are a mechanism for storing data in the remote browser and thus tracking or identifying return users.
SilverStripe uses cookies for remembering users preferences. Application code can modify a users cookies through
the [api:Cookie] class. This class mostly follows the PHP API.
## set
Sets the value of cookie with configuration.
:::php
Cookie::set($name, $value, $expiry = 90, $path = null, $domain = null, $secure = false, $httpOnly = false);
// Cookie::set('MyApplicationPreference', 'Yes');
## get
Returns the value of cookie.
:::php
Cookie::get($name);
// Cookie::get('MyApplicationPreference');
// returns 'Yes'
## force_expiry
Clears a given cookie.
:::php
Cookie::force_expiry($name, $path = null, $domain = null);
// Cookie::force_expiry('MyApplicationPreference')
## Cookie_Backend
The [api:Cookie] class manipulates and sets cookies using a [api:Cookie_Backend]. The backend is in charge of the logic
that fetches, sets and expires cookies. By default we use a [api:CookieJar] backend which uses PHP's
[setcookie](http://www.php.net/manual/en/function.setcookie.php) function.
The [api:CookieJar] keeps track of cookies that have been set by the current process as well as those that were received
from the browser.
:::php
$myCookies = array(
'cookie1' => 'value1',
);
$newBackend = new CookieJar($myCookies);
Injector::inst()->registerService($newBackend, 'Cookie_Backend');
Cookie::get('cookie1');
## Resetting the Cookie_Backend state
Assuming that your application hasn't messed around with the `$_COOKIE` superglobal, you can reset the state of your
`Cookie_Backend` by simply unregistering the `CookieJar` service with `Injector`. Next time you access `Cookie` it'll
create a new service for you using the `$_COOKIE` superglobal.
:::php
Injector::inst()->unregisterNamedObject('Cookie_Backend');
Cookie::get('cookiename'); // will return $_COOKIE['cookiename'] if set
Alternatively, if you know that the superglobal has been changed (or you aren't sure it hasn't) you can attempt to use
the current `CookieJar` service to tell you what it was like when it was registered.
:::php
//store the cookies that were loaded into the `CookieJar`
$recievedCookie = Cookie::get_inst()->getAll(false);
//set a new `CookieJar`
Injector::inst()->registerService(new CookieJar($recievedCookie), 'CookieJar');
### Using your own Cookie_Backend
If you need to implement your own Cookie_Backend you can use the injector system to force a different class to be used.
:::yml
---
Name: mycookie
After: '#cookie'
---
Injector:
Cookie_Backend:
class: MyCookieJar
To be a valid backend your class must implement the [api:Cookie_Backend] interface.
## API Documentation
* [api:Cookie]
* [api:CookieJar]
* [api:CookieBackend]

View File

@ -0,0 +1,68 @@
title: Sessions
summary: A set of static methods for manipulating PHP sessions.
# Sessions
Session support in PHP consists of a way to preserve certain data across subsequent accesses such as logged in user
information and security tokens.
In order to support things like testing, the session is associated with a particular Controller. In normal usage,
this is loaded from and saved to the regular PHP session, but for things like static-page-generation and
unit-testing, you can create multiple Controllers, each with their own session.
## set
:::php
Session::set('MyValue', 6);
Saves the value of to session data. You can also save arrays or serialized objects in session (but note there may be
size restrictions as to how much you can save).
:::php
// saves an array
Session::set('MyArrayOfValues', array('1','2','3'));
// saves an object (you'll have to unserialize it back)
$object = new Object();
Session::set('MyObject', serialize($object));
## get
Once you have saved a value to the Session you can access it by using the `get` function. Like the `set` function you
can use this anywhere in your PHP files.
:::php
echo Session::get('MyValue');
// returns 6
$data = Session::get('MyArrayOfValues');
// $data = array(1,2,3)
$object = unserialize(Session::get('MyObject', $object));
// $object = Object()
## get_all
You can also get all the values in the session at once. This is useful for debugging.
:::php
Session::get_all();
// returns an array of all the session values.
## clear
Once you have accessed a value from the Session it doesn't automatically wipe the value from the Session, you have
to specifically remove it.
:::php
Session::clear('MyValue');
Or you can clear every single value in the session at once. Note SilverStripe stores some of its own session data
including form and page comment information. None of this is vital but `clear_all` will clear everything.
:::php
Session::clear_all();
## API Documentation
* [api:Session]

View File

@ -0,0 +1,10 @@
title: Cookies and Sessions
summary: Save state information using the Cookie class and the Session class.
introduction: Both the Cookie and Session classes can be used to preserve certain data across subsequent page requests.
[CHILDREN]
## API Documentation
* [api:Cookie]
* [api:Session]

View File

@ -0,0 +1,7 @@
title: Developer Guides
introduction: The following guides take a more detailed look into the core concepts and code examples for building SilverStripe applications.
In each guide you'll find reference documentation followed by a collection of short and informal How-to's which contain
snippets of code to use in your own projects.
[CHILDREN]

View File

@ -1,30 +1,41 @@
title: Upgrading
introduction: Keep your SilverStripe installations up to date with the latest fixes, security patches and new features.
# Upgrading
Usually an update or upgrade to your SilverStripe installation just means
overwriting files and updating your database-schema.
SilverStripe Applications should be kept up to date with the latest security releases. Usually an update or upgrade to
your SilverStripe installation means overwriting files, flushing the cache and updating your database-schema.
<div class="info" markdown="1">
See our [upgrade notes and changelogs](/changelogs) for release-specific information.
</div>
## Process
## Composer
For projects managed through Composer, check the version defined in your `composer.json` file. Update the version
constraints if required and update composer.
:::bash
composer update
## Manual
* Check if any modules (e.g. blog or forum) in your installation are incompatible and need to be upgraded as well
* Backup your database content
* Backup your webroot files
* Download the new release and uncompress it to a temporary folder
* Leave custom folders like *mysite* or *themes* in place.
* Identify system folders in your webroot (`cms`, `framework`, `sapphire` and any additional modules).
* Identify system folders in your webroot (`cms`, `framework` and any additional modules).
* Delete existing system folders (or move them outside of your webroot)
* Extract and replace system folders from your download (Deleting instead of "copying over" existing folders ensures that files removed from the new SilverStripe release are not persisting in your installation)
* Visit http://localhost/dev/build/?flush=1 to rebuild the website database
* Visit http://yoursite.com/dev/build/?flush=1 to rebuild the website database.
* Check if you need to adapt your code to changed PHP APIs
* Check if you have overwritten any core templates or styles which might need an update
* See [common-problems](common-problems) for a list of likely mistakes that could happen during an upgrade.
* Check if you have overwritten any core templates or styles which might need an update.
<div class="warning" markdown="1">
Never update a website on the live server without trying it on a development copy first.
Never update a website on the live server without trying it on a development copy first.
</div>
## Decision Helpers
How easy will it be to update my project? It's a fair question, and sometimes a difficult one to answer.
@ -32,10 +43,10 @@ How easy will it be to update my project? It's a fair question, and sometimes a
* "Micro" releases (x.y.z) are explicitly backwards compatible, "minor" and "major" releases can deprecate features and change APIs (see our [/misc/release-process](release process) for details)
* If you've made custom branches of SilverStripe core, or any thirdparty module, it's going to be harder to upgrade.
* The more custom features you have, the harder it will be to upgrade. You will have to re-test all of those features, and adapt to API changes in core.
* Customisations of a well defined type - such as custom page types or custom blog widgets - are going to be easier to upgrade than customisations that modify deep system internals like rewriting SQL queries.
* Customizations of a well defined type - such as custom page types or custom blog widgets - are going to be easier to upgrade than customisations that modify deep system internals like rewriting SQL queries.
## Related
* [Release Announcements](http://groups.google.com/group/silverstripe-announce/)
* [Blog posts about releases on silverstripe.org](http://silverstripe.org/blog/tag/release)
* [/misc/release-process](Release Process)
* [Release Announcements](http://groups.google.com/group/silverstripe-announce/)
* [Blog posts about releases on silverstripe.org](http://silverstripe.org/blog/tag/release)
* [Release Process](../contributing/release_process)

View File

@ -48,7 +48,7 @@ Special attention will need to be given to custom sections on a case-by-case bas
As we have changed the design of the CMS, the top bar for your custom sections is no longer needed. We've moved the
buttons that it once held down to the bottom.
![](_images/cms22screenie.jpg)
![](/_images/cms22screenie.jpg)
### Classes added to 2.2 that may conflict

View File

@ -403,8 +403,8 @@ We have also moved all fields from the "Metadata" tab into the "Main Content" ta
$fields->addFieldToTab('Root.Main', $myField);
$fields->addFieldToTab('Root.Main', $myOtherField);
![Tab paths in 2.4](_images/tab-paths-before.png)
![Tab paths in 3.0](_images/tab-paths-after.png)
![Tab paths in 2.4](/_images/tab-paths-before.png)
![Tab paths in 3.0](/_images/tab-paths-after.png)
The old paths are rewritten automatically, but will be deprecated in the next point release.
If you are working with tab objects directly in your `FieldSet`, you'll need to update
@ -421,7 +421,7 @@ to reduce UI clutter. You still need to address it through the usual tabset meth
as the underlying object structure doesn't change. Once you add more tabs,
e.g. to the "Root.Main" tab in `SiteTree`, the tab bar automatically shows.
![Tab paths in 3.0 with a custom tab](_images/tab-paths-customtab.png)
![Tab paths in 3.0 with a custom tab](/_images/tab-paths-customtab.png)
### New `SiteTree::$description` field to describe purpose of a page type [sitetree-description]###

View File

@ -0,0 +1,35 @@
# 3.0.9
## Overview
### Default current Versioned "stage" to "Live" rather than "Stage"
Previously only the controllers responsible for page and CMS display
(`LeftAndMain` and `ContentController`) explicitly set a stage through
`Versioned::choose_site_stage()`. Unless this method is called,
the default stage will be "Stage", showing draft content.
Any direct subclasses of `Controller` interacting with "versioned" objects
are vulnerable to exposing unpublished content, unless `choose_site_stage()`
is called explicitly in their own logic.
In order to provide more secure default behaviour, we have changed
`choose_site_stage()` to be called on all requests, defaulting to the "Live" stage.
If your logic relies on querying draft content, use `Versioned::reading_stage('Stage')`.
Important: The `choose_site_stage()` call only deals with setting the default stage,
and doesn't check if the user is authenticated to view it. As with any other controller logic,
please use `DataObject->canView()` to determine permissions.
:::php
class MyController extends Controller {
private static $allowed_actions = array('showpage');
public function showpage($request) {
$page = Page::get()->byID($request->param('ID'));
if(!$page->canView()) return $this->httpError(401);
// continue with authenticated logic...
}
}
### API Changes
* 2013-08-03 [0e7231f](https://github.com/silverstripe/sapphire/commit/0e7231f) Disable discontinued Google Spellcheck in TinyMCE (Ingo Schommer)

View File

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -0,0 +1,11 @@
title: Changelogs
introduction: Key information on new features and improvements in each version.
Keep up to date with new releases subscribe to the [SilverStripe Release Announcements](https://groups.google.com/group/silverstripe-announce) group,
or read our [blog posts about releases](http://silverstripe.org/blog/tag/release).
We also keep an overview of [security-related releases](http://silverstripe.org/security-releases/).
For information on how to upgrade to newer versions consult the [upgrading](/upgrading) guide.
[CHILDREN]

View File

@ -1,6 +1,7 @@
# Contributing Issues and Opinions
title: Bug Reports
summary: Report bugs or problems with SilverStripe, feature requests or other issues.
[« Back to Contributing page](../contributing)
# Contributing Issues and Opinions
## Reporting Bugs
@ -31,26 +32,31 @@ If the issue does look like a new bug:
* Describe your environment as detailed as possible: SilverStripe version, Browser, PHP version, Operating System, any installed SilverStripe modules.
* *(optional)* [Submit a pull request](/misc/contributing/code) which fixes the issue.
Lastly, don't get your hopes up too high. Unless your issue is a blocker affecting a large
number of users, don't expect SilverStripe developers to jump onto it right way.
Your issue is a starting point where others with the same problem can collaborate
with you to develop a fix.
Lastly, don't get your hopes up too high. Unless your issue is a blocker
affecting a large number of users, don't expect SilverStripe developers to jump
onto it right way. Your issue is a starting point where others with the same
problem can collaborate with you to develop a fix.
## Feature Requests
<div class="warning" markdown='1'>
Please don't file "feature requests" as issues. If there's a new feature you'd like to see
in SilverStripe, you either need to write it yourself (and [submit a pull request](/misc/contributing/code))
or convince somebody else to write it for you. Any "wishlist" type issues without code attached
can be expected to be closed as soon as they're reviewed.
Please don't file "feature requests" as Github issues. If there's a new feature
you'd like to see in SilverStripe, you either need to write it yourself (and
[submit a pull request](/misc/contributing/code)) or convince somebody else to
write it for you. Any "wishlist" type issues without code attached can be
expected to be closed as soon as they're reviewed.
</div>
In order to gain interest and feedback in your feature, we encourage you to present
it to the community through the [forums](http://silverstripe.org/forums), [core mailinglist](http://groups.google.com/group/silverstripe-dev) or on [IRC](http://silverstripe.org/irc).
In order to gain interest and feedback in your feature, we encourage you to
present it to the community through the [forums](http://silverstripe.org/forums),
[core mailinglist](http://groups.google.com/group/silverstripe-dev) or on
[IRC](http://silverstripe.org/irc).
## Reporting Security Issues
Report security issues to [security@silverstripe.com](mailto:security@silverstripe.com). See our "[Release Process](release-process)" documentation for more info, and read our guide on [how to write secure code](/topics/security).
Report security issues to [security@silverstripe.com](mailto:security@silverstripe.com).
See our "[Release Process](release-process)" documentation for more info, and
read our guide on [how to write secure code](/topics/security).
## Sharing your Opinion

View File

@ -0,0 +1,115 @@
summary: Describes the process followed for "core" releases.
# Release Process
Describes the process followed for "core" releases (mainly the `framework` and `cms` modules).
## Release Maintainer
The current maintainer responsible for planning and performing releases is Ingo Schommer (ingo at silverstripe dot com).
## Release Planning
Our most up-to-date release plans are typically in the ["framework" milestone](https://github.com/silverstripe/silverstripe-framework/issues/milestones) and ["cms" milestone](https://github.com/silverstripe/silverstripe-cms/issues/milestones).
New features and API changes are discussed on the [core mailinglist](http://groups.google.com/group/silverstripe-dev). They are prioritised by the core team as tickets on
github.com. In addition, we collect community feedback on [silverstripe.uservoice.com](https://silverstripe.uservoice.com).
Any feature ideas we're planning to implement will be flagged there.
Release dates are usually not published prior to the release, but you can get a good idea of the release status by
reviewing the release milestone on github.com. Releases will be
announced on the [release announcements mailing list](http://groups.google.com/group/silverstripe-announce).
Releases of the *cms* and *framework* modules are coupled at the moment, they follow the same numbering scheme.
## Release Numbering
SilverStripe follows [Semantic Versioning](http://semver.org).
Note: Until November 2014, the project didn't adhere to Semantic Versioning. Instead. a "minor release" in semver terminology
was treated as a "major release" in SilverStripe. For example, the *3.1.0* release contained API breaking changes, and
the *3.1.1* release contained new features rather than just bugfixes.
## Supported versions
At any point in time, the core development team will support a set of releases to varying levels:
* The current *master* will get new features, bug fixes and API changes that might require major refactoring before going
into a release. At the moment, bugfixing and feature development might happen on the current major release branch (e.g. *3*), to be
merged forward to master regularly.
* Applicable bugfixes on master will also be merged back to the last major release branch, to be released as the next
patch release
* Security fixes will be applied to the current master and the previous two major releases (e.g. *4.0*, *3.2* and *3.1*).
## Deprecation
Needs of developers (both on core framework and custom projects) can outgrow the capabilities
of a certain API. Existing APIs might turn out to be hard to understand, maintain, test or stabilize.
In these cases, it is best practice to "refactor" these APIs into something more useful.
SilverStripe acknowledges that developers have built a lot of code on top of existing APIs,
so we strive for giving ample warning on any upcoming changes through a "deprecation cycle".
How to deprecate an API:
* Add a `@deprecated` item to the docblock tag, with a `{@link <class>}` item pointing to the new API to use.
* Update the deprecated code to throw a `[api:Deprecation::notice()]` error.
* Both the docblock and error message should contain the **target version** where the functionality is removed.
So if you're committing the change to a *3.1* minor release, the target version will be *4.0*.
* Deprecations should not be committed to patch releases
* Deprecations should just be committed to pre-release branches, ideally before they enter the "beta" phase.
If deprecations are introduced after this point, their target version needs to be increased by one.
* Make sure that the old deprecated function works by calling the new function - don't have duplicated code!
* The commit message should contain an `API` prefix (see ["commit message format"](code#commit-messages))
* Document the change in the [changelog](/changelogs) for the next release
* Deprecated APIs can be removed after developers had a chance to react to the changes. As a rule of thumb, leave the
code with the deprecation warning in for at least three micro releases. Only remove code in a minor or major release.
* Exceptions to the deprecation cycle are APIs that have been moved into their own module, and continue to work with the
new minor release. These changes can be performed in a single minor release without a deprecation period.
Here's an example for replacing `Director::isDev()` with a (theoretical) `Env::is_dev()`:
:::php
/**
* Returns true if your are in development mode
* @deprecated 4.0 Use {@link Env::is_dev()} instead.
*/
public function isDev() {
Deprecation::notice('4.0', 'Use Env::is_dev() instead');
return Env::is_dev();
}
This change could be committed to a minor release like *3.2.0*, and stays deprecated in all following minor releases
(e.g. *3.3.0*, *3.4.0*), until a new major release (e.g. *4.0.0*) where it gets removed from the codebase.
## Security Releases
### Reporting an issue
Report security issues to [security@silverstripe.com](mailto:security@silverstripe.com).
Please don't file security issues in our [bugtracker](issues_and_bugs).
### Acknowledgment and disclosure
In the event of a confirmed vulnerability in SilverStripe core, we will take the following actions:
* Acknowledge to the reporter that weve received the report and that a fix is forthcoming. Well give a rough
timeline and ask the reporter to keep the issue confidential until we announce it.
* Halt all other development as long as is needed to develop a fix, including patches against the current and one
previous major release (if applicable).
* We will inform you about resolution and [announce](http://groups.google.com/group/silverstripe-announce) a
[new release](http://silverstripe.org/security-releases/) publically.
You can help us determine the problem and speed up responses by providing us with more information on how to reproduce
the issue: SilverStripe version (incl. any installed modules), PHP/webserver version and configuration, anonymized
webserver access logs (if a hack is suspected), any other services and web packages running on the same server.
### Severity rating
Each [security release](http://www.silverstripe.org/security-releases/) includes an overall severity rating and one for
each vulnerability. The rating indicates how important an update is:
| Severity | Description |
|---------------|-------------|
| **Critical** | Critical releases require immediate actions. Such vulnerabilities allow attackers to take control of your site and you should upgrade on the day of release. *Example: Directory traversal, privilege escalation* |
| **Important** | Important releases should be evaluated immediately. These issues allow an attacker to compromise a site's data and should be fixed within days. *Example: SQL injection.* |
| **Moderate** | Releases of moderate severity should be applied as soon as possible. They allow the unauthorized editing or creation of content. *Examples: Cross Site Scripting (XSS) in template helpers.* |
| **Low** | Low risk releases fix information disclosure and read-only privilege escalation vulnerabilities. These updates should also be applied as soon as possible, but with an impact-dependent priority. *Example: Exposure of the core version number, Cross Site Scripting (XSS) limited to the admin interface.* |

View File

@ -0,0 +1,147 @@
title: Documentation
summary: Writing guide for contributing to SilverStripe developer and CMS user help documentation.
# Contributing documentation
Documentation for a software project is a continued and collaborative effort, we encourage everybody to contribute, from
simply fixing spelling mistakes, to writing recipes, reviewing existing documentation, and translating the whole thing.
Modifying documentation requires basic [PHPDoc](http://en.wikipedia.org/wiki/PHPDoc) and
[Markdown](http://daringfireball.net/projects/markdown/) knowledge, and a GitHub user account.
## Editing online
The easiest way of making a change to the documentation is by clicking the "Edit this page" link at the bottom of the
page you want to edit. Alternatively, you can find the appropriate .md file in the
[github.com/silverstripe/silverstripe-framework](https://github.com/silverstripe/silverstripe-framework/tree/master/docs/)
repository and press the "edit" button. **You will need a free GitHub account to do this**.
* After you have made your change, describe it in the "commit summary" and "extended description" fields below, and
press "Commit Changes".
* After that you will see form to submit a Pull Request. You should be able to adjust the version your document ion change is for and then submit the form. Your changes
will be sent to the core committers for approval.
<div class="warning" markdown='1'>
You should make the changes in the lowest branch they apply to. For instance, if you fix a spelling issue that you
found in the 3.1 documentation, submit your fix to that branch in Github and it'll be copied to the master (3.2)
version of the documentation automatically. *Don't submit multiple pull requests*.
</div>
## Editing on your computer
If you prefer to edit the content on your local machine, you can "[fork](http://help.github.com/forking/)" the
[github.com/silverstripe/silverstripe-framework](http://github.com/silverstripe/silverstripe-framework) and
[github.com/silverstripe/silverstripe-cms](http://github.com/silverstripe/silverstripe-cms) repositories and send us
"[pull requests](http://help.github.com/pull-requests/)". If you have downloaded SilverStripe or a module, chances are
that you already have these repositories on your machine.
The documentation is kept alongside the source code in the `docs/` subfolder of any SilverStripe module, framework or
CMS folder.
<div class="warning" markdown='1'>
If you submit a new feature or an API change, we strongly recommend that your patch includes updates to the necessary
documentation. This helps prevent our documentation from getting out of date.
</div>
## Repositories
* End-user: [userhelp.silverstripe.org](http://github.com/silverstripe/userhelp.silverstripe.org)
* Developer guides: [doc.silverstripe.org](http://github.com/silverstripe/doc.silverstripe.org)
* Developer API documentation: [api.silverstripe.org](http://github.com/silverstripe/api.silverstripe.org)
## Source control
In order to balance editorial control with effective collaboration, we keep documentation alongside the module source
code, e.g. in `framework/docs/`, or as code comments within PHP code. Contributing documentation is the same process as
providing any other patch (see [Contributing code](code)).
## What to write
See [what to write (jacobian.org)](http://jacobian.org/writing/great-documentation/what-to-write/) for an excellent
introduction to the different types of documentation, and
[producing OSS: "documentation"](http://producingoss.com/en/getting-started.html#documentation) for good rules of thumb
for documenting open source software.
## Structure
* Keep documentation lines to 120 characters.
* Don't duplicate: Search for existing places to put your documentation. Do you really require a new page, or just a new paragraph
of text somewhere?
* Use PHPDoc in source code: Leave low level technical documentation to code comments within PHP, in [PHPDoc](http://en.wikipedia.org/wiki/PHPDoc) format.
* API and developer guides complement each other: Both forms of documenting source code (API and Developer Guides) are valuable resources.
* Provide context: Give API documentation the "bigger picture" by referring to developer guides inside your PHPDoc.
* Make your documentation findable: Documentation lives by interlinking content, so please make sure your contribution doesn't become an
inaccessible island. Your page should at least be linked on the index page in the same folder. It can also appear
as "related content" on other resource (e.g. `/tutorials/site_search` might link to `/developer_guides/forms/introduction`).
## Writing style
* Write in second plural form: Use "we" instead of "I". It gives the text an instructive and collaborative style.
* It's okay to address the reader: For example "First you'll install a webserver" is good style.
* Write in an active and direct voice.
* Mark up correctly: Use preformatted text, emphasis and bold to make technical writing more "scannable".
* Avoid FAQs: FAQs are not a replacement of a coherent, well explained documentation. If you've done a good job
documenting, there shouldn't be any "frequently asked questions" left.
* "SilverStripe" should always appear without a space, use two capital S.
* Use simple language and words. Avoid uncommon jargon and overly long words.
* Use UK English and not US English. SilverStripe is proudly a New Zealand open source project we use the UK spelling and forms of English. The most common of these differences are -ize vs -ise, or -or vs our (eg color vs colour).
* We use sentence case for titles so only capitalise the first letter of the first word of a title. Only exceptions to this are when using branded (e.g. SilverStripe), acronyms (e.g. PHP) and class names (e.g. ModelAdmin).
* Use gender neutral language throughout the document, unless referencing a specific person. Use them, they, their, instead of he and she, his or her.
* URLs: is the end of your sentence is a URL, you don't need to use a full stop.
* Bullet points: Sentence case your bullet points, if it is a full sentence then end with a full stop. If it is a short point or list full stops are not required.
## Highlighted blocks
There are several built-in block styles for highlighting a paragraph of text. Please use these graphical elements
sparingly.
<div class="hint" markdown='1'>
"Tip box": Adds, deepens or accents information in the main text. Can be used for background knowledge, or "see also"
links.
</div>
Code:
<div class="hint" markdown='1'>
...
</div>
<div class="notice" markdown='1'>
"Notification box": Technical notifications relating to the main text. For example, notifying users about a deprecated
feature.
</div>
Code:
<div class="notice" markdown='1'>
...
</div>
<div class="warning" markdown='1'>
"Warning box": Highlight a severe bug or technical issue requiring a users attention. For example, a code block with
destructive functionality might not have its URL actions secured to keep the code shorter.
</div>
Code:
<div class="warning" markdown='1'>
...
</div>
See [markdown extra documentation](http://michelf.com/projects/php-markdown/extra/#html) for more restriction
on placing HTML blocks inside Markdown.
## Translating documentation
Documentation is kept alongside the source code, typically in a module subdirectory like `framework/docs/en/`. Each
language has its own subfolder, which can duplicate parts or the whole body of documentation. German documentation
would for example live in `framework/docs/de/`. The
[docsviewer](https://github.com/silverstripe/silverstripe-docsviewer) module that drives
[doc.silverstripe.org](http://doc.silverstripe.org) automatically resolves these subfolders into a language dropdown.
## Further reading
* [Writing great documentation (jacobian.org)](http://jacobian.org/writing/great-documentation/)
* [How tech writing sucks: Five sins](http://www.slash7.com/articles/2006/11/15/tech-writing-the-five-sins)
* [What is good documentation?](http://www.techscribe.co.uk/techw/whatis.htm)

View File

@ -0,0 +1,146 @@
title: Translations
summary: Translate interface components like button labels into multiple languages.
# Contributing Translations
We are always looking for new translators. Even if a specific language already is translated and has an official
maintainer, we can use helping hands in reviewing and updating translations. Important: It is perfectly fine if you
only have time for a partial translation or quick review work - our system accomodates many people collaborating on the
same language.
The content for UI elements (button labels, field titles) and instruction texts shown in the CMS and elsewhere is
stored in the PHP code for a module (see [i18n](/topics/i18n)). All content can be extracted as a "language file", and
uploaded to an online translation editor interface. SilverStripe is already translated in over 60 languages, and we're
relying on native speakers to keep these up to date, and of course add new languages.
Please register a free translator account to get started, even if you just feel like fixing up a few sentences.
## The online translation tool
We provide a GUI for translations through [transifex.com](http://transifex.com). If you don't have an account yet,
please follow the links there to sign up. Select a project from the
[list of translatable modules](https://www.transifex.com/accounts/profile/silverstripe/) and start translating online!
For all modules listed there, we automatically import new master strings as they get committed to the various code
bases (via a nightly task), so you're always translating on the latest and greatest version.
You can check the last successful push of the translation master strings in our
[public continuous integration server](http://teamcity.silverstripe.com/viewType.html?buildTypeId=bt112)
(select "log in as guest").
## FAQ
### What happened to getlocalization.com?
We migrated from getlocalization.com to transifex in mid 2013.
### How do I translate a module not listed on Transifex?
Most modules maintained by SilverStripe are on Transifex. For other modules, have a look in the module README if
there's any specific instructions. If there aren't, you'll need to translate the YAML files directly. If the module is
on github, you can create a fork, edit the files, and send back your pull request all directly on the website
([instructions](https://help.github.com/articles/fork-a-repo)).
### How do I translate substituted strings? (e.g. '%s' or '{my-variable}')
You don't have to - if the english master-string reads 'Hello %s', your german translation would be 'Hallo %s'. Strings
prefixed by a percentage-sign are automatically replaced by silverstripe with dynamic content. See
http://php.net/sprintf for details. The newer `{my-variable}` format works the same way, but makes its intent clearer,
and allows reordering of placeholders in your translation.
### Do I need to convert special characters (e.g. HTML-entities)?
Special characters (such as german umlauts) need to be entered in their native form. Please don't use HTML-entities
("ä" instead of "ä"). Silverstripe stores and renders most strings in UTF8 (Unicode) format.
### How can I check out my translation in the interface?
Currently translated entities are not directly factored into code (for security reasons and release/review-control), so
you can't see them straight away.
It is strongly encouraged that you check your translation this way, as its a good way to double check your translation
works in the right context. Please use our [daily-builds](http://www.silverstripe.org/daily-builds/) for your local
installation, to ensure you're looking at the most up to date interface. See "Download Translations" above to find out
how to retrieve the latest translation files.
### Can I change a translation just for one SilverStripe version?
While we version control our translation files like all other source code, the online translation tool doesn't have the
same capabilities. A translated string (as identified by its unique "entity name") is assumed to work well in all
releases. If the interface changes in a non-trivial fashion, the new translations required should have new identifiers
as well.
Example: We renamed the "Security" menu title to "Users" in our 3.0 release. As it would confuse users of older versions
unnecessarily, we should be using a new entity name for this, and avoid the change propagating to an older SilverStripe
version.
### How do I change my interface language?
Once you've logged into the CMS, you should see the text "Hi <your name>" near the top left, you can click this to edit
your profile ([direct link](http://localhost/admin/myprofile/)). You can then set the "interface language" from a
dropdown which automatically includes all found translations (based on the files in the `/lang` folders).
### I've found a piece of untranslatable text
It is entirely possible that we missed certain strings in preparing Silverstripe for translation-support. If you're
technically minded, please read [i18n](/topics/i18n) on how to make it translatable. Otherwise just post your findings
to the forum.
### How do I add my own module?
Once you've built a translation-enabled module, you can run the "textcollector" on your local installation for this
specific module (see [i18n](/topics/i18n)). This should find all calls to `_t()` in php and template files, and generate
a new lang file with the default locale (path: <mymodule>/lang/en.yml). Upload this file to the online translation
tool, and wait for your translators to do their magic!
### What about right-to-left (RTL) languages (e.g. Arabic)?
SilverStripe doesn't have built-in support for attribute-based RTL-modifications (`<html dir="rtl">`).
We are currently investigating the available options, and are eager to get feedback on your experiences with
translating silverstripe RTL.
### Can I translate/edit the language files in my favorite text editor (on my local installation)
Not for modules managed by transifex.com, including "framework" and "cms. It causes us a lot of work in merging these
files back. Please use the online translation tool for all new and existing translations.
### How does my translation get into a SilverStripe release?
Currently this is a manual process of a core team member downloading approved translations and committing them into our
source tree.
### How does my translation get approved, who is the maintainer?
The online translation tool (transifex.com) is designed to be decentralized and collaborative, so there's no strict
approval or review process. Every logged-in user on the system can flag translations, and discuss them with other
translators.
### I'm seeing lots of duplicated translations, what should I do?
For now, please translate all duplications - sometimes they might be intentional, but mostly the developer just didn't
know his phrase was already translated. Please contact us about any duplicates that might be worth merging.
### What happened to translate.silverstripe.org?
This was a custom-built online translation tool serving us well for a couple of years, but started to show its age
(performance and maintainability). It was replaced with transifex.com. All translations from translate.silverstripe.org
were migrated. Unfortunately, the ownership of individual translations couldn't be migrated.
As the new tool doesn't support the PHP format used in SilverStripe 2.x, this means that we no longer have a working
translation tool for PHP files. Please edit the PHP files directly and [send us pull requests](/misc/contributing).
This also applies for any modules staying compatible with SilverStripe 2.x.
## Contact
Translators have their own [mailinglist](https://groups.google.com/forum/#!forum/silverstripe-translators), but you can
also reach a core member on [IRC](http://silverstripe.org/irc). The transifex.com interface has a built-in discussion
board if you have specific comments on a translation.
## Related
* [i18n](/developer_guids/i18n): Developer-level documentation of Silverstripe's i18n capabilities
* [translation-process](translation-process): Information about managing translations for the core team and/or module maintainers.
* [translatable](https://github.com/silverstripe/silverstripe-translatable): DataObject-interface powering the website-content translations
* ["Translatable ModelAdmin" module](http://silverstripe.org/translatablemodeladmin-module/): An extension which allows translations of DataObjects inside ModelAdmin

View File

@ -0,0 +1,133 @@
title: Implement Internationalization
summary: Implement SilverStripe's internationalization system in your own modules.
# Implementing Internationalization
To find out about how to assist with translating SilverStripe from a users point of view, see the
[Contributing Translations page](translation).
## Set up your own module for localization
### Collecting translatable text
As a first step, you can automatically collect all translatable text in your module through the `i18nTextCollector`
task. See [i18n](/topics/i18n#collecting-text) for more details.
### Import master files
If you don't have an account on transifex.com yet, [create a free account now](http://www.transifex.com/signup). After
creating a new project, you have to upload the `en.yml` master file as a new "Resource". While you can do this through
the web interface, there's a convenient
[commandline client](http://support.transifex.com/customer/portal/topics/440187-transifex-client/articles) for this
purpose. In order to use it, set up a new `.tx/config` file in your module folder:
```yaml
[main]
host = https://www.transifex.com
[my-project.master]
file_filter = lang/<lang>.yml
source_file = lang/en.yml
source_lang = en
type = YML
```
If you don't have existing translations, your project is ready to go - simply point translators to the URL, have them
sign up, and they can create languages and translations as required.
### Import existing translations
In case you have existing translations in YML format, there's a "New language" option in the web interface.
Alternatively, use the [commandline client](http://support.transifex.com/customer/portal/topics/440187-transifex-client/articles).
### Export existing translations
You can download new translations in YML format through the web interface, but that can get quite tedious for more than
a handful of translations. Again, the [commandline client](http://support.transifex.com/customer/portal/topics/440187-transifex-client/articles)
provides a more convenient interface here with the `tx pull` command, downloading all translations as a batch.
### Merge back existing translations
If you want to backport translations onto release branches, simply run the `tx pull` command on multiple branches. This
assumes you're adhering to the following guidelines:
- For significantly changed content of an entity, create a new entity key
- For added/removed placeholders, create a new entity
- Run the `i18nTextCollectorTask` with the `merge=true` option to avoid deleting unused entities
(which might still be relevant in older release branches)
### Converting your language files from 2.4 PHP format
The conversion from PHP format to YML is taken care of by a module called
[i18n_yml_converter](https://github.com/chillu/i18n_yml_converter).
## Download Translations from Transifex.com
We are managing our translations through a tool called [transifex.com](http://transifex.com). Most modules are handled
under the "silverstripe" user, see
[list of translatable modules](https://www.transifex.com/accounts/profile/silverstripe/).
Translations need to be reviewed before being committed, which is a process that happens roughly once per month. We're
merging back translations into all supported release branches as well as the `master` branch. The following script
should be applied to the oldest release branch, and then merged forward into newer branches:
:::bash
tx pull
# Manually review changes through git diff, then commit
git add lang/*
git commit -m "Updated translations"
<div class="notice" markdown="1">
You can download your work right from Transifex in order to speed up the process for your desired language.
</div>
## JavaScript Translations
SilverStripe also supports translating strings in JavaScript (see [i18n](/developer_guides/i18n)), but there's a
conversion step involved in order to get those translations syncing with Transifex. Our translation files stored in
`mymodule/javascript/lang/*.js` call `ss.i18n.addDictionary()` to add files.
:::js
ss.i18n.addDictionary('de', {'MyNamespace.MyKey': 'My Translation'});
But Transifex only accepts structured formats like JSON.
```
{'MyNamespace.MyKey': 'My Translation'}
```
First of all, you need to create those source files in JSON, and store them in `mymodule/javascript/lang/src/*.js`. In your `.tx/config` you can configure this path as a separate master location.
:::ruby
[main]
host = https://www.transifex.com
[silverstripe-mymodule.master]
file_filter = lang/<lang>.yml
source_file = lang/en.yml
source_lang = en
type = YML
[silverstripe-mymodule.master-js]
file_filter = javascript/lang/src/<lang>.js
source_file = javascript/lang/src/en.js
source_lang = en
type = KEYVALUEJSON
Now you can upload the source files via a normal `tx push`. Once translations come in, you need to convert the source
files back into the JS files SilverStripe can actually read. This requires an installation of our
[buildtools](https://github.com/silverstripe/silverstripe-buildtools).
tx pull
(cd .. && phing -Dmodule=mymodule translation-generate-javascript-for-module)
git add javascript/lang/*
git commit -m "Updated javascript translations"
# Related
* [i18n](/topics/i18n): Developer-level documentation of Silverstripe's i18n capabilities
* [contributing/translation](contributing/translation): Information for translators looking to contribute translations of the SilverStripe UI.
* [translatable](https://github.com/silverstripe/silverstripe-translatable): DataObject-interface powering the website-content translations
* ["Translatable ModelAdmin" module](http://silverstripe.org/translatablemodeladmin-module/): An extension which allows translations of DataObjects inside ModelAdmin

View File

@ -1,5 +1,5 @@
# Core Commiters
The core commiters team is reviewed approximately annually, new members are added based on quality contributions to SilverStipe code and outstanding community participation.
# Core Committers
The core committers team is reviewed approximately annually, new members are added based on quality contributions to SilverStipe code and outstanding community participation.
## Core committer team
* [Damian Mooyman](https://github.com/tractorcow/)
@ -14,20 +14,20 @@ The core commiters team is reviewed approximately annually, new members are adde
* [Will Morgan](https://github.com/willmorgan)
* [Will Rossiter](https://github.com/wilr/)
## House rules for the core commiter team
## House rules for the core committer team
The "core commiters" consist of everybody with write permissions to our codebase.
The "core committers" consist of everybody with write permissions to our codebase.
With great power comes great responsibility, so we have agreed on certain expectations:
* Be friendly, encouraging and constructive towards other community members
* Frequently review pull requests and new issues (in particular, respond quickly to @mentions)
* Treat issues according to our [issue guidelines](/misc/contributing/issues)
* Treat issues according to our [issue guidelines](issues_and_bugs)
* Don't commit directly to core, raise pull requests instead (except trivial fixes)
* Only merge code you have tested and fully understand. If in doubt, ask for a second opinion.
* Ensure contributions have appropriate [test coverage](/topics/testing), are documented, and pass our [coding conventions](/misc/coding-conventions)
* Keep the codebase "releasable" at all times (check our [release process](/misc/release-process))
* Ensure contributions have appropriate [test coverage](/topics/testing), are documented, and pass our [coding conventions](/getting_started/coding-conventions)
* Keep the codebase "releasable" at all times (check our [release process](release_process))
* API changes and non-trivial features should not be merged into release branches.
* API changes on master should not be merged until they have the buy-in of at least two core committers (or better, through the [core mailinglist](https://groups.google.com/forum/#!forum/silverstripe-dev))
* API changes on master should not be merged until they have the buy-in of at least two core committers (or better, through the [core mailing list](https://groups.google.com/forum/#!forum/silverstripe-dev))
* Be inclusive. Ensure a wide range of SilverStripe developers can obtain an understanding of your code and docs, and you're not the only one who can maintain it.
* Avoid `git push --force`, and be careful with your git remotes (no accidental pushes)
* Use your own forks to create feature branches

View File

@ -0,0 +1,13 @@
title: Contributing
introduction: Any open source product is only as good as the community behind it. You can participate by sharing code, ideas, or simply helping others. No matter what your skill level is, every contribution counts.
## House rules for everybody contributing to SilverStripe
* Ask questions on the [forum](http://silverstripe.org/community/forums), and stick to more high-level discussions on the [core mailinglist](https://groups.google.com/forum/#!forum/silverstripe-dev)
* Make sure you know how to [raise good bug reports](issues_and_bugs)
* Everybody can contribute to SilverStripe! If you do, ensure you can [submit solid pull requests](code)
See our [high level overview on silverstripe.org](http://www.silverstripe.org/community/contributing-to-silverstripe/)
on how you can help out. Or, for more detailed guidance, read one of the following pages:
[CHILDREN]

62
docs/en/README.md Normal file
View File

@ -0,0 +1,62 @@
# Todo
This folder is a work in progress for the new SilverStripe.org documentation
project run by Cameron Findlay and Will Rossiter. If you want to contribute
we'd love you too so flick us a message via email or twitter.
The docsviewer module issue tracker has a set of functional requirements that
we need to make as part of this work.
At the current point, the existing docs have just been dropped into the correct
sections. Index files need to be written as well as perhaps files merged or
reworked within sections.
## How-tos
How-tos should be each of the learning categories under a `howto` folder which
is visible within the section. This separates the context of reference
documentation to more tutorial style steps.
## Review *
Below where we say 'review' this relates to writing new index folders,
organizing the existing pages into a cohesive structure, how-tos out to
individual files and rewriting documentation pages in a standard and agreed upon
language style.
We are also looking at using a consistent example across all the documentation
and releasing this code on Github so that it gives developers a great reference
of what a beautiful SilverStripe project looks like.
## Writing and Language notes
Todo
## Developer Guide notes
The developer guides are a new concept. Each guide is broken into 2 sections
- How tos (stored within a how-to folder)
- Reference documentation
How-tos should be short, sweet and full of code. The style of these is for users
to basically copy and paste to get a solution. An example of this would be
`How to add a custom action to a GridField row`.
Everything else in the developer guide should be written as a reference manual.
Each section should contain an index.md file which summaries the topic, provides
the *entry level* user an introduction guide to the feature and any background
then it can go down into more detailed explanation into detailed references.
If you cannot place a how-to within a single developer guide, that would be an
indication that it should be a tutorial rather than part of a guide. Tutorials
should cover a full working case of a problem, the thought behind the problem
and a annotated implementation. An example of a new tutorial would be
'Building a Website without the CMS'. 'Building a contact form' would still sit
under 'Forms' as while it may have templates and controllers involved, as a user
'Form' is the action word.
## The plan
See our plan and progress at https://trello.com/b/y32uSVM1/silverstripe-documentation

View File

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

View File

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 140 KiB

View File

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

View File

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 109 KiB

View File

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

View File

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

View File

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

View File

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

View File

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 170 B

After

Width:  |  Height:  |  Size: 170 B

View File

Before

Width:  |  Height:  |  Size: 782 B

After

Width:  |  Height:  |  Size: 782 B

View File

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 173 B

After

Width:  |  Height:  |  Size: 173 B

View File

Before

Width:  |  Height:  |  Size: 176 B

After

Width:  |  Height:  |  Size: 176 B

View File

Before

Width:  |  Height:  |  Size: 170 B

After

Width:  |  Height:  |  Size: 170 B

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

View File

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

View File

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 104 KiB

View File

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

View File

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

View File

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 83 KiB

View File

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 75 KiB

View File

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 120 KiB

View File

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

View File

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View File

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

View File

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

View File

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 78 KiB

View File

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View File

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 86 KiB

View File

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

View File

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

View File

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 86 KiB

View File

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 108 KiB

View File

Before

Width:  |  Height:  |  Size: 142 KiB

After

Width:  |  Height:  |  Size: 142 KiB

View File

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

View File

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 101 KiB

View File

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 109 KiB

View File

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 102 KiB

View File

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 116 KiB

View File

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 154 KiB

View File

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -1,26 +0,0 @@
# 3.0.10
## Upgrading
* If relying on partial caching of content between logged in users, be aware that the cache is now automatically
segmented based on both the current member ID, and the versioned reading mode. If this is not an appropriate
method (such as if the same content is served to logged in users within partial caching) then it is necessary
to adjust the config value of `SSViewer.global_key` to something more or less sensitive.
## Security
* [BUG Fix issue with versioned dataobjects being cached between stages](https://github.com/silverstripe/silverstripe-framework/commit/4415a75d9304a3930b9c28763fc092299640c685) - See [announcement SS-2014-007](http://www.silverstripe.org/ss-2014-007-confidentiality-breach-can-occur-between-draft-and-live-modes/)
* [BUG Fix encoding of JS redirection script](https://github.com/silverstripe/silverstripe-framework/commit/f8e3bbe3ae3f29f22d85abb73cea033659511168) - See [announcement SS-2014-006](http://www.silverstripe.org/ss-2014-006-xss-in-returnurl-redirection/)
* [Amends solution to SS-2014-006](https://github.com/silverstripe/silverstripe-framework/commit/5b0a96979484fad12e11ce69aef98feda57b321f)
* [FIX Prevent SQLi when no URL filters are applied](https://github.com/silverstripe/silverstripe-cms/commit/114df8a3a5e4800ef7586c5d9c8d79798fd2a11d) - See [announcement SS-2014-004](http://www.silverstripe.org/ss-2014-004-sql-injection-in-sitetree-with-custom-urlsegmentfilter-rules/)
* [FIX Do now allow arbitary class creation in CMS](https://github.com/silverstripe/silverstripe-cms/commit/bf9b22fd4331a6f78cec12a75262f570b025ec2d) - See [announcement SS-2014-005](http://www.silverstripe.org/ss-2014-005-arbitrary-class-creation-in-cms-backend/)
## General
* [Rewrote usages of error suppression operator](https://github.com/silverstripe/silverstripe-framework/commit/6d5d3d8cb7e69e0b37471b1e34077211b0f631fe)
## Changelog
* [framework](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.0.10)
* [cms](https://github.com/silverstripe/silverstripe-cms/releases/tag/3.0.10)
* [installer](https://github.com/silverstripe/silverstripe-installer/releases/tag/3.0.10)

View File

@ -1,19 +0,0 @@
# 3.0.11
Minor security release
## Security
* 2014-04-16 [9d74bc4](https://github.com/silverstripe/sapphire/commit/9d74bc4) Potential DoS exploit in TinyMCE - See [announcement SS-2014-009](http://www.silverstripe.org/ss-2014-009-potential-dos-exploit-in-tinymce/)
* 2014-05-05 [9bfeffd](https://github.com/silverstripe/silverstripe-framework/commit/9bfeffd) Injection / Filesystem vulnerability in generatesecuretoken - See [announcement SS-2014-010](http://www.silverstripe.org/ss-2014-010-injection-filesystem-vulnerability-in-generatesecuretoken/)
* 2014-05-07 [0099a18](https://github.com/silverstripe/silverstripe-framework/commit/0099a18) Folder filename injection - See [announcement SS-2014-011](http://www.silverstripe.org/ss-2014-011-folder-filename-injection/)
### Bugfixes
* 2013-06-20 [f2c4a62](https://github.com/silverstripe/sapphire/commit/f2c4a62) ConfirmedPasswordField used to expose existing hash (Hamish Friedlander)
## Changelog
* [framework](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.0.11)
* [cms](https://github.com/silverstripe/silverstripe-cms/releases/tag/3.0.11)
* [installer](https://github.com/silverstripe/silverstripe-installer/releases/tag/3.0.11)

View File

@ -1,12 +0,0 @@
# 3.0.9
## Overview
* Security: Require ADMIN for ?flush=1&isDev=1 ([SS-2014-001](http://www.silverstripe.org/ss-2014-001-require-admin-for-flush1-and-isdev1))
* Security: XSS in third party library (SWFUpload) ([SS-2014-002](http://www.silverstripe.org/ss-2014-002-xss-in-third-party-library-swfupload/))
## Changelog
* [framework](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.0.9)
* [cms](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.0.9)
* [installer](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.0.9)

View File

@ -1,29 +0,0 @@
# 3.1.3
## Overview
* Security: Require ADMIN for ?flush=1&isDev=1 ([SS-2014-001](http://www.silverstripe.org/ss-2014-001-require-admin-for-flush1-and-isdev1))
* Security: XSS in third party library (SWFUpload) ([SS-2014-002](http://www.silverstripe.org/ss-2014-002-xss-in-third-party-library-swfupload/))
* Security: SiteTree.ExtraMeta allows JavaScript for malicious CMS authors ([SS-2014-003](http://www.silverstripe.org/ss-2014-003-extrameta-allows-javascript-for-malicious-cms-authors-/))
* Better loading performance when using multiple `UploadField` instances
* Option for `force_js_to_bottom` on `Requirements` class (ignoring inline `<script>` tags)
* Added `ListDecorator->filterByCallback()` for more sophisticated filtering
* New `DataList` filters: `LessThanOrEqualFilter` and `GreaterThanOrEqualFilter`
* "Cancel" button on "Add Page" form
* Better code hinting on magic properties (for IDE autocompletion)
* Increased Behat test coverage (editing HTML content, managing page permissions)
* Support for PHPUnit 3.8
## Upgrading
### SiteTree.ExtraMeta allows JavaScript for malicious CMS authors
If you have previously used the `SiteTree.ExtraMeta` field for `<head>` markup
other than its intended use case (`<meta>` and `<link>`), please consult
[SS-2014-003](http://www.silverstripe.org/ss-2014-003-extrameta-allows-javascript-for-malicious-cms-authors-/).
## Changelog
* [framework](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.1.3)
* [cms](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.1.3)
* [installer](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.1.3)

View File

@ -1,46 +0,0 @@
# 3.1.4
## Upgrading
* If relying on partial caching of content between logged in users, be aware that the cache is now automatically
segmented based on both the current member ID, and the versioned reading mode. If this is not an appropriate
method (such as if the same content is served to logged in users within partial caching) then it is necessary
to adjust the config value of `SSViewer.global_key` to something more or less sensitive.
## Security
* [BUG Fix issue with versioned dataobjects being cached between stages](https://github.com/silverstripe/silverstripe-framework/commit/4415a75d9304a3930b9c28763fc092299640c685) - See [announcement SS-2014-007](http://www.silverstripe.org/ss-2014-007-confidentiality-breach-can-occur-between-draft-and-live-modes/)
* [BUG Fix encoding of JS redirection script](https://github.com/silverstripe/silverstripe-framework/commit/f8e3bbe3ae3f29f22d85abb73cea033659511168) - See [announcement SS-2014-006](http://www.silverstripe.org/ss-2014-006-xss-in-returnurl-redirection/)
* [Amends solution to SS-2014-006](https://github.com/silverstripe/silverstripe-framework/commit/5b0a96979484fad12e11ce69aef98feda57b321f)
* [FIX Prevent SQLi when no URL filters are applied](https://github.com/silverstripe/silverstripe-cms/commit/114df8a3a5e4800ef7586c5d9c8d79798fd2a11d) - See [announcement SS-2014-004](http://www.silverstripe.org/ss-2014-004-sql-injection-in-sitetree-with-custom-urlsegmentfilter-rules/)
* [FIX Do now allow arbitary class creation in CMS](https://github.com/silverstripe/silverstripe-cms/commit/bf9b22fd4331a6f78cec12a75262f570b025ec2d) - See [announcement SS-2014-005](http://www.silverstripe.org/ss-2014-005-arbitrary-class-creation-in-cms-backend/)
## Bugfixes
* [Fix Versioned::augmentSQL() when the data query was null.](https://github.com/silverstripe/silverstripe-framework/commit/deb1bfbcbaaa62acb2263ba797b5068e142a6353)
* [FIX UploadField validation error and styles](https://github.com/silverstripe/silverstripe-framework/commit/02bceca9b478358bdd569c16818d3be2467beb64)
* [FIX Overriding of theme templates in project folder](https://github.com/silverstripe/silverstripe-framework/commit/5f87d344f11c382dbee3fae8edfc00bb9a5a0265)
* [BUG Ensure TreeMultiSelectField doesn't populate menus with "unchanged".](https://github.com/silverstripe/silverstripe-framework/commit/9e2c7b657221c336137e07985bd5994682216d65)
* [BUG: #2503 Fixes performReadonlyTransformation for OptionSetField](https://github.com/silverstripe/silverstripe-framework/commit/44a8537f68872f0587cdf4cceadd433817dfdf60)
* [FIX: Rewrite Member getCMSFields to ensure updateCMSFields is only run once](https://github.com/silverstripe/silverstripe-framework/commit/d91c7d14b84d8b3caed948b0bbab94d254ea2b96)
* [FIX: Ensure valid CSS classes for GridField header](https://github.com/silverstripe/silverstripe-framework/commit/90952e7bd4bf7a278959ff320b3a71d30596f5d8)
* [BUG Fix case where setFolder('/') would break UploadField::fileexists](https://github.com/silverstripe/silverstripe-framework/commit/c1e0f98f87fa58edf7967d818732c7467cf47d80)
* [BUG Prevent unnecessary reconstruction of ClassName field after default records are generated](https://github.com/silverstripe/silverstripe-framework/commit/53b5adbcd98ff4d0e3947f4472b7b7b62a2b064a)
* [BUG Fix DataObject::loadLazyFields discarding original query parameters](https://github.com/silverstripe/silverstripe-framework/commit/23f5f08eda4201e0d3d4c28b81805da10b55bdb1)
* [Upload: retrieve existing File if an object without an ID is given and replaceFile=true](https://github.com/silverstripe/silverstripe-framework/commit/3c1e82b42c282ab64dfe7f5a68a50f59d8ebcc69)
* [BUG Fix Date and SS_DateTime::FormatFromSettings](https://github.com/silverstripe/silverstripe-framework/commit/84d8022b326e3938753430678cfc3dfa50770d83)
## API
* [Add support for many_many_extraField in YAML](https://github.com/silverstripe/silverstripe-framework/commit/8b923006227b0177983c96b949edaa6df18fbbf8)
* [Allow vetoing forgot password requests](https://github.com/silverstripe/silverstripe-framework/commit/9afcf8f01ac6b5c3c054b9a49f1731d35aa868ed)
## General
* [Rewrote usages of error suppression operator](https://github.com/silverstripe/silverstripe-framework/commit/6d5d3d8cb7e69e0b37471b1e34077211b0f631fe)
## Changelog
* [framework](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.1.4)
* [cms](https://github.com/silverstripe/silverstripe-cms/releases/tag/3.1.4)
* [installer](https://github.com/silverstripe/silverstripe-installer/releases/tag/3.1.4)

View File

@ -1,67 +0,0 @@
# 3.1.5
## Upgrading
* If running an application in an environment where user security is critical, it may be necessary to
assign the config value `Security.remember_username` to false. This will disable persistence of
user login name between sessions, and disable browser auto-completion on the username field.
Note that users of certain browsers who have previously autofilled and saved login credentials
will need to clear their password autofill history before this setting is properly respected.
* Test cases that rely on updating and restoring `[api:Injector]` services may now take advantage
of the new `Injector::nest()` and `Injector::unnest()` methods to sandbox their alterations.
* If errors could potentially be raised by any `[api:RequestHandler]` class such as a `[api:Form]` or
`[api:Controller]`, you may now add the new `[api:ErrorPageControllerExtension]` to this class to
transform plain text error messages into `ErrorPage` rendered HTML errors. In the past this
behaviour was limited to subclasses of `[api:ContentController]`. By default this extension is now
added to the `Security` controller, and if this is not desirable then it should be removed
explicitly via the Config system.
## Security
* 2014-04-16 [bde16f0](https://github.com/silverstripe/sapphire/commit/bde16f0) Potential DoS exploit in TinyMCE - See [announcement SS-2014-009](http://www.silverstripe.org/ss-2014-009-potential-dos-exploit-in-tinymce/)
* 2014-05-05 [d9bc352](https://github.com/silverstripe/silverstripe-framework/commit/d9bc352) Injection / Filesystem vulnerability in generatesecuretoken - See [announcement SS-2014-010](http://www.silverstripe.org/ss-2014-010-injection-filesystem-vulnerability-in-generatesecuretoken/)
* 2014-05-02 [8e841cc](https://github.com/silverstripe/sapphire/commit/8e841cc) Folder filename injection - See [announcement SS-2014-011](http://www.silverstripe.org/ss-2014-011-folder-filename-injection/)
* 2014-05-05 [df28ccb](https://github.com/silverstripe/sapphire/commit/df28ccb) Upload fileexists vulnerability - See [announcement SS-2014-013](http://www.silverstripe.org/ss-2014-013-upload-fileexists-vulnerability/)
### API Changes
* 2014-05-02 [f9cb880](https://github.com/silverstripe/silverstripe-cms/commit/f9cb880) Error page support for Security controller errors (Damian Mooyman)
* 2014-05-01 [3162d0e](https://github.com/silverstripe/silverstripe-cms/commit/3162d0e) Update ErrorPage to respect new HTTP Error codes (Damian Mooyman)
* 2014-04-28 [0285322](https://github.com/silverstripe/silverstripe-cms/commit/0285322) Ability to configure paging for assets / pages (Damian Mooyman)
* 2014-04-22 [d06d5c1](https://github.com/silverstripe/sapphire/commit/d06d5c1) Injector supports nesting BUG Resolve issue with DirectorTest breaking RequestProcessor Injector::nest and Injector::unnest are introduced to better support sandboxing of testings. Injector and Config ::nest and ::unnest support chaining Test cases for both Injector::nest and Config::nest (Damian Mooyman)
* 2014-04-17 [a6017a0](https://github.com/silverstripe/sapphire/commit/a6017a0) HTTP 429 Allowed for use with rate limiting methods (Damian Mooyman)
* 2014-04-11 [892b440](https://github.com/silverstripe/sapphire/commit/892b440) Make default gridfield paging configurable Documentation improved (Damian Mooyman)
* 2014-04-09 [997077a](https://github.com/silverstripe/sapphire/commit/997077a) Security.remember_username to disable login form autocompletion (Damian Mooyman)
### Features and Enhancements
* 2014-03-28 [a502c9d](https://github.com/silverstripe/silverstripe-cms/commit/a502c9d) Fixes #966. Ability to filter pages on page status. - New filters for statuses normally found through SiteTree::getStatusFlags(). - Refactored menu sorting. Now alphabetical, as it wasn't previously. (Russell Michell)
* 2014-04-11 [3765030](https://github.com/silverstripe/silverstripe-cms/commit/3765030) Filter by date created for files Added test cases Do not merge before https://github.com/silverstripe-labs/silverstripe-behat-extension/pull/32 (Damian Mooyman)
### Bugfixes
* 2014-05-05 [c5d5d10](https://github.com/silverstripe/silverstripe-cms/commit/c5d5d10) Behat now uses explicit radio button behaviour (Damian Mooyman)
* 2014-05-01 [bd5abb6](https://github.com/silverstripe/sapphire/commit/bd5abb6) parent::init is not called first (Michael Parkhill)
* 2014-05-01 [4fd3015](https://github.com/silverstripe/sapphire/commit/4fd3015) corrected link to CMS Alternating Button Page (James Pluck)
* 2014-04-29 [8673b11](https://github.com/silverstripe/sapphire/commit/8673b11) Fix ImageTest Image test would erroneously reset the Image::$backend to null if the test was skipped, breaking subsequent test cases (Damian Mooyman)
* 2014-04-29 [89fbae2](https://github.com/silverstripe/silverstripe-cms/commit/89fbae2) Fix encoding of SiteTree.MetaTags (Damian Mooyman)
* 2014-04-25 [ff5f607](https://github.com/silverstripe/sapphire/commit/ff5f607) Docs for DataList::filter() (Daniel Hensby)
* 2014-04-24 [5e9ae57](https://github.com/silverstripe/sapphire/commit/5e9ae57) Fix edge case IE8 / dev / ssl / download file crash Prevents issue at http://support.microsoft.com/kb/323308 appearing on dev (Damian Mooyman)
* 2014-04-17 [bec8927](https://github.com/silverstripe/sapphire/commit/bec8927) Allow PHPUnit installation with composer / Fix travis (Will Morgan)
* 2014-04-16 [396fd9a](https://github.com/silverstripe/silverstripe-cms/commit/396fd9a) Broken file link tracking (fixes #996) (Loz Calver)
* 2014-04-14 [0b4f62d](https://github.com/silverstripe/sapphire/commit/0b4f62d) Fix jstree when duplicating subtrees (Damian Mooyman)
* 2014-04-11 [a261f22](https://github.com/silverstripe/sapphire/commit/a261f22) Delete Character \x01 (Stevie Mayhew)
* 2014-04-09 [91034d1](https://github.com/silverstripe/sapphire/commit/91034d1) HTMLText whitelist considers text nodes Minor improvement to #2853. If a list of whitelisted elements are specified, text nodes no longer evade the whitelist (Damian Mooyman)
* 2014-04-09 [a3c8a59](https://github.com/silverstripe/sapphire/commit/a3c8a59) Fix data query not always joining necessary tables Fixes #2846 (Damian Mooyman)
* 2014-04-08 [a060784](https://github.com/silverstripe/sapphire/commit/a060784) - missing link url for composer (camfindlay)
* 2014-04-07 [3204ab5](https://github.com/silverstripe/silverstripe-cms/commit/3204ab5) Fix orphaned pages reporting they can be viewed (Damian Mooyman)
* 2014-04-01 [84d8022](https://github.com/silverstripe/sapphire/commit/84d8022) Fix Date and SS_DateTime::FormatFromSettings This issue is caused by the odd default behaviour of Zend_Date, which attempts to parse yyyy-mm-dd format date and times as though they were yyyy-dd-mm. (Damian Mooyman)
* 2014-03-12 [b4a1aa4](https://github.com/silverstripe/silverstripe-cms/commit/b4a1aa4) Fixes #965. Allow user date-settings to show on GridField Page admin (Russell Michell)
* 2014-03-04 [ae573f8](https://github.com/silverstripe/sapphire/commit/ae573f8) Fix Versioned stage not persisting in Session. Fixes #962 BUG Disabled disruptive test case in DirectorTest API RequestProcessor and VersionedRequestFilter now both correctly implement RequestFilter Better PHPDoc on RequestFilter and implementations (Damian Mooyman)
* 2013-06-20 [f2c4a62](https://github.com/silverstripe/sapphire/commit/f2c4a62) ConfirmedPasswordField used to expose existing hash (Hamish Friedlander)
## Changelog
* [framework](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.1.5)
* [cms](https://github.com/silverstripe/silverstripe-cms/releases/tag/3.1.5)
* [installer](https://github.com/silverstripe/silverstripe-installer/releases/tag/3.1.5)

View File

@ -1,132 +0,0 @@
# Changelogs
Keep up to date with new releases subscribe to the [SilverStripe Release Announcements](https://groups.google.com/group/silverstripe-announce) group,
or read our [blog posts about releases](http://silverstripe.org/blog/tag/release).
We also keep an overview of [security-related releases](http://silverstripe.org/security-releases/).
For information on how to upgrade to newer versions consult the [upgrading](/installation/upgrading) guide.
## Stable Releases
* [3.1.8](3.1.8) - 18 November 2014
* [3.1.7](3.1.7) - 14 November 2014
* [3.1.6](3.1.6) - 25 August 2014
* [3.1.5](3.1.5) - 13 May 2014
* [3.1.4](3.1.4) - 8 April 2014
* [3.1.0](3.1.0) - 1 October 2013
* [3.0.11](3.0.11) - 13 May 2014
* [3.0.10](3.0.10) - 8 April 2014
* [3.0.5](3.0.5) - 20 February 2013
* [3.0.4](3.0.4) - 19 February 2013
* [3.0.3](3.0.3) - 26 November 2012
* [3.0.2](3.0.2) - 17 September 2012
* [3.0.1](3.0.1) - 31 July 2012
* [3.0.0](3.0.0) - 28 June 2012
* [2.4.7](2.4.7) - 1 February 2012
* [2.4.6](2.4.6) - 18 October 2011
* [2.4.10](2.4.10) - 2013-02-19
* [2.4.9](2.4.9) - 2012-12-04
* [2.4.8](2.4.8) - 2012-10-30
* [2.4.7](2.4.7) - 2012-02-01
* [2.4.6](2.4.6) - 2011-10-17
* [2.4.5](2.4.5) - 2 February 2011
* [2.4.4](2.4.4) - 21 December 2010
* [2.4.3](2.4.3) - 11 November 2010
* [2.4.2](2.4.2) - 22 September 2010
* [2.4.1](2.4.1) - 23 July 2010
* [2.4.0](2.4.0)
* [2.3.13](2.3.13) - 1 February 2012
* [2.3.12](2.3.12) - 17 October 2011
* [2.3.11](2.3.11) - 2 February 2011
* [2.3.10](2.3.10) - 21 December 2010
* [2.3.9](2.3.9) - 11 November 2010
* [2.3.8](2.3.8) - 23 July 2010
* [2.3.7](2.3.7) - 18 March 2010
* [2.3.6](2.3.6) - 8 February 2010
* [2.3.5](2.3.5) - 21 January 2010
* [2.3.4](2.3.4) - 27 November 2009
* [2.3.3](2.3.3) - 3 August 2009
* [2.3.2](2.3.2) - 18 June 2009
* [2.3.1](2.3.1) - 19 March 2009
* [2.3.0](2.3.0) - 23 February 2009
* [2.2.4](2.2.4) - 20 March 2009
* [2.2.3](2.2.3) - ~31 October 2008
* [2.2.2](2.2.2) - 22 May 2008
* [2.2.1](2.2.1) - 21 December 2007
* [2.2.0](2.2.0) - 28 November 2007
* [2.1.1](2.1.1) - 2 November 2007
* [2.1.0](2.1.0) - 2 October 2007
* [2.0.2](2.0.2) - 14 July 2007
* [2.0.1](2.0.1) - 17 April 2007
* 2.0.0 - 3 February 2007 (initial release)
## Alpha/beta/release candidate
* [3.1.7-rc1](rc/3.1.7-rc1) - 8 November 2014
* [3.1.6-rc3](rc/3.1.6-rc3) - 18 August 2014
* [3.1.6-rc2](rc/3.1.6-rc2) - 12 August 2014
* [3.1.6-rc1](rc/3.1.6-rc1) - 5 August 2014
* [3.1.5-rc1](rc/3.1.5-rc1) - 7 May 2014
* [3.1.4-rc1](rc/3.1.4-rc1) - 1 April 2014
* [3.0.11-rc1](rc/3.0.11-rc1) - 7 May 2014
* [3.0.10-rc1](rc/3.0.10-rc1) - 1 April 2014
* [3.0.6-rc1](rc/3.0.6-rc1) - 2013-08-08
* [3.0.3-rc1](rc/3.0.3-rc1) - 6 November 2012
* [3.0.2-rc2](rc/3.0.2-rc2) - 12 September 2012
* [3.0.2-rc1](rc/3.0.2-rc1) - 5 September 2012
* [3.0.0-rc3](rc/3.0.0-rc3) - 27 June 2012
* [3.0.0-rc2](rc/3.0.0-rc2) - 26 June 2012
* [3.0.0-rc1](rc/3.0.0-rc1) - 18 June 2012
* [3.0.0-beta3](beta/3.0.0-beta3) - 28 May 2012
* [3.0.0-beta2](beta/3.0.0-beta2) - 20 April 2012
* [3.0.0-beta1](beta/3.0.0-beta1) - 12 March 2012
* [3.0.0-alpha2](alpha/3.0.0-alpha2) - 12 January 2012
* [3.0.0-alpha1](alpha/3.0.0-alpha1) - 1 November 2011
* [3.0.0-pr1](pr/3.0.0-pr1) - 2 May 2011
* [2.4.5-rc1](rc/2.4.5-rc1) - 31 January 2011
* [2.4.4-rc2](rc/2.4.4-rc2) - 20 December 2010
* [2.4.4-rc1](rc/2.4.4-rc1) - 10 December 2010
* [2.4.3-rc2](rc/2.4.3-rc2) - 4 November 2010
* [2.4.3-rc1](rc/2.4.3-rc1) - 1 November 2010
* [2.4.2-rc2](rc/2.4.2-rc2) - 20 September 2010
* [2.4.2-rc1](rc/2.4.2-rc1) - 16 September 2010
* [2.4.1-rc2](rc/2.4.1-rc2) - 21 July 2010
* [2.4.1-rc1](rc/2.4.1-rc1) - 16 July 2010
* [2.4.0-rc3](rc/2.4.0-rc3) - 4 May 2010
* [2.4.0-rc2](rc/2.4.0-rc2) - 30 April 2010
* [2.4.0-rc1](rc/2.4.0-rc1) - 1st April 2010
* [2.4.0-beta2](beta/2.4.0-beta2) - 17 March 2010
* [2.4.0-beta1](beta/2.4.0-beta1) - 29 January 2010
* [2.4.0-alpha1](alpha/2.4.0-alpha1) - 11 November 2009
* [2.3.11-rc1](rc/2.3.11-rc1) - 31 January 2011
* [2.3.10-rc2](rc/2.3.10-rc2) - 20 December 2010
* [2.3.10-rc1](rc/2.3.10-rc1) - 10 December 2010
* [2.3.9-rc2](rc/2.3.9-rc2) - 4 November 2010
* [2.3.9-rc1](rc/2.3.9-rc1) - 1 November 2010
* [2.3.8-rc1](rc/2.3.8-rc1) - 16 July 2010

View File

@ -1,25 +0,0 @@
# 3.0.10-rc1
## Upgrading
* If relying on partial caching of content between logged in users, be aware that the cache is now automatically
segmented based on both the current member ID, and the versioned reading mode. If this is not an appropriate
method (such as if the same content is served to logged in users within partial caching) then it is necessary
to adjust the config value of `SSViewer.global_key` to something more or less sensitive.
## Security
* [BUG Fix issue with versioned dataobjects being cached between stages](https://github.com/silverstripe/silverstripe-framework/commit/4415a75d9304a3930b9c28763fc092299640c685) - See [announcement SS-2014-007](http://www.silverstripe.org/ss-2014-007-confidentiality-breach-can-occur-between-draft-and-live-modes/)
* [BUG Fix encoding of JS redirection script](https://github.com/silverstripe/silverstripe-framework/commit/f8e3bbe3ae3f29f22d85abb73cea033659511168) - See [announcement SS-2014-006](http://www.silverstripe.org/ss-2014-006-xss-in-returnurl-redirection/)
* [FIX Prevent SQLi when no URL filters are applied](https://github.com/silverstripe/silverstripe-cms/commit/114df8a3a5e4800ef7586c5d9c8d79798fd2a11d) - See [announcement SS-2014-004](http://www.silverstripe.org/ss-2014-004-sql-injection-in-sitetree-with-custom-urlsegmentfilter-rules/)
* [FIX Do now allow arbitary class creation in CMS](https://github.com/silverstripe/silverstripe-cms/commit/bf9b22fd4331a6f78cec12a75262f570b025ec2d) - See [announcement SS-2014-005](http://www.silverstripe.org/ss-2014-005-arbitrary-class-creation-in-cms-backend/)
## General
* [Rewrote usages of error suppression operator](https://github.com/silverstripe/silverstripe-framework/commit/6d5d3d8cb7e69e0b37471b1e34077211b0f631fe)
## Changelog
* [framework](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.0.10-rc1)
* [cms](https://github.com/silverstripe/silverstripe-cms/releases/tag/3.0.10-rc1)
* [installer](https://github.com/silverstripe/silverstripe-installer/releases/tag/3.0.10-rc1)

View File

@ -1,19 +0,0 @@
# 3.0.11-rc1
Minor security release
## Security
* 2014-04-16 [9d74bc4](https://github.com/silverstripe/sapphire/commit/9d74bc4) Potential DoS exploit in TinyMCE - See [announcement SS-2014-009](http://www.silverstripe.org/ss-2014-009-potential-dos-exploit-in-tinymce/)
* 2014-05-05 [9bfeffd](https://github.com/silverstripe/silverstripe-framework/commit/9bfeffd) Injection / Filesystem vulnerability in generatesecuretoken - See [announcement SS-2014-010](http://www.silverstripe.org/ss-2014-010-injection-filesystem-vulnerability-in-generatesecuretoken/)
* 2014-05-07 [0099a18](https://github.com/silverstripe/silverstripe-framework/commit/0099a18) Folder filename injection - See [announcement SS-2014-011](http://www.silverstripe.org/ss-2014-011-folder-filename-injection/)
### Bugfixes
* 2013-06-20 [f2c4a62](https://github.com/silverstripe/sapphire/commit/f2c4a62) ConfirmedPasswordField used to expose existing hash (Hamish Friedlander)
## Changelog
* [framework](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.0.11-rc1)
* [cms](https://github.com/silverstripe/silverstripe-cms/releases/tag/3.0.11-rc1)
* [installer](https://github.com/silverstripe/silverstripe-installer/releases/tag/3.0.11-rc1)

View File

@ -1,12 +0,0 @@
# 3.0.9-rc1 (2014-02-19)
## Overview
* Security: Require ADMIN for ?flush=1&isDev=1 ([SS-2014-001](http://www.silverstripe.org/ss-2014-001-require-admin-for-flush1-and-isdev1))
* Security: XSS in third party library (SWFUpload) ([SS-2014-002](http://www.silverstripe.org/ss-2014-002-xss-in-third-party-library-swfupload/))
## Changelog
* [framework](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.0.9-rc1)
* [cms](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.0.9-rc1)
* [installer](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.0.9-rc1)

View File

@ -1,29 +0,0 @@
# 3.1.3-rc1
## Overview
* Security: Require ADMIN for ?flush=1&isDev=1 ([SS-2014-001](http://www.silverstripe.org/ss-2014-001-require-admin-for-flush1-and-isdev1))
* Security: XSS in third party library (SWFUpload) ([SS-2014-002](http://www.silverstripe.org/ss-2014-002-xss-in-third-party-library-swfupload/))
* Security: SiteTree.ExtraMeta allows JavaScript for malicious CMS authors ([SS-2014-003](http://www.silverstripe.org/ss-2014-003-extrameta-allows-javascript-for-malicious-cms-authors-/))
* Better loading performance when using multiple `UploadField` instances
* Option for `force_js_to_bottom` on `Requirements` class (ignoring inline `<script>` tags)
* Added `ListDecorator->filterByCallback()` for more sophisticated filtering
* New `DataList` filters: `LessThanOrEqualFilter` and `GreaterThanOrEqualFilter`
* "Cancel" button on "Add Page" form
* Better code hinting on magic properties (for IDE autocompletion)
* Increased Behat test coverage (editing HTML content, managing page permissions)
* Support for PHPUnit 3.8
## Upgrading
### SiteTree.ExtraMeta allows JavaScript for malicious CMS authors
If you have previously used the `SiteTree.ExtraMeta` field for `<head>` markup
other than its intended use case (`<meta>` and `<link>`), please consult
[SS-2014-003](http://www.silverstripe.org/ss-2014-003-extrameta-allows-javascript-for-malicious-cms-authors-/).
## Changelog
* [framework](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.1.3-rc1)
* [cms](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.1.3-rc1)
* [installer](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.1.3-rc1)

View File

@ -1,12 +0,0 @@
# 3.1.3-rc2
# Overview
* Fixed regression around CMS loading in IE8
* Fixed regression in folder creation on upload
### Bugfixes
* 2014-02-20 [ebeb663](https://github.com/silverstripe/sapphire/commit/ebeb663) Fixed critical issue with Folder::find_or_make failing to handle invalid filename characters BUG Fix UploadField duplicate checking with invalid folderName (Damian Mooyman)
* 2014-02-19 [a681bd7](https://github.com/silverstripe/sapphire/commit/a681bd7) IE8 support in jquery.ondemand.js (fixes #2872) (Loz Calver)

View File

@ -1,44 +0,0 @@
# 3.1.4-rc1
## Upgrading
* If relying on partial caching of content between logged in users, be aware that the cache is now automatically
segmented based on both the current member ID, and the versioned reading mode. If this is not an appropriate
method (such as if the same content is served to logged in users within partial caching) then it is necessary
to adjust the config value of `SSViewer.global_key` to something more or less sensitive.
## Security
* [BUG Fix issue with versioned dataobjects being cached between stages](https://github.com/silverstripe/silverstripe-framework/commit/4415a75d9304a3930b9c28763fc092299640c685) - See [announcement SS-2014-007](http://www.silverstripe.org/ss-2014-007-confidentiality-breach-can-occur-between-draft-and-live-modes/)
* [BUG Fix encoding of JS redirection script](https://github.com/silverstripe/silverstripe-framework/commit/f8e3bbe3ae3f29f22d85abb73cea033659511168) - See [announcement SS-2014-006](http://www.silverstripe.org/ss-2014-006-xss-in-returnurl-redirection/)
* [FIX Prevent SQLi when no URL filters are applied](https://github.com/silverstripe/silverstripe-cms/commit/114df8a3a5e4800ef7586c5d9c8d79798fd2a11d) - See [announcement SS-2014-004](http://www.silverstripe.org/ss-2014-004-sql-injection-in-sitetree-with-custom-urlsegmentfilter-rules/)
* [FIX Do now allow arbitary class creation in CMS](https://github.com/silverstripe/silverstripe-cms/commit/bf9b22fd4331a6f78cec12a75262f570b025ec2d) - See [announcement SS-2014-005](http://www.silverstripe.org/ss-2014-005-arbitrary-class-creation-in-cms-backend/)
## Bugfixes
* [Fix Versioned::augmentSQL() when the data query was null.](https://github.com/silverstripe/silverstripe-framework/commit/deb1bfbcbaaa62acb2263ba797b5068e142a6353)
* [FIX UploadField validation error and styles](https://github.com/silverstripe/silverstripe-framework/commit/02bceca9b478358bdd569c16818d3be2467beb64)
* [FIX Overriding of theme templates in project folder](https://github.com/silverstripe/silverstripe-framework/commit/5f87d344f11c382dbee3fae8edfc00bb9a5a0265)
* [BUG Ensure TreeMultiSelectField doesn't populate menus with "unchanged".](https://github.com/silverstripe/silverstripe-framework/commit/9e2c7b657221c336137e07985bd5994682216d65)
* [BUG: #2503 Fixes performReadonlyTransformation for OptionSetField](https://github.com/silverstripe/silverstripe-framework/commit/44a8537f68872f0587cdf4cceadd433817dfdf60)
* [FIX: Rewrite Member getCMSFields to ensure updateCMSFields is only run once](https://github.com/silverstripe/silverstripe-framework/commit/d91c7d14b84d8b3caed948b0bbab94d254ea2b96)
* [FIX: Ensure valid CSS classes for GridField header](https://github.com/silverstripe/silverstripe-framework/commit/90952e7bd4bf7a278959ff320b3a71d30596f5d8)
* [BUG Fix case where setFolder('/') would break UploadField::fileexists](https://github.com/silverstripe/silverstripe-framework/commit/c1e0f98f87fa58edf7967d818732c7467cf47d80)
* [BUG Prevent unnecessary reconstruction of ClassName field after default records are generated](https://github.com/silverstripe/silverstripe-framework/commit/53b5adbcd98ff4d0e3947f4472b7b7b62a2b064a)
* [BUG Fix DataObject::loadLazyFields discarding original query parameters](https://github.com/silverstripe/silverstripe-framework/commit/23f5f08eda4201e0d3d4c28b81805da10b55bdb1)
* [Upload: retrieve existing File if an object without an ID is given and replaceFile=true](https://github.com/silverstripe/silverstripe-framework/commit/3c1e82b42c282ab64dfe7f5a68a50f59d8ebcc69)
## API
* [Add support for many_many_extraField in YAML](https://github.com/silverstripe/silverstripe-framework/commit/8b923006227b0177983c96b949edaa6df18fbbf8)
* [Allow vetoing forgot password requests](https://github.com/silverstripe/silverstripe-framework/commit/9afcf8f01ac6b5c3c054b9a49f1731d35aa868ed)
## General
* [Rewrote usages of error suppression operator](https://github.com/silverstripe/silverstripe-framework/commit/6d5d3d8cb7e69e0b37471b1e34077211b0f631fe)
## Changelog
* [framework](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.1.4-rc1)
* [cms](https://github.com/silverstripe/silverstripe-cms/releases/tag/3.1.4-rc1)
* [installer](https://github.com/silverstripe/silverstripe-installer/releases/tag/3.1.4-rc1)

View File

@ -1,67 +0,0 @@
# 3.1.5-rc1
## Upgrading
* If running an application in an environment where user security is critical, it may be necessary to
assign the config value `Security.remember_username` to false. This will disable persistence of
user login name between sessions, and disable browser auto-completion on the username field.
Note that users of certain browsers who have previously autofilled and saved login credentials
will need to clear their password autofill history before this setting is properly respected.
* Test cases that rely on updating and restoring `[api:Injector]` services may now take advantage
of the new `Injector::nest()` and `Injector::unnest()` methods to sandbox their alterations.
* If errors could potentially be raised by any `[api:RequestHandler]` class such as a `[api:Form]` or
`[api:Controller]`, you may now add the new `[api:ErrorPageControllerExtension]` to this class to
transform plain text error messages into `ErrorPage` rendered HTML errors. In the past this
behaviour was limited to subclasses of `[api:ContentController]`. By default this extension is now
added to the `Security` controller, and if this is not desirable then it should be removed
explicitly via the Config system.
## Security
* 2014-04-16 [bde16f0](https://github.com/silverstripe/sapphire/commit/bde16f0) Potential DoS exploit in TinyMCE - See [announcement SS-2014-009](http://www.silverstripe.org/ss-2014-009-potential-dos-exploit-in-tinymce/)
* 2014-05-05 [d9bc352](https://github.com/silverstripe/silverstripe-framework/commit/d9bc352) Injection / Filesystem vulnerability in generatesecuretoken - See [announcement SS-2014-010](http://www.silverstripe.org/ss-2014-010-injection-filesystem-vulnerability-in-generatesecuretoken/)
* 2014-05-02 [8e841cc](https://github.com/silverstripe/sapphire/commit/8e841cc) Folder filename injection - See [announcement SS-2014-011](http://www.silverstripe.org/ss-2014-011-folder-filename-injection/)
* 2014-05-05 [df28ccb](https://github.com/silverstripe/sapphire/commit/df28ccb) Upload fileexists vulnerability - See [announcement SS-2014-013](http://www.silverstripe.org/ss-2014-013-upload-fileexists-vulnerability/)
### API Changes
* 2014-05-02 [f9cb880](https://github.com/silverstripe/silverstripe-cms/commit/f9cb880) Error page support for Security controller errors (Damian Mooyman)
* 2014-05-01 [3162d0e](https://github.com/silverstripe/silverstripe-cms/commit/3162d0e) Update ErrorPage to respect new HTTP Error codes (Damian Mooyman)
* 2014-04-28 [0285322](https://github.com/silverstripe/silverstripe-cms/commit/0285322) Ability to configure paging for assets / pages (Damian Mooyman)
* 2014-04-22 [d06d5c1](https://github.com/silverstripe/sapphire/commit/d06d5c1) Injector supports nesting BUG Resolve issue with DirectorTest breaking RequestProcessor Injector::nest and Injector::unnest are introduced to better support sandboxing of testings. Injector and Config ::nest and ::unnest support chaining Test cases for both Injector::nest and Config::nest (Damian Mooyman)
* 2014-04-17 [a6017a0](https://github.com/silverstripe/sapphire/commit/a6017a0) HTTP 429 Allowed for use with rate limiting methods (Damian Mooyman)
* 2014-04-11 [892b440](https://github.com/silverstripe/sapphire/commit/892b440) Make default gridfield paging configurable Documentation improved (Damian Mooyman)
* 2014-04-09 [997077a](https://github.com/silverstripe/sapphire/commit/997077a) Security.remember_username to disable login form autocompletion (Damian Mooyman)
### Features and Enhancements
* 2014-03-28 [a502c9d](https://github.com/silverstripe/silverstripe-cms/commit/a502c9d) Fixes #966. Ability to filter pages on page status. - New filters for statuses normally found through SiteTree::getStatusFlags(). - Refactored menu sorting. Now alphabetical, as it wasn't previously. (Russell Michell)
* 2014-04-11 [3765030](https://github.com/silverstripe/silverstripe-cms/commit/3765030) Filter by date created for files Added test cases Do not merge before https://github.com/silverstripe-labs/silverstripe-behat-extension/pull/32 (Damian Mooyman)
### Bugfixes
* 2014-05-05 [c5d5d10](https://github.com/silverstripe/silverstripe-cms/commit/c5d5d10) Behat now uses explicit radio button behaviour (Damian Mooyman)
* 2014-05-01 [bd5abb6](https://github.com/silverstripe/sapphire/commit/bd5abb6) parent::init is not called first (Michael Parkhill)
* 2014-05-01 [4fd3015](https://github.com/silverstripe/sapphire/commit/4fd3015) corrected link to CMS Alternating Button Page (James Pluck)
* 2014-04-29 [8673b11](https://github.com/silverstripe/sapphire/commit/8673b11) Fix ImageTest Image test would erroneously reset the Image::$backend to null if the test was skipped, breaking subsequent test cases (Damian Mooyman)
* 2014-04-29 [89fbae2](https://github.com/silverstripe/silverstripe-cms/commit/89fbae2) Fix encoding of SiteTree.MetaTags (Damian Mooyman)
* 2014-04-25 [ff5f607](https://github.com/silverstripe/sapphire/commit/ff5f607) Docs for DataList::filter() (Daniel Hensby)
* 2014-04-24 [5e9ae57](https://github.com/silverstripe/sapphire/commit/5e9ae57) Fix edge case IE8 / dev / ssl / download file crash Prevents issue at http://support.microsoft.com/kb/323308 appearing on dev (Damian Mooyman)
* 2014-04-17 [bec8927](https://github.com/silverstripe/sapphire/commit/bec8927) Allow PHPUnit installation with composer / Fix travis (Will Morgan)
* 2014-04-16 [396fd9a](https://github.com/silverstripe/silverstripe-cms/commit/396fd9a) Broken file link tracking (fixes #996) (Loz Calver)
* 2014-04-14 [0b4f62d](https://github.com/silverstripe/sapphire/commit/0b4f62d) Fix jstree when duplicating subtrees (Damian Mooyman)
* 2014-04-11 [a261f22](https://github.com/silverstripe/sapphire/commit/a261f22) Delete Character \x01 (Stevie Mayhew)
* 2014-04-09 [91034d1](https://github.com/silverstripe/sapphire/commit/91034d1) HTMLText whitelist considers text nodes Minor improvement to #2853. If a list of whitelisted elements are specified, text nodes no longer evade the whitelist (Damian Mooyman)
* 2014-04-09 [a3c8a59](https://github.com/silverstripe/sapphire/commit/a3c8a59) Fix data query not always joining necessary tables Fixes #2846 (Damian Mooyman)
* 2014-04-08 [a060784](https://github.com/silverstripe/sapphire/commit/a060784) - missing link url for composer (camfindlay)
* 2014-04-07 [3204ab5](https://github.com/silverstripe/silverstripe-cms/commit/3204ab5) Fix orphaned pages reporting they can be viewed (Damian Mooyman)
* 2014-04-01 [84d8022](https://github.com/silverstripe/sapphire/commit/84d8022) Fix Date and SS_DateTime::FormatFromSettings This issue is caused by the odd default behaviour of Zend_Date, which attempts to parse yyyy-mm-dd format date and times as though they were yyyy-dd-mm. (Damian Mooyman)
* 2014-03-12 [b4a1aa4](https://github.com/silverstripe/silverstripe-cms/commit/b4a1aa4) Fixes #965. Allow user date-settings to show on GridField Page admin (Russell Michell)
* 2014-03-04 [ae573f8](https://github.com/silverstripe/sapphire/commit/ae573f8) Fix Versioned stage not persisting in Session. Fixes #962 BUG Disabled disruptive test case in DirectorTest API RequestProcessor and VersionedRequestFilter now both correctly implement RequestFilter Better PHPDoc on RequestFilter and implementations (Damian Mooyman)
* 2013-06-20 [f2c4a62](https://github.com/silverstripe/sapphire/commit/f2c4a62) ConfirmedPasswordField used to expose existing hash (Hamish Friedlander)
## Changelog
* [framework](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.1.5-rc1)
* [cms](https://github.com/silverstripe/silverstripe-cms/releases/tag/3.1.5-rc1)
* [installer](https://github.com/silverstripe/silverstripe-installer/releases/tag/3.1.5-rc1)

View File

@ -1,25 +0,0 @@
# Cache control
By default, PHP add caching headers that make the page appear "purely dynamic".
This isn't usually appropriate for most sites, even ones that are updated reasonably frequently.
In particular, the default PHP cache-control settings prevent sites from appearing in the internet archive.
SilverStripe overrides the default settings with the following:
Default setting:
* The `Last-Modified` date is set to be most recent modification date of any database record queried in the generation of the page.
* The `Expiry` date is set by taking the age of the page and adding that to the current time.
* `Cache-Control` is set to `max-age=86400, must-revalidate`
* Since a visitor cookie is set, the site won't be cached by proxies
* Ajax requests are never cached.
Overriding these defaults
* `[api:HTTP::$cache_age]` can be used to set the max-age component of the cache-control line, in seconds.
Set it to 0 to disable caching; the "no-cache" clause in `Cache-Control` and `Pragma` will be included. It works only for live sites, if `SS_ENVIRONMENT_TYPE` is set to "dev" `[api:HTTP::$cache_age]` will be always overridden with 0.
* `[api:HTTP::register_modification_date()]` can be used to set the modification date to something more recent than the default.
How it works:
* `[api:DataObject::__construct()]` calls `[api:HTTP::register_modification_date()]` whenever a record comes from the database
* `Controller::run()` calls `[api:HTTP::add_cache_headers()]` before outputting the page

View File

@ -1,22 +0,0 @@
# How To Guides
In this section you will find a collection of guides to answer your "How do I?" questions. These are designed to be informal and focused
on tasks and goals rather than going into deep details.
You will find it useful to read the introduction [tutorials](/tutorials) before tackling these How-Tos so you can understand some of
the language and functions which are used in the guides.
* [Howto: Customize the Pages List in the CMS](customize-cms-pages-list)
* [Import CSV Data](csv-import). Build a simple CSV importer using either [api:ModelAdmin] or a custom controller
* [Dynamic Default Fields](dynamic-default-fields). Pre populate a [api:DataObject] with data.
* [Grouping Lists](grouping-dataobjectsets). Group results in a [api:SS_List] to create sub sections.
* [PHPUnit Configuration](phpunit-configuration). How to setup your testing environment with PHPUnit
* [Extend the CMS Interface](extend-cms-interface).
* [How to customize CMS Tree](customize-cms-tree).
* [How to Show Help Text on CMS Form Fields](cms-formfield-help-text).
* [Cache control](cache-control). Override the default PHP cache-control settings.
* [Howto customize the CMS menu](customize-cms-menu).
* [How to create a navigation menu](navigation-menu). Create primary navigation for your website.
* [Paginating A List](pagination). Add pagination for an SS_List object.
* [How to make a simple contact form](simple-contact-form).
* [How to add a custom action to a GridField row](gridfield-rowaction)

View File

@ -1,21 +0,0 @@
# How to create a navigation menu
In this how-to, we'll create a simple menu which
you can use as the primary navigation for your website.
Add the following code to your main template,
most likely the "Page" template in your theme,
located in `themes/<mytheme>/templates/Page.ss`.
:::ss
<ul>
<% loop $Menu(1) %>
<li>
<a href="$Link" title="Go to the $Title page" class="$LinkingMode">
<span>$MenuTitle</span>
</a>
</li>
<% end_loop %>
</ul>
More details on creating a menu are explained as part of ["Tutorial 1: Building a basic site"](/tutorials/1-building-a-basic-site), as well as ["Page type templates" topic](/topics/page-type-templates).

View File

@ -1,95 +0,0 @@
# Paginating A List
Adding pagination to a `[api:SS_List]` is quite simple. All
you need to do is wrap the object in a `[api:PaginatedList]` decorator, which takes
care of fetching a sub-set of the total list and presenting it to the template.
In order to create a paginated list, you can create a method on your controller
that first creates a `SS_List` that will return all pages, and then wraps it
in a `[api:PaginatedList]` object. The `PaginatedList` object is also passed the
HTTP request object so it can read the current page information from the
"?start=" GET var.
The paginator will automatically set up query limits and read the request for
information.
:::php
/**
* Returns a paginated list of all pages in the site.
*/
public function PaginatedPages() {
return new PaginatedList(Page::get(), $this->request);
}
Note that the concept of "pages" used in pagination does not necessarily
mean that we're dealing with `Page` classes, its just a term to describe
a sub-collection of the list.
## Setting Up The Template
Now all that remains is to render this list into a template, along with pagination
controls. 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:
:::ss
<ul>
<% loop $PaginatedPages %>
<li><a href="$Link">$Title</a></li>
<% end_loop %>
</ul>
By default this will display 10 pages at a time. The next step is to add pagination
controls below this so the user can switch between pages:
:::ss
<% if $PaginatedPages.MoreThanOnePage %>
<% if $PaginatedPages.NotFirstPage %>
<a class="prev" href="$PaginatedPages.PrevLink">Prev</a>
<% end_if %>
<% loop $PaginatedPages.Pages %>
<% if $CurrentBool %>
$PageNum
<% else %>
<% if $Link %>
<a href="$Link">$PageNum</a>
<% else %>
...
<% end_if %>
<% end_if %>
<% end_loop %>
<% if $PaginatedPages.NotLastPage %>
<a class="next" href="$PaginatedPages.NextLink">Next</a>
<% end_if %>
<% end_if %>
If there is more than one page, this block will render a set of pagination
controls in the form `[1] ... [3] [4] [[5]] [6] [7] ... [10]`.
## Paginating Custom Lists
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 when using custom lists.
## Setting the limit of items to be displayed on a page ##
To set the limit of items displayed in a paginated page use the `[api:PaginatedList->setPageLength()]` method. e.g:
:::php
/**
* Returns a paginated list of all pages in the site, and limits the items displayed to 4 per page.
*/
public function PaginatedPagesLimit() {
$paginatedItems = new PaginatedList(Page::get(), $this->request);
$paginatedItems->setPageLength(4);
return $paginatedItems;
}
## Related
* [Howto: "Grouping Lists"](/howto/grouping-dataobjectsets)

View File

@ -1,110 +0,0 @@
# Configure PHPUnit for your project
This guide helps you to run [PHPUnit](http://phpunit.de) tests in your SilverStripe project.
See "[Testing](/topics/testing)" for an overview on how to create unit tests.
## Coverage reports
PHPUnit can generate code coverage reports for you ([docs](http://www.phpunit.de/manual/current/en/code-coverage-analysis.html)):
* `phpunit --coverage-html assets/coverage-report`: Generate coverage report for the whole project
* `phpunit --coverage-html assets/coverage-report mysite/tests/`: Generate coverage report for the "mysite" module
Typically, only your own custom PHP code in your project should be regarded when
producing these reports. Here's how you would exclude some `thirdparty/` directories:
<filter>
<blacklist>
<directory suffix=".php">framework/dev/</directory>
<directory suffix=".php">framework/thirdparty/</directory>
<directory suffix=".php">cms/thirdparty/</directory>
<!-- Add your custom rules here -->
<directory suffix=".php">mysite/thirdparty/</directory>
</blacklist>
</filter>
## Running unit and functional tests separately
You can use the filesystem structure of your unit tests to split
different aspects. In the simplest form, you can limit your test exeuction
to a specific directory by passing in a directory argument (`phpunit mymodule/tests`).
To specify multiple directories, you have to use the XML configuration file.
This can be useful to only run certain parts of your project
on continous integration, or produce coverage reports separately
for unit and functional tests.
Example `phpunit-unittests-only.xml`:
<phpunit bootstrap="framework/tests/bootstrap.php" colors="true">
<testsuites>
<testsuite>
<directory>mysite/tests/unit</directory>
<directory>othermodule/tests/unit</directory>
<!-- ... -->
</testsuite>
</testsuites>
<!-- ... -->
</phpunit>
You can run with this XML configuration simply by invoking `phpunit --configuration phpunit-unittests-only.xml`.
The same effect can be achieved with the `--group` argument and some PHPDoc (see [phpunit.de](http://www.phpunit.de/manual/current/en/appendixes.configuration.html#appendixes.configuration.groups)).
## Speeding up your test execution with the SQLite3 module
Test execution can easily take a couple of minutes for a full run,
particularly if you have a lot of database write operations.
This is a problem when you're trying to to "[Test Driven Development](http://en.wikipedia.org/wiki/Test-driven_development)".
To speed things up a bit, you can simply use a faster database just for executing tests.
The SilverStripe database layer makes this relatively easy, most likely
you won't need to adjust any project code or alter SQL statements.
The [SQLite3 module](http://www.silverstripe.org/sqlite-database/) provides an interface
to a very fast database that requires minimal setup and is fully file-based.
It should give you up to 4x speed improvements over running tests in MySQL or other
more "heavy duty" relational databases.
Example `mysite/_config.php`:
// Customized configuration for running with different database settings.
// Ensure this code comes after ConfigureFromEnv.php
if(Director::isDev()) {
if(isset($_GET['db']) && ($db = $_GET['db'])) {
global $databaseConfig;
if($db == 'sqlite3') $databaseConfig['type'] = 'SQLite3Database';
}
}
You can either use the database on a single invocation:
phpunit framework/tests "" db=sqlite3
or through a `<php>` flag in your `phpunit.xml` (see [Appenix C: "Setting PHP INI settings"](http://www.phpunit.de/manual/current/en/appendixes.configuration.html)):
<phpunit>
<!-- ... -->
<php>
<get name="db" value="sqlite3"/>
</php>
</phpunit>
Note that on every test run, the manifest is flushed to avoid any bugs where a test doesn't clean up after
itself properly. You can override that behaviour by passing `flush=0` to the test command:
phpunit framework/tests flush=0
Alternatively, you can set the var in your `phpunit.xml` file:
<phpunit>
<!-- ... -->
<php>
<get name="flush" value="0"/>
</php>
</phpunit>
<div class="hint" markdown="1">
It is recommended that you still run your tests with the original database driver (at least on continuous integration)
to ensure a realistic test scenario.
</div>

View File

@ -1,58 +1,51 @@
This website documents high-level features of the [SilverStripe open source platform](http://www.silverstripe.org), aimed
at developers.
Please read our [guide to contributing documentation](misc/contributing/documentation) if you want to help out!
title: SilverStripe Documentation
introduction: Welcome to the SilverStripe Developer Documentation. This website is aimed at website developers looking to learn how to build and manage web applications with the SilverStripe Framework.
### Overview
## Getting Started with SilverStripe
* [Getting started](http://silverstripe.org/getting-started/) | [Feature Overview](http://silverstripe.org/introduction/) | [Demo](http://demo.silverstripe.org/)
* [Download and Installation](installation/) | [Upgrading](/installation/upgrading) | [Requirements](/installation/server-requirements) | [Changelog](/changelogs) | [Roadmap](http://www.silverstripe.org/roadmap/)
* [API documentation](http://api.silverstripe.org/current) | [Official english book](http://www.silverstripe.org/silverstripe-book) | [Official german book](http://www.silverstripe.org/das-silverstripe-buch)
Before you start developing your first web application, you'll need to install the latest version of SilverStripe onto
a web server. The [Getting Started](getting_started/) section will show you what
[server requirements](/installation/server-requirements) you will need to meet and how to
[download and install SilverStripe](installation/).
To check out the features that SilverStripe offers without installing it, read the [Feature Overview](http://silverstripe.org/introduction/)
and play with the interactive [demo website](http://demo.silverstripe.org/).
## Getting support
SilverStripe has an wide range of options for getting support. The [forums](http://www.silverstripe.org/forums/)
and [IRC channel](http://silverstripe.org/irc) are the best places to talk and discuss questions and problems with the
community. There are also several other websites with SilverStripe documentation to make use of.
* The [API Documentation](http://api.silverstripe.org/current) contains technical reference and class information.
* The [User Help](http://userhelp.silverstripe.com) website contains documentation related to working within the CMS.
New features, API changes and the development [roadmap](http://www.silverstripe.org/roadmap/) for the product are
discussed on the [core mailinglist](https://groups.google.com/forum/#!forum/silverstripe-dev) along with
[UserVoice](http://silverstripe.uservoice.com/forums/251266-new-features).
### Getting support
## Building your first SilverStripe Web application
[Forum](http://www.silverstripe.org/forums/) | [IRC channel](http://silverstripe.org/irc) | [End user docs](http://userhelp.silverstripe.com) | [Core mailinglist](https://groups.google.com/forum/#!forum/silverstripe-dev)
Once you have completed the [Getting Started](http://silverstripe.org/getting-started/) guide and have got SilverStripe
installed and running, the following [Tutorials](tutorials/) will lead through the basics and core concepts of
SilverStripe.
Make sure you know the basic concepts of PHP5 before attempting to follow the tutorials. If you have not programmed with
PHP5 be sure to read the [Introduction to PHP5 (zend.com)](http://devzone.zend.com/6/php-101--php-for-the-absolute-beginner).
### Level 1: Building your first SilverStripe website
[CHILDREN Folder=01_Tutorials]
* [Introduction to PHP5 (zend.com)](http://devzone.zend.com/6/php-101--php-for-the-absolute-beginner)
* [Tutorials](tutorials)
* [1. Building a basic site](tutorials/1-building-a-basic-site)
* [2. Extending a basic site](tutorials/2-extending-a-basic-site)
* [3. Forms](tutorials/3-forms)
* [4. Site Search](tutorials/4-site-search)
* [5. Relationships](tutorials/5-dataobject-relationship-management)
* [Common Problems](installation/common-problems)
## SilverStripe Concepts
### Level 2: SilverStripe fundamentals
The [Developer Gudes](developer_guides/) contain more detailed documentation on certain SilverStripe topics, how-to
examples and reference documentation.
* [Templates](reference/templates): SilverStripe has its own templating engine
* [Themes](topics/themes): How to customize your site with themes
* [Controllers](topics/controller): Coordination from a URL-request to finding the controller-class
* [Pagetypes](topics/page-types): Clarifying the relationship between a page-object and a silverstripe-class
* [Datamodel](topics/datamodel): Object-relational database model with MVC
* [Database Structure](reference/database-structure): Breakdown of a typical SilverStripe database
* [Datatypes](topics/data-types): Casting database-columns
* [Forms](topics/forms): Sophisticated form generation and processing
* [Formfield Types](reference/form-field-types): Simple and complex form-elements with built-in validation
* [Javascript](topics/javascript)
* [Widgets](topics/widgets)
* [Modules](topics/modules)
* [Configuration](topics/configuration)
[CHILDREN Folder=02_Developer_Guides]
### Level 3: The less obvious features
## Contributing to SilverStripe
* [Security](topics/security)
* [Email](topics/email)
* [RSS Feeds](reference/rssfeed)
* [Debugging](topics/debugging)
* [Errorhandling](topics/error-handling)
* [Testing Guide](topics/testing/): Unit and integration testing
* [Execution Pipeline](reference/execution-pipeline): Tracking a request from director to template-rendering
* [Recipes/Howtos](howto/)
The SilverStripe Framework, Content Management System and related websites are open source and welcome community
contributions.
### Level 4: Contributing to the SilverStripe core
* [Contributing](misc/contributing)
* [Coding Conventions](misc/coding-conventions)
[CHILDREN Folder=05_Contributing]

View File

@ -1,6 +0,0 @@
# Installation from Source Control
The best way to install from source is to use [Composer](composer).
The original instructions on this page have been removed, as they were obsolete and misleading. Please use Composer
instead.

View File

@ -1,31 +0,0 @@
# Manual installation on Windows using IIS
Install SilverStripe manually on Windows using IIS as the web server.
If you are not confident in installing web server software manually on Windows, it is recommended you use the
[Web Platform Installer](windows-pi) method instead, which will do the installation for you.
## [Install using IIS 7.x](windows-manual-iis-7)
This applies to Windows Server 2008, Windows Server 2008 R2, Windows Vista, and Windows 7.
## [Install using IIS 6.x](windows-manual-iis-6)
This applies to Windows Server 2003 and Windows Server 2003 R2.
<div class="warning" markdown="1">Note: These instructions may not work, as they're no longer maintained.</div>
## Additional notes
Microsoft has no URL rewriting module for any version below IIS 7.x. This will mean your URLs are like yoursite.com/index.php/about-us rather than yoursite.com/about-us.
However, if you do want friendly URLs you must you must buy or use other URL rewriting software:
* [IIRF](http://iirf.codeplex.com/) (should work for most cases - see [IIS 6 guide](windows-manual-iis-6) for rewrite rules)
* [ISAPI_Rewrite](http://www.helicontech.com/download-isapi_rewrite3.htm) (The freeware, lite version should be fine for simple installations)
* If you have 64-bit Windows, you can try [this](http://www.micronovae.com/ModRewrite/ModRewrite.html)
Instructions are available for [installing PHP on IIS 6](http://learn.iis.net/page.aspx/248/configuring-fastcgi-extension-for-iis60/) by the IIS team.
On Windows XP, you need to disable **Check that file exists**. See [installation-on-windows-pi](windows-pi) for more information.
Matthew Poole has expanded on these instructions [with a tutorial](http://cubiksoundz.blogspot.com/2008/12/tech-note-installing-silverstripe-cms.html).

View File

@ -1,153 +0,0 @@
# Contributing Documentation
[« Back to Contributing page](.)
Documentation for a software project is a continued and collaborative effort,
we encourage everybody to contribute, from simply fixing spelling mistakes, to writing recipes/howtos,
reviewing existing documentation, and translating the whole thing.
Modifying documentation requires basic [PHPDoc](http://en.wikipedia.org/wiki/PHPDoc) and
[Markdown](http://daringfireball.net/projects/markdown/)/[SSMarkdown](../ss-markdown) knowledge,
and a GitHub user account.
## Editing online
The easiest way of making a change to the documentation is by clicking the "Edit this page" link at
the bottom of the page you want to edit. Alternativly, you can find the appropriate .md file in
the [github.com/silverstripe/silverstripe-framework](https://github.com/silverstripe/silverstripe-framework/tree/3.0/docs/) repository
and press the "edit" button. You will need a GitHub account to do this. You should make the changes in the lowest branch they apply to.
* After you have made your change, describe it in the "commit summary" and "extended description" fields below, and press "Commit Changes".
* After that you will see form to submit a Pull Request. You should just be able to submit the form, and your changes will be sent to the core team for approval.
## Editing on your computer
If you prefer to edit the content on your local machine, you can "[fork](http://help.github.com/forking/)"
the [github.com/silverstripe/silverstripe-framework](http://github.com/silverstripe/silverstripe-framework)
and [github.com/silverstripe/silverstripe-cms](http://github.com/silverstripe/silverstripe-cms)
repositories and send us "[pull requests](http://help.github.com/pull-requests/)". If you have
downloaded SilverStripe or a module, chances are that you already have these checkouts.
The documentation is kept alongside the source code in the `docs/` subfolder.
**Note:** If you submit a new feature or an API change, we strongly recommend that your patch
includes updates to the necessary documentation. This helps prevent our documentation from
getting out of date.
## Repositories
* End-user: [userhelp.silverstripe.org](http://userhelp.silverstripe.org) - a custom SilverStripe project (not open sourced at the moment).
* Developer Guides: [doc.silverstripe.org](http://doc.silverstripe.org) - powered by a
SilverStripe project that uses the ["docsviewer" module](https://github.com/silverstripe/silverstripe-docsviewer)
to convert Markdown formatted files into searchable HTML pages with index lists.
Its contents are fetched from different releases automatically every couple of minutes.
* Developer API Docuumentation: [api.silverstripe.org](http://api.silverstripe.org) - powered by a customized
[phpDocumentor](http://www.phpdoc.org/) template, and is regenerated automatically every night.
## Source Control
In order to balance editorial control with effective collaboration, we keep
documentation alongside the module source code, e.g. in `framework/docs/`,
or as code comments within PHP code.
Contributing documentation is the same process as providing any other patch
(see [Contributing code](code)).
## What to write
* **API Docs**: Written alongside source code and displayed on [api.silverstripe.com](http://api.silverstripe.org).
This documents the low-level, technical usage of a class, method or property.
Not suited for longer textual descriptions, due to the limited support of PHPDoc formatting for headlines.
* **Tutorials**: The first contact for new users, guiding them step-by-step through achievable projects, in a book-like style.
*Example: Building a basic site*
* **Topics**: Provides an overview on how things fit together, the "conceptual glue" between APIs and features.
This is where most documentation should live, and is the natural "second step" after finishing the tutorials.
*Example: Templates, Testing, Datamodel*
* **Howto**: Recipes that solve a specific task or problem, rather than describing a feature.
*Example: Export DataObjects as CSV, Customizing TinyMCE in the CMS*
* **Reference**: Complements API docs in providing deeper introduction into a specific API. Most documentation
should fit elsewhere. *Example: ModelAdmin*
* **Misc**: "Meta" documentation like coding conventions that doesn't directly relate to a feature or API.
See [What to write (jacobian.org)](http://jacobian.org/writing/great-documentation/what-to-write/) for an excellent
introduction to the different types of documentation, and [Producing OSS: "Documentation"](http://producingoss.com/en/getting-started.html#documentation)
for good rules of thumb for documenting opensource software.
## Structure
* Don't duplicate: Search for existing places to put your documentation. Do you really require a new page, or just a new paragraph
of text somewhere?
* Use PHPDoc in source code: Leave lowlevel technical documentation to code comments within PHP, in [PHPDoc](http://en.wikipedia.org/wiki/PHPDoc) format.
* Use Markdown in Developer Guides: We have a slightly customized version of Markdown called [SSMarkdown](../ss-markdown)
* API and Developer Guides complement each other: Both forms of documenting sourcecode (API and Developer Guides) are valueable ressources.
* Provide context: Give API documentation the "bigger picture" by referring to Developer Guides inside your PHPDoc.
* Make your documentation findable: Documentation lives by interlinking content, so please make sure your contribution doesn't become an
inaccessible island. Your page should at least be linked on the index page in the same folder. It can also appear
as "related content" on other resource (e.g. `/topics/search` might link to `howto/search-dataobjects`).
* Avoid FAQs: FAQs are not a replacement of a coherent, well explained documentation. If you've done a good job
documenting, there shouldn't be any "frequently asked questions" left ;)
* Commit early and often: You don't need to completely finish documentation, as long as you mark areas needing refinement.
* Every file should have exactly one `<h1>` headline, roughly matching the filename. It should be short enough to be
used in table of content lists.
## Writing Style
* Write in second plural form: Use "we" instead of "I". It gives the text an instructive and collaborative style.
* Its okay to address the reader: For example "First you'll install a webserver" is good style.
* Write in an active and direct voice
* Mark up correctly: Use preformatted text, emphasis and bold to make technical writing more "scannable".
## Highlighted blocks
There are several built-in block styles for highlighting a paragraph of text.
Please use these graphical elements sparingly.
<div class="hint" markdown='1'>
"Tip box": Adds, deepens or accents information in the main text.
Can be used for background knowledge, or "see also" links.
</div>
Code:
<div class="hint" markdown='1'>
...
</div>
<div class="notice" markdown='1'>
"Notification box": Technical notifications relating to the main text.
For example, notifying users about a deprecated feature.
</div>
Code:
<div class="notice" markdown='1'>
...
</div>
<div class="warning" markdown='1'>
"Warning box": Highlight a severe bug or technical issue requiring
a users attention. For example, a code block with destructive functionality
might not have its URL actions secured to keep the code shorter.
</div>
Code:
<div class="warning" markdown='1'>
...
</div>
See [Markdown Extra Documentation](http://michelf.com/projects/php-markdown/extra/#html) for more restriction
on placing HTML blocks inside Markdown.
## Translating Documentation
Documentation is kept alongside the source code, typically in a module subdirectory like `framework/docs/en/`.
Each language has its own subfolder, which can duplicate parts or the whole body of documentation.
German documentation would for example live in `framework/docs/de/`.
The [docsviewer](https://github.com/silverstripe/silverstripe-docsviewer) module that drives
[doc.silverstripe.org](http://doc.silverstripe.org) automatically resolves these subfolders into a language dropdown.
## Further reading
* [Writing great documentation (jacobian.org)](http://jacobian.org/writing/great-documentation/)
* [How tech writing sucks: Five Sins](http://www.slash7.com/articles/2006/11/15/tech-writing-the-five-sins)
* [What is good documentation?](http://www.techscribe.co.uk/techw/whatis.htm)

View File

@ -1,20 +0,0 @@
# Contributing
Any open source product is only as good as the community behind it. You can
participate by sharing code, ideas, or simply helping others. No matter what
your skill level is, every contribution counts.
See our [high level overview on silverstripe.org](http://silverstripe.org/contributing-to-silverstripe)
on how you can help out.
Or, for more detailed guidance, read one of the following pages:
* [Sharing your **opinion** and raising **issues**](issues)
* [Providing **code**, whether it's creating a feature or fixing a bug](code)
* [Writing and translating **documentation**](documentation)
* [**Translating** user-interface elements](translation)
## House rules for everybody contributing to SilverStripe
* Ask questions on the [forum](http://silverstripe.org/forum), and stick to more high-level discussions on the [core mailinglist](https://groups.google.com/forum/#!forum/silverstripe-dev)
* Make sure you know how to [raise good bug reports](/misc/contributing/issues)
* Everybody can contribute to SilverStripe! If you do, ensure you can [submit solid pull requests](/misc/contributing/code)

View File

@ -1,130 +0,0 @@
# Contributing Translations for the User Interface
[« Back to Contributing page](../contributing)
We are always looking for new translators. Even if a specific language already is translated and has an official maintainer, we can use helping hands in reviewing and updating translations. Important: It is perfectly fine if you only have time for a partial translation or quick review work - our system accomodates many people collaborating on the same language.
The content for UI elements (button labels, field titles) and instruction texts shown in the CMS and elsewhere is stored in the PHP code for a module (see [i18n](/topics/i18n)). All content can be extracted as a "language file", and uploaded to an online translation editor interface. SilverStripe is already translated in over 60 languages, and we're relying on native speakers to keep these up to date, and of course add new languages.
Please register a free translator account to get started, even if you just feel like fixing up a few sentences.
## The online translation tool
We provide a GUI for translations through [transifex.com](http://transifex.com). If you don't have an account yet, please follow the links there to sign up. Select a project from the [list of translatable modules](https://www.transifex.com/accounts/profile/silverstripe/) and start translating online!
For all modules listed there, we automatically import new master strings
as they get committed to the various codebases (via a nightly task),
so you're always translating on the latest and greatest version.
You can check the last successful push of the translation master strings in our
[public continuous integration server](http://teamcity.silverstripe.com/viewType.html?buildTypeId=bt112) (select "log in as guest").
## FAQ
### What happened to getlocalization.com?
We migrated from getlocalization.com to transifex in mid 2013.
### How do I translate a module not listed on Transifex?
Most modules maintained by SilverStripe are on Transifex.
For other modules, have a look in the module README if there's any specific instructions.
If there aren't, you'll need to translate the YAML files directly. If the module is on github,
you can create a fork, edit the files, and send back your pull request all directly on
the website ([instructions](https://help.github.com/articles/fork-a-repo)).
### How do I translate substituted strings? (e.g. '%s' or '{my-variable}')
You don't have to - if the english master-string reads 'Hello %s', your german translation would be 'Hallo %s'. Strings prefixed by a percentage-sign are automatically replaced by silverstripe with dynamic content. See http://php.net/sprintf for details. The newer `{my-variable}` format works the same way,
but makes its intent clearer, and allows reordering of placeholders in your translation.
### Do I need to convert special characters (e.g. HTML-entities)?
Special characters (such as german umlauts) need to be entered in their native form. Please don't use HTML-entities ("ä" instead of "ä"). Silverstripe stores and renders most strings in UTF8 (Unicode) format.
### How can I check out my translation in the interface?
Currently translated entities are not directly factored into code (for security reasons and release/review-control), so you can't see them straight away.
It is strongly encouraged that you check your translation this way, as its a good way to doublecheck your translation works in the right context.
Please use our [daily-builds](http://www.silverstripe.org/daily-builds/) for your local installation, to ensure you're looking at the most up to date interface. See "Download Translations" above
to find out how to retrieve the latest translation files.
### Can I change a translation just for one SilverStripe version?
While we version control our translation files like all other source code,
the online translation tool doesn't have the same capabilities.
A translated string (as identified by its unique "entity name")
is assumed to work well in all releases. If the interface changes
in a non-trivial fashion, the new translations required should
have new identifiers as well.
Example: We renamed the "Security" menu title to "Users"
in our 3.0 release. As it would confuse users of older versions
unnecessarily, we should be using a new entity name for this,
and avoid the change propagating to an older SilverStripe version.
### How do I change my interface language?
Once you've logged into the CMS, you should see the text "Hi <your name>" near the top left, you can click this to edit your profile ([direct link](http://localhost/admin/myprofile/)). You can then set the "interface language" from a dropdown which automatically includes all found translations (based on the files in the `/lang` folders).
### I've found a piece of untranslatable text
It is entirely possible that we missed certain strings in preparing Silverstripe for translation-support. If you're technically minded, please read [i18n](/topics/i18n) on how to make it translatable. Otherwise just post your findings to the forum.
### How do I add my own module?
Once you've built a translation-enabled module, you can run the "textcollector" on your local installation for this specific module (see [i18n](/topics/i18n)). This should find all calls to `_t()` in php and template files, and generate a new lang file with the default locale (path: <mymodule>/lang/en.yml). Upload this file to the
online translation tool, and wait for hyour translators to do their magic!
### What about right-to-left (RTL) languages (e.g. Arabic)?
SilverStripe doesn't have built-in support for attribute-based RTL-modifications (`<html dir="rtl">`).
We are currently investigating the available options, and are eager to get feedback on your experiences with translating silverstripe RTL.
### Can I translate/edit the language files in my favourite text editor (on my local installation)
Not for modules managed by transifex.com, including "framework" and "cms.
It causes us a lot of work in merging these files back.
Please use the online translation tool for all new and existing translations.
### How does my translation get into a SilverStripe release?
Currently this is a manual process of a core team member downloading approved translations and committing them into our source tree.
### How does my translation get approved, who is the maintainer?
The online translation tool (transifex.com) is designed to be decentralized and collaborative,
so there's no strict approval or review process.
Every logged-in user on the system can flag translations,
and discuss them with other translators.
### I'm seeing lots of duplicated translations, what should I do?
For now, please translate all duplications - sometimes they might be intentional, but mostly the developer just didn't know his phrase was already translated.
Please contact us about any duplicates that might be worth merging.
### What happened to translate.silverstripe.org?
This was a custom-built online translation tool serving us well for a couple of years,
but started to show its age (performance and maintainability). It was replaced
with transifex.com. All translations from translate.silverstripe.org were migrated.
Unfortunately, the ownership of individual translations couldn't be migrated.
As the new tool doesn't support the PHP format used in SilverStripe 2.x,
this means that we no longer have a working translation tool for PHP files.
Please edit the PHP files directly and [send us pull requests](/misc/contributing).
This also applies for any modules staying compatible with SilverStripe 2.x.
## Contact
Translators have their own [mailinglist](https://groups.google.com/forum/#!forum/silverstripe-translators),
but you can also reach a core member on [IRC](http://silverstripe.org/irc).
The transifex.com interface has a built-in discussion board if
you have specific comments on a translation.
## Related
* [i18n](/topics/i18n): Developer-level documentation of Silverstripe's i18n capabilities
* [translation-process](../translation-process): Information about managing translations for the core team and/or module maintainers.
* [translatable](https://github.com/silverstripe/silverstripe-translatable): DataObject-interface powering the website-content translations
* ["Translatable ModelAdmin" module](http://silverstripe.org/translatablemodeladmin-module/): An extension which allows translations of DataObjects inside ModelAdmin

View File

@ -1,9 +0,0 @@
# Misc
This section is dedicated to going to detail about an assortment of topics which
don't necessary fit into other documentation sections.
* [Coding conventions](coding-conventions): Guidelines and standards for code formatting and documentation
* [Contributing](contributing): How you can be a part of the SilverStripe Open Source community
* [Release process](release-process): Describes the Framework and CMS release process
* [SS markdown](ss-markdown): Markdown syntax for our technical documentation

View File

@ -1,153 +0,0 @@
# Release Process
Describes the process followed for "core" releases (mainly the `framework` and `cms` modules).
## Release Maintainer
The current maintainer responsible for planning and performing releases is Ingo Schommer (ingo at silverstripe dot com).
## Release Planning
Our most up-to-date release plans are typically in the ["framework" milestone](https://github.com/silverstripe/silverstripe-framework/issues/milestones) and ["cms" milestone](https://github.com/silverstripe/silverstripe-cms/issues/milestones).
New features and API changes are typically discussed on the [core
mailinglist](http://groups.google.com/group/silverstripe-dev). They are prioritized by the core team as tickets on
github.com.
Release dates are usually not published prior to the release, but you can get a good idea of the release status by
reviewing the release milestone on github.com. Releases will be
announced on the [release announcements mailing list](http://groups.google.com/group/silverstripe-announce).
Releases of the *cms* and *framework* modules are coupled at the moment, they
follow the same numbering scheme.
## Release Numbering
* Versions are numbered by major version number, minor version number, and micro version number, in the form *A.B.C*
(e.g. *2.4.1*)
* *A* is the *major version number*, which is only incremented for major changes and core rewrites, lots of them won't
be backwards compatible.
* *B* is the *minor version number*. It is incremented for our typical releases with new features and bugfixes. We
strive for few changes to be backwards incompatible, and will deprecate any APIs before removing them.
* *C* is the *micro version number*, incremented for bugfixes, minor enhancements and security fixes. Unless
security-related, all changes will be fully backwards compatible to the minor version number.
* Major and minor releases have an *alpha* cycle, which is a preview developer release which that see major changes
until release. It is followed by a *beta* cycle, which is feature complete and used by the wider development community
for stability and regression testing. Naming convention is *A.B.C-alpha* and *A.B.C-beta*.
* Major, minor and micro releases can have one or more *release candidates (RC)*, to be used by the wider community. A
release candidate signifies that the core team thinks the release is ready without further changes. The actual release
should be a identical copy of the latest RC. Naming convention is *A.B.C-rc1* (and further increments).
* Major releases may have a *preview* cycle which is a early snapshot of the codebase for developers before
going into the *alpha* cycle. Preview releases are named *A.B.C-pr1* (and further increments).
### Major releases
So far, major releases have happened every couple of years. Most new releases are *minor version number* or *micro
version number* increments.
So far, we have had two major releases; from the *1.x* to the *2.x* line and from the *2.x* to the *3.x* line.
### Minor releases
Minor releases have happened about once every 18 months.
For example, *2.3* was released in February 2009, followed by *2.4* in May 2010.
These releases will contain new features, general enhancements and bugfixes. APIs from previous minor releases can be
*deprecated*, but will stay available for one more minor release. So, if an API is deprecated in *A.B*, it will continue
to work in *A.B+1*, and removed in *A.B+2*.
An example: Say we'd want to rename *BasicAuth::requireLogin()* to follow our coding conventions, which is
*BasicAuth::require_login()*. The method was introduced in *2.1*, we've made the change in *2.3*?
* *2.3* would've marked the method as *@deprecated*, and documents it as an *API CHANGE* in our
[changelog](/changelogs). The old method continues to work, but will throw an *E_USER_NOTICE*.
* *2.4* would've removed the method, also documenting it as an *API CHANGE*, and mentioning it in the
[upgrading](/installation/upgrading) guidelines.
Exceptions to the deprecation cycle are APIs that have been moved into their own module, and continue to work with the
new minor release. These changes can be performed in a single minor release without a deprecation period.
### Micro releases
Micro releases are issued about every two months for the latest release, typically for security reasons.
You can safely upgrade to those releases (after reading the [upgrading](/installation/upgrading) guidelines).
For example, *2.3.6* was released in February 2010, followed by *2.3.7* in March 2010.
### Supported versions
At any point in time, the core development team will support a set of releases to varying levels:
* The current *development trunk* will get new features and bug fixes that might require major refactoring before going
into a release (Note: At the moment, bugfixing and feature development might happen on the current release branch, to be
merged back to trunk regularly).
* Applicable bugfixes on trunk will also be merged back to the last minor release branch, to be released as the next
micro release.
* Security fixes will be applied to the current trunk and the previous two minor releases (e.g. *2.3.8* and *2.4.1*).
## Deprecation
Needs of developers (both on core framework and custom projects) can outgrow the capabilities
of a certain API. Existing APIs might turn out to be hard to understand, maintain, test or stabilize.
In these cases, it is best practice to "refactor" these APIs into something more useful.
SilverStripe acknowledges that developers have built a lot of code on top of existing APIs,
so we strive for giving ample warning on any upcoming changes through a "deprecation cycle".
How to deprecate an API:
* Add a `@deprecated` item to the docblock tag, with a `{@link <class>}` item pointing to the new API to use.
* Update the deprecated code to throw a `[api:Deprecation::notice()]` error.
* Both the docblock and error message should contain the **target version** where the functionality is removed.
So if you're committing the change to a 3.1 pre-release version, the target version will either be 3.2 or 4.0,
depending on how disruptive the change is.
* Deprecations should just be committed to pre-release branches, ideally before they enter the "beta" phase.
If deprecations are introduced after this point, their target version needs to be increased by one.
* Make sure that the old deprecated function works by calling the new function - don't have duplicated code!
* The commit message should contain an `API` prefix (see ["commit message format"](/misc/contributing/code#commit-messages))
* Deprecated APIs can be removed after developers had a chance to react to the changes. As a rule of thumb, leave the code with the deprecation warning in for at least three micro releases. Only remove code in a minor or major release.
Here's an example for replacing `Director::isDev()` with a (theoretical) `Env::is_dev()`:
:::php
/**
* Returns true if your are in development mode
* @deprecated 3.1 Use {@link Env::is_dev()} instead.
*/
public function isDev() {
Deprecation::notice('3.1', 'Use Env::is_dev() instead');
return Env::is_dev();
}
This change could be committed to a 3.1.0-alpha2 release, stays deprecated in all following minor releases
(3.1.0-beta1, 3.1.0, 3.1.1), and gets removed from 3.2.0. If the change was introduced in an already
released version (e.g. 3.1.1), the target version becomes 3.2 instead.
## Security Releases
### Reporting an issue
Report security issues to [security@silverstripe.com](mailto:security@silverstripe.com).
Please don't file security issues in our [bugtracker](/misc/contributing/issues).
### Acknowledgement and disclosure
In the event of a confirmed vulnerability in SilverStripe core, we will take the following actions:
* Acknowledge to the reporter that weve received the report and that a fix is forthcoming. Well give a rough
timeline and ask the reporter to keep the issue confidential until we announce it.
* Halt all other development as long as is needed to develop a fix, including patches against the current and one
previous major release (if applicable).
* We will inform you about resolution and [announce](http://groups.google.com/group/silverstripe-announce) a
[new release](http://silverstripe.org/security-releases/) publically.
You can help us determine the problem and speed up responses by providing us with more information on how to reproduce
the issue: SilverStripe version (incl. any installed modules), PHP/webserver version and configuration, anonymized
webserver access logs (if a hack is suspected), any other services and web packages running on the same server.
### Severity rating
Each [security release](http://www.silverstripe.org/security-releases/) includes an overall severity rating and one for each vulnerability. The rating indicates how important an update is:
| Severity | Description |
|---------------|-------------|
| **Critical** | Critical releases require immediate actions. Such vulnerabilities allow attackers to take control of your site and you should upgrade on the day of release. *Example: Directory traversal, privilege escalation* |
| **Important** | Important releases should be evaluated immediately. These issues allow an attacker to compromise a site's data and should be fixed within days. *Example: SQL injection.* |
| **Moderate** | Releases of moderate severity should be applied as soon as possible. They allow the unauthorized editing or creation of content. *Examples: Cross Site Scripting (XSS) in template helpers.* |
| **Low** | Low risk releases fix information disclosure and read-only privilege escalation vulnerabilities. These updates should also be applied as soon as possible, but with an impact-dependent priority. *Example: Exposure of the core version number, Cross Site Scripting (XSS) limited to the admin interface.* |

View File

@ -1,115 +0,0 @@
# SilverStripe Markdown Syntax
As Markdown by default is quite limited and not well suited for technical documentation,
the SilverStripe project relies on certain syntax additions. As a base syntax, we use
the [Markdown Extra](http://michelf.com/projects/php-markdown/extra/) format, which provides us
with support for tables, definition lists, code blocks and inline HTML.
**Please read the [Markdown](http://daringfireball.net/projects/markdown/syntax) and
[Markdown Extra](http://michelf.com/projects/php-markdown/extra/) documentation for a syntax overview**
On top of that, we have added syntax that is only resolved by our custom parser.
The goal is to keep the customization to a necessary minimum,
and HTML output should still be readable with our custom markup unparsed.
## Rendering
While most of the Markdown syntax is parseable by all common implementations,
the special syntax is relying on a custom SilverStripe project that powers `http://doc.silverstripe.org`.
The website a standard SilverStripe installation with the [docsviewer](https://github.com/silverstripe/silverstripe-docsviewer/)
module installed (see module [README](https://github.com/silverstripe/silverstripe-docsviewer/blob/master/README.md) and
[documentation](https://github.com/silverstripe/silverstripe-docsviewer/tree/master/docs/en)).
## Syntax
### Relative Links
Relative links can point to other markdown pages in the same module.
They are always referred to **without** the `.md` file extension.
"Absolute" links relate to the root of a certain module,
not the webroot of the renderer project or the filesystem root.
* link to folder on same level: `[title](sibling/)`
* link to page on same level: `[title](sibling)`
* link to parent folder: `[title](../parent/)`
* link to page in parent folder: `[title](../parent/page)`
* link to root folder: `[title](/)`
* link to root page: `[title](/rootpage)`
Don't forget the trailing slash for directory links,
it is important to distinguish files from directories.
<div class="notice" markdown='1'>
It is recommended to use absolute links over relative links
to make files easier to move around without changing all links.
</div>
### API Links
You can link to API documentation from within the markup by pseudo-links.
These are automatically resolved to the right URL on `http://api.silverstripe.org`.
API links are automatically wrapped in `<code>` blocks by the formatter.
* Link to class: `[api:DataObject]`
* Link to static method: `[api:DataObject::has_one()]`
* Link to instance method: `[api:DataObject->write()]`
* Link to static property: `[api:DataObject::$searchable_fields]`
* Link to instance property: `[api:DataObject->changedFields]`
* Custom titles: `[my title](api:DataObject)`
There's some gotchas:
* This notation can't be used in code blocks.
* If you want to use API links to other modules or versions of the same module, you'll have to use the full `http://` URL.
* You can't mark API links in backticks to trigger `<pre>` formatting, as it will stop the link parsing.
The backticks are automatically added by the parser.
### Code Blocks with Highlighting
Code blocks can optionally contain language hints that a syntax highlighter can
pick up. Use the first line in the block to add a language identifier, prefixed by three colons (`:::`), for example `:::php`.
We're currently using the [syntaxhighlighter](http://code.google.com/p/syntaxhighlighter/) JavaScript implementation.
See a [list of supported languages](http://code.google.com/p/syntaxhighlighter/wiki/Languages).
Example for PHP:
:::php
class Page extends SiteTree {
public function myFunction() {
// ...
}
}
For SilverStripe templates, please use `:::ss` as a brush.
### Images
As a convention, referenced images in a Markdown formatted page should always be stored
in an `_images/` folder on the same level as the page itself. Try to keep the image size
small, as we typically package the documentation with the source code download, and
need to keep the file size small.
You can link to absolute image URLs as well, of course.
## FAQs
### How do I preview my own SS Markdown?
Thats only possible with the `docsviewer` module - we don't have a standalone parser.
### Can I run my own documentation server?
Yes, the `docsviewer` module just requires a default SilverStripe installation (2.4+).
### Can I generate SS Markdown other formats?
Currently this is not supported, as all HTML is generated on the fly.
### Can I contribute to the parser and rendering project?
Of course, the `docsviewer` code is BSD licensed - we're looking forward to your contributions!
## Related ##
* [contributing/documentation](contributing/documentation): The doc.silverstripe.org website has certain styling and writing conventions

View File

@ -1,125 +0,0 @@
# Translation Process #
This page covers a few advanced topics related to SilverStripe's translation system.
To find out about how to assist with translating SilverStripe, see the [Contributing Translations page](contributing/translation).
## Set up your own module for localization
### Collecting translatable text
As a first step, you can automatically collect
all translatable text in your module through the `i18nTextCollector` task.
See [i18n](/topics/i18n#collecting-text) for more details.
### Import master files
If you don't have an account on transifex.com yet, [create a free account now](http://www.transifex.com/signup).
After creating a new project, you have to upload the `en.yml` master file as a new "Resource".
While you can do this through the web interface, there's a convenient
[commandline client](http://support.transifex.com/customer/portal/topics/440187-transifex-client/articles)
for this purpose. In order to use it, set up a new `.tx/config` file in your module folder:
[main]
host = https://www.transifex.com
[my-project.master]
file_filter = lang/<lang>.yml
source_file = lang/en.yml
source_lang = en
type = YML
If you don't have existing translations,
your project is ready to go - simply point translators
to the URL, have them sign up, and they can create languages and translations as required.
### Import existing translations
In case you have existing translations in YML format,
there's a "New language" option in the web interface.
Alternatively, use the [commandline client](http://support.transifex.com/customer/portal/topics/440187-transifex-client/articles).
### Export existing translations
You can download new translations in YML format through the web interface,
but that can get quite tedious for more than a handful of translations.
Again, the [commandline client](http://support.transifex.com/customer/portal/topics/440187-transifex-client/articles)
provides a more convenient interface here with the `tx pull` command, downloading all translations as a batch.
### Merge back existing translations
If you want to backport translations onto release branches, simply run the `tx pull` command on multiple branches.
This assumes you're adhereing to the following guidelines:
- For significantly changed content of an entity, create a new entity key
- For added/removed placeholders, create a new entity
- Run the `i18nTextCollectorTask` with the `merge=true` option to avoid deleting unused entities
(which might still be relevant in older release branches)
### Converting your language files from 2.4 PHP format
The conversion from PHP format to YML is taken care of by a module
called [i18n_yml_converter](https://github.com/chillu/i18n_yml_converter).
## Download Translations from Transifex.com
We are managing our translations through a tool called [transifex.com](http://transifex.com).
Most modules are handled under the "silverstripe" user,
see [list of translatable modules](https://www.transifex.com/accounts/profile/silverstripe/).
Translations need to be reviewed before being committed,
which is a process that happens roughly once per month.
We're merging back translations into all supported release branches as well as the `master` branch.
The following script should be applied to the oldest release branch, and then merged forward into newer branches:
tx pull
# Manually review changes through git diff, then commit
git add lang/*
git commit -m "Updated translations"
Note: You can download your work right from Transifex in order to speed up the process for your desired language.
## JavaScript Translations
SilverStripe also supports translating strings in JavaScript (see [i18n](/topics/i18n)),
but there's a conversion step involved in order to get those translations syncing with Transifex.
Our translation files stored in `mymodule/javascript/lang/*.js` call `ss.i18n.addDictionary()` to add files.
ss.i18n.addDictionary('de', {"MyNamespace.MyKey": "My Translation"});
But Transifex only accepts structured formats like JSON.
{"MyNamespace.MyKey": "My Translation"}
First of all, you need to create those source files in JSON, and store them in `mymodule/javascript/lang/src/*.js`. In your `.tx/config` you can configure this path as a separate master location.
[main]
host = https://www.transifex.com
[silverstripe-mymodule.master]
file_filter = lang/<lang>.yml
source_file = lang/en.yml
source_lang = en
type = YML
[silverstripe-mymodule.master-js]
file_filter = javascript/lang/src/<lang>.js
source_file = javascript/lang/src/en.js
source_lang = en
type = KEYVALUEJSON
Now you can upload the source files via a normal `tx push`. Once translations come in,
you need to convert the source files back into the JS files SilverStripe can actually read.
This requires an installation of our [buildtools](https://github.com/silverstripe/silverstripe-buildtools).
tx pull
(cd .. && phing -Dmodule=mymodule translation-generate-javascript-for-module)
git add javascript/lang/*
git commit -m "Updated javascript translations"
# Related
* [i18n](/topics/i18n): Developer-level documentation of Silverstripe's i18n capabilities
* [contributing/translation](contributing/translation): Information for translators looking to contribute translations of the SilverStripe UI.
* [translatable](https://github.com/silverstripe/silverstripe-translatable): DataObject-interface powering the website-content translations
* ["Translatable ModelAdmin" module](http://silverstripe.org/translatablemodeladmin-module/): An extension which allows translations of DataObjects inside ModelAdmin

View File

@ -1,189 +0,0 @@
# Aspects
## Introduction
Aspect oriented programming is the idea that some logic abstractions can be
applied across various type hierarchies "after the fact", altering the
behaviour of the system without altering the code structures that are already
in place.
> In computing, aspect-oriented programming (AOP) is a programming paradigm
> which isolates secondary or supporting functions from the main program's
> business logic. It aims to increase modularity by allowing the separation of
> cross-cutting concerns, forming a basis for aspect-oriented software
> development.
[The wiki article](http://en.wikipedia.org/wiki/Aspect-oriented_programming)
provides a much more in-depth explanation!
In the context of this dependency injector, AOP is achieved thanks to PHP's
__call magic method combined with the Proxy design pattern.
## In practice
* Assume an existing service declaration exists called MyService
* An AopProxyService class instance is created, and the existing MyService object is bound in as a member variable of the AopProxyService class
* Objects are added to the AopProxyService instance's "beforeCall" and "afterCall" lists; each of these implements either the beforeCall or afterCall method
* When client code declares a dependency on MyService, it is actually passed in the AopProxyService instance
* Client code calls a method `myMethod` that it knows exists on MyService - this doesn't exist on AopProxyService, so __call is triggered.
* All classes bound to the beforeCall list are executed; if any explicitly returns 'false', `myMethod` is not executed.
* Otherwise, myMethod is executed
* All classes bound to the afterCall list are executed
## A worked example
To provide some context, imagine a situation where we want to direct all 'write' queries made in the system to a specific
database server, whereas all read queries can be handled by slave servers. A simplified implementation might look
like the following - note that this doesn't cover all cases used by SilverStripe so is not a complete solution, more
just a guide to how it would be used.
```
<?php
/**
* Redirects write queries to a specific database configuration
*
* @author <marcus@silverstripe.com.au>
* @license BSD License http://www.silverstripe.org/bsd-license
*/
class MySQLWriteDbAspect implements BeforeCallAspect {
/**
*
* @var MySQLDatabase
*/
public $writeDb;
public $writeQueries = array('insert','update','delete','replace');
public function beforeCall($proxied, $method, $args, &$alternateReturn) {
if (isset($args[0])) {
$sql = $args[0];
$code = isset($args[1]) ? $args[1] : E_USER_ERROR;
if (in_array(strtolower(substr($sql,0,strpos($sql,' '))), $this->writeQueries)) {
$alternateReturn = $this->writeDb->query($sql, $code);
return false;
}
}
}
}
```
To actually make use of this class, a few different objects need to be configured. First up, define the `writeDb`
object that's made use of above
```
WriteMySQLDatabase:
class: MySQLDatabase
constructor:
- type: MySQLDatabase
server: write.hostname.db
username: user
password: pass
database: write_database
```
This means that whenever something asks the injector for the `WriteMySQLDatabase` object, it'll receive an object of
type `MySQLDatabase`, configured to point at the 'write' database
Next, this should be bound into an instance of the aspect class
```
MySQLWriteDbAspect:
properties:
writeDb: %$WriteMySQLDatabase
```
Next, we need to define the database connection that will be used for all non-write queries
```
ReadMySQLDatabase:
class: MySQLDatabase
constructor:
- type: MySQLDatabase
server: slavecluster.hostname.db
username: user
password: pass
database: read_database
```
The final piece that ties everything together is the AopProxyService instance that will be used as the replacement
object when the framework creates the database connection in DB.php
```
MySQLDatabase:
class: AopProxyService
properties:
proxied: %$ReadMySQLDatabase
beforeCall:
query:
- %$MySQLWriteDbAspect
```
The two important parts here are in the `properties` declared for the object
- **proxied** : This is the 'read' database connectino that all queries should be initially directed through
- **beforeCall** : A hash of method\_name => array containing objects that are to be evaluated _before_ a call to the defined method\_name
Overall configuration for this would look as follows
```
Injector:
ReadMySQLDatabase:
class: MySQLDatabase
constructor:
- type: MySQLDatabase
server: slavecluster.hostname.db
username: user
password: pass
database: read_database
MySQLWriteDbAspect:
properties:
writeDb: %$WriteMySQLDatabase
WriteMySQLDatabase:
class: MySQLDatabase
constructor:
- type: MySQLDatabase
server: write.hostname.db
username: user
password: pass
database: write_database
MySQLDatabase:
class: AopProxyService
properties:
proxied: %$ReadMySQLDatabase
beforeCall:
query:
- %$MySQLWriteDbAspect
```
## Changing what a method returns
One major feature of an aspect is the ability to modify what is returned from the client's call to the proxied method.
As seen in the above example, the `beforeCall` method modifies the byref `&$alternateReturn` variable, and returns
`false` after doing so.
```
$alternateReturn = $this->writeDb->query($sql, $code);
return false;
```
By returning false from the `beforeCall()` method, the wrapping proxy class will _not_ call any additional `beforeCall`
handlers defined for the called method. Assigning the $alternateReturn variable also indicates to return that value
to the caller of the method.
Similarly the `afterCall()` aspect can be used to manipulate the value to be returned to the calling code. All the
`afterCall()` method needs to do is return a non-null value, and that value will be returned to the original calling
code instead of the actual return value of the called method.

View File

@ -1,36 +0,0 @@
# BBcode support
A bbcode tags help box shows when the "BBCode help" link is clicked. Javascript is required for this to work.
It has been encorporated as a modified version of PEAR's [HTML_BBCodeParser](http://pear.php.net/package/HTML_BBCodeParser)
BBCode is used by default in the [blog](http://silverstripe.org/blog-module) and
[forum](http://silverstripe.org/forum-module) modules.
## Usage
To add bbcode parsing to a template, instead of $Content use:
:::ss
$Content.Parse(BBCodeParser)
BBCode can be enabled in comments by adding the following to _config.php
:::php
PageComment::enableBBCode();
## Supported Tags
- [b]Bold[/b]
- [i]Italics[/i]
- [u]Underlined[/u]
- [s]Struck-out[/s]
- [color=blue]blue text[/color]
- [align=right]right aligned[/align]
- [code]Code block[/code]
- [email]you@yoursite.com[/email]
- [email=you@yoursite.com]Email[/email]
- [ulist][*]unordered item 1[/ulist]
- [img]http://www.website.com/image.jpg[/img]
- [url]http://www.website.com/[/url]
- [url=http://www.website.com/]Website[/url]

View File

@ -1,135 +0,0 @@
# Complex Table Field
## Introduction
<div class="warning" markdown="1">
This field is deprecated in favour of the new [GridField](/reference/grid-field) API.
</div>
Shows a group of DataObjects as a (readonly) tabular list (similiar to `[api:TableListField]`.)
You can specify limits and filters for the resultset by customizing query-settings (mostly the ID-field on the other
side of a one-to-many-relationship).
See `[api:TableListField]` for more documentation on the base-class
## Source Input
See `[api:TableListField]`.
## Setting Parent/Child-Relations
`[api:ComplexTableField]` tries to determine the parent-relation automatically by looking at the $has_one property on the listed
child, or the record loaded into the surrounding form (see getParentClass() and getParentIdName()). You can force a
specific parent relation:
:::php
$myCTF->setParentClass('ProductGroup');
## Customizing Popup
By default, getCMSFields() is called on the listed DataObject.
You can override this behaviour in various ways:
:::php
// option 1: implicit (left out of the constructor), chooses based on Object::useCustomClass or specific instance
$myCTF = new ComplexTableField(
$this,
'MyName',
'Product',
array('Price','Code')
)
// option 2: constructor
$myCTF = new ComplexTableField(
$this,
'MyName',
'Product',
array('Price','Code'),
new FieldList(
new TextField('Price')
)
)
// option 3: constructor function
$myCTF = new ComplexTableField(
$this,
'MyName',
'Product',
array('Price','Code'),
'getCustomCMSFields'
)
## Customizing Display & Functionality
If you don't want several functions to appear (e.g. no add-link), there's several ways:
* Use `ComplexTableField->setPermissions(array("show","edit"))` to limit the functionality without touching the template
(more secure). Possible values are "show","edit", "delete" and "add".
* Subclass `[api:ComplexTableField]` and override the rendering-mechanism
* Use `ComplexTableField->setTemplate()` and `ComplexTableField->setTemplatePopup()` to provide custom templates
### Customising fields and Requirements in the popup
There are several ways to customise the fields in the popup. Often you would want to display more information in the
popup as there is more real-estate for you to play with.
`[api:ComplexTableField]` gives you several options to do this. You can either
* Pass a FieldList in the constructor.
* Pass a String in the constructor.
The first will simply add the fieldlist to the form, and populate it with the source class.
The second will call the String as a method on the source class (Which should return a FieldList) of fields for the
Popup.
You can also customise Javascript which is loaded for the Lightbox. As Requirements::clear() is called when the popup is
instantiated, `[api:ComplexTableField]` will look for a function to gather any specific requirements that you might need on your
source class. (e.g. Inline Javascript or styling).
For this, create a function called "getRequirementsForPopup".
## Getting it working on the front end (not the CMS)
Sometimes you'll want to have a nice table on the front end, so you can move away from relying on the CMS for maintaing
parts of your site.
You'll have to do something like this in your form:
:::php
$tableField = new ComplexTableField(
$controller,
'Works',
'Work',
array(
'MyField' => 'My awesome field name'
),
'getPopupFields'
);
$tableField->setParentClass(false);
$fields = new FieldList(
new HiddenField('ID', ''),
$tableField
);
You have to hack in an ID on the form, as the CMS forms have this, and front end forms usually do not.
It's not a perfect solution, but it works relatively well to get a simple `[api:ComplexTableField]` up and running on the front
end.
To come: Make it a lot more flexible so tables can be easily used on the front end. It also needs to be flexible enough
to use a popup as well, out of the box.
## Subclassing
Most of the time, you need to override the following methods:
* ComplexTableField->sourceItems() - querying
* ComplexTableField->DetailForm() - form output
* ComplexTableField_Popup->saveComplexTableField() - saving

View File

@ -1,114 +0,0 @@
# Database Structure
SilverStripe is currently hard-coded to use a fix mapping between data-objects and the underlying database structure -
opting for "convention over configuration". This page details what that database structure is.
## Base tables
Each direct sub-class of `[api:DataObject]` will have its own table.
The following fields are always created.
* ID: Primary Key
* ClassName: An enumeration listing this data-class and all of its subclasses.
* Created: A date/time field set to the creation date of this record
* LastEdited: A date/time field set to the date this record was last edited
Every object of this class **or any of its subclasses** will have an entry in this table
### Extra Fields
* Every field listed in the data object's **$db** array will be included in this table.
* For every relationship listed in the data object's **$has_one** array, there will be an integer field included in the
table. This will contain the ID of the data-object being linked to. The database field name will be of the form
"(relationship-name)ID", for example, ParentID.
### ID Generation
When a new record is created, we don't use the database's built-in auto-numbering system. Instead, we generate a new ID
by adding 1 to the current maximum ID.
## Subclass tables
At SilverStripe's heart is an object-relational model. And a component of object-oriented data is **inheritance**.
Unfortunately, there is no native way of representing inheritance in a relational database. What we do is store the
data sub-classed objects across **multiple tables**.
For example, suppose we have the following set of classes:
* Class `[api:SiteTree]` extends `[api:DataObject]`: Title, Content fields
* Class `[api:Page]` extends `[api:SiteTree]`: Abstract field
* Class NewsSection extends `[api:SiteTree]`: *No special fields*
* Class NewsArticle extend `[api:Page]`: ArticleDate field
The data for the following classes would be stored across the following tables:
* `[api:SiteTree]`
* ID: Int
* ClassName: Enum('SiteTree', 'Page', 'NewsArticle')
* Created: Datetime
* LastEdited: Datetime
* Title: Varchar
* Content: Text
* `[api:Page]`
* ID: Int
* Abstract: Text
* NewsArticle
* ID: Int
* ArticleDate: Date
The way it works is this:
* "Base classes" are direct sub-classes of `[api:DataObject]`. They are always given a table, whether or not they have
special fields. This is called the "base table"
* The base table's ClassName field is set to class of the given record. It's an enumeration of all possible
sub-classes of the base class (including the base class itself)
* Each sub-class of the base object will also be given its own table, *as long as it has custom fields*. In the
example above, NewsSection didn't have its own data and 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]`.
To retrieve a news article, SilverStripe joins the `[api:SiteTree]`, `[api:Page]` and NewsArticle tables by their ID fields. We use a
left-join for robustness; if there is no matching record in Page, we can return a record with a blank Article field.
## Staging and versioning
[todo]
## Schema auto-generation
SilverStripe has a powerful tool for automatically building database schemas. We've designed it so that you should never have to build them manually.
To access it, visit http://localhost/dev/build?flush=1. This script will analyze the existing schema, compare it to what's required by your data classes, and alter the schema as required.
Put the ?flush=1 on the end if you've added PHP files, so that the rest of the system will find these new classes.
It will perform the following changes:
* Create any missing tables
* Create any missing fields
* Create any missing indexes
* Alter the field type of any existing fields
* Rename any obsolete tables that it previously created to _obsolete_(tablename)
It **won't** do any of the following
* Deleting tables
* Deleting fields
* Rename any tables that it doesn't recognize - so other applications can co-exist in the same database, as long as their table names don't match a SilverStripe data class.
## Related code
The information documented in this page is reflected in a few places in the code:
* `[api:DataObject]`
* requireTable() is responsible for specifying the required database schema
* instance_get() and instance_get_one() are responsible for generating the database queries for selecting data.
* write() is responsible for generating the database queries for writing data.
* `[api:Versioned]`
* augmentWrite() is responsible for altering the normal database writing operation to handle versions.
* augmentQuery() is responsible for altering the normal data selection queries to support versions.
* augmentDatabase() is responsible for specifying the altered database schema to support versions.
* `[api:MySQLDatabase]`: getNextID() is used when creating new objects; it also handles the mechanics of
updating the database to have the required schema.

View File

@ -1,293 +0,0 @@
# DataExtension
## Introduction
Extensions allow for adding additional functionality to a `[api:DataObject]` or
modifying existing functionality without the hassle of creating a subclass.
## Usage
Extensions are defined as subclasses of either `[api:DataExtension]` for
extending a `[api:DataObject]` subclass or the `[api:Extension]` class for non
DataObject subclasses (such as Controllers)
:::php
<?php
// mysite/code/MyMemberExtension.php
class MyMemberExtension extends DataExtension {
}
This defines your own extension where we can add our own functions, database
fields or other properties. After this class has been created, it
does not yet apply it to your object. Next you need to tell SilverStripe what
class you want to extend.
### Adding a extension to a built-in class
For example, you may might want to add a `MyMemberExtension` class to the
`[api:Member]` object to provide a custom method.
In order to active this extension, you need to add the following to your
[config.yml](/topics/configuration).
:::yml
Member:
extensions:
- MyMemberExtension
Alternatively, you can add extensions through PHP code (in your `_config.php`
file).
:::php
Member::add_extension('MyMemberExtension');
### Extending code to allow for extensions
If you're providing a module or working on code that may need to be extended by
other code, it can provide a *hook* which allows an Extension to modify the
results. This is done through the `[api:Object->extend()]` method.
:::php
public function myFunc() {
$foo = // ..
$this->extend('alterFoo', $foo);
return $foo;
}
In this example, the myFunc() method adds a hook to allow `DataExtension`
subclasses added to the instance to define an `alterFoo($foo)` method to modify
the result of the method.
The `$foo` parameter is passed by reference, as it is an object.
### Accessing the original Object from an Extension
In your extension class you can refer to the source object through the `owner`
property on the class.
:::php
<?php
class MyMemberExtension extends DataExtension {
public function alterFoo($foo) {
// outputs the original class
var_dump($this->owner);
}
}
### Checking to see if an Object has an Extension
To see what extensions are currently enabled on an object, you can use
`[api:Object->getExtensionInstances()]` and `[api:Object->hasExtension($extension)]`.
## Implementation
### Adding extra database fields
Extra database fields can be added with a extension in the same manner as if
they 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.
The function should return a map where the keys are the names of the static
variables to update:
:::php
<?php
class MyMemberExtension extends DataExtension {
private static $db = array(
'Position' => 'Varchar',
);
private static $has_one = array(
'Image' => 'Image',
);
}
### Modifying CMS Fields
The member class demonstrates an extension that allows you to update the default
CMS fields for an object in an extension:
:::php
<?php
class MyMemberExtension extends DataExtension {
private static $db = array(
'Position' => 'Varchar',
);
private static $has_one = array(
'Image' => 'Image',
);
public function updateCMSFields(FieldList $fields) {
$fields->push(new TextField('Position'));
$fields->push(new UploadField('Image', 'Profile Image'));
}
}
### Adding/modifying fields prior to extensions
User code can intervene in the process of extending cms fields by using
`beforeUpdateCMSFields` in its implementation of `getCMSFields`. This can be
useful in cases where user code will add fields to a dataobject that should be
present in the `$fields` parameter when passed to `updateCMSFields` in
extensions.
This method is preferred to disabling, enabling, and calling cms field
extensions manually.
:::php
function getCMSFields() {
$this->beforeUpdateCMSFields(function($fields) {
// Include field which must be present when updateCMSFields is called on extensions
$fields->addFieldToTab("Root.Main", new TextField('Detail', 'Details', null, 255));
});
$fields = parent::getCMSFields();
// ... additional fields here
return $fields;
}
### Object extension injection points
`Object` now has two additional methods, `beforeExtending` and `afterExtending`,
each of which takes a method name and a callback to be executed immediately
before and after `Object::extend()` is called on extensions.
This is useful in many cases where working with modules such as `Translatable`
which operate on `DataObject` fields that must exist in the `FieldList` at the
time that `$this->extend('UpdateCMSFields')` is called.
<div class="notice" markdown='1'>
Please note that each callback is only ever called once, and then cleared, so
multiple extensions to the same function require that a callback is registered
each time, if necessary.
</div>
Example: A class that wants to control default values during object
initialization. The code needs to assign a value if not specified in
`self::$defaults`, but before extensions have been called:
:::php
function __construct() {
$self = $this;
$this->beforeExtending('populateDefaults', function() use ($self) {
if(empty($self->MyField)) {
$self->MyField = 'Value we want as a default if not specified in $defaults, but set before extensions';
}
});
parent::__construct();
}
### Custom database generation
Some extensions are designed to transparently add more sophisticated
data-collection capabilities to your `DataObject`. For example, `[api:Versioned]`
adds version tracking and staging to any `DataObject` that it is applied to.
To do this, define an **augmentDatabase()** method on your extension. This will
be called when the database is rebuilt.
* You can query `$this->owner` for information about the data object, such as
the fields it has
* You can use **DB::requireTable($tableName, $fieldList, $indexList)** to set
up your new tables. This function takes care of creating, modifying, or leaving
tables as required, based on your desired schema.
### Custom write queries
If you have customised the generated database, then you probably want to change
the way that writes happen. This isused by `[api:Versioned]` to get an entry
written in ClassName_versions whenever an insert/update happens.
To do this, define the **augmentWrite(&$manipulation)** method. This method is
passed a manipulation array representing the write about to happen, and is able
to amend this as desired, since it is passed by reference.
### Custom relation queries
The other queries that you will want to customise are the selection queries,
called by get & get_one. For example, the Versioned object has code to redirect
every request to ClassName_live, if you are browsing the live site.
To do this, define the **augmentSQL(SQLQuery &$query)** method. Again, the
`$query` object is passed by reference and can be modified as needed by your
method. Instead of a manipulation array, we have a `[api:SQLQuery]` object.
### Additional methods
The other thing you may want to do with a extension is provide a method that can
be called on the `[api:DataObject]` that is being extended. For instance, you
may add a publish() method to every `[api:DataObject]` that is extended with
`[api:Versioned]`.
This is as simple as defining a method called publish() on your extension. Bear
in mind, however, that instead of $this, you should be referring to
`$this->owner`.
* $this = The `[api:DataExtension]` object.
* $this->owner = The related `[api:DataObject]` object.
If you want to add your own internal properties, you can add this to the
`[api:DataExtension]`, and these will be referred to as `$this->propertyName`.
Every `[api:DataObject]` has an associated `[api:DataExtension]` instance for
each class that it is extended by.
:::php
<?php
class Customer extends DataObject {
private static $has_one = array(
'Account' => 'Account'
);
private static $extensions = array(
'CustomerWorkflow'
);
}
class Account extends DataObject {
private static $db = array(
'IsMarkedForDeletion'=>'Boolean'
);
private static $has_many = array(
'Customers' => 'Customer'
);
}
class CustomerWorkflow extends DataExtension {
public function IsMarkedForDeletion() {
return (bool) $this->owner->Account()->IsMarkedForDeletion;
}
}
## API Documentation
* `[api:Extension]`
* `[api:DataExtension]`
## See Also
* [Injector](injector/)
* `[api:Object::useCustomClass]`

View File

@ -1,302 +0,0 @@
# DataObject
## Introduction
The `[api:DataObject]` class represents a single row in a database table,
following the ["Active Record"](http://en.wikipedia.org/wiki/Active_record_pattern) design pattern.
## Defining Properties
Properties defined through `DataObject::$db` map to table columns,
and can be declared as different [data-types](/topics/data-types).
## Loading and Saving Records
The basic principles around data persistence and querying for objects
is explained in the ["datamodel" topic](/topics/datamodel).
## Defining Form Fields
In addition to defining how data is persisted, the class can also
help with editing it by providing form fields through `DataObject->getCMSFields()`.
The resulting `[api:FieldList]` is the centrepiece of many data administration interfaces in SilverStripe.
Many customizations of the SilverStripe CMS interface start here,
by adding, removing or configuring fields.
Here is an example getCMSFields implementation:
:::php
class MyDataObject extends DataObject {
$db = array(
'IsActive' => 'Boolean'
);
public function getCMSFields() {
return new FieldList(
new CheckboxField('IsActive')
);
}
}
There's various [form field types](/references/form-field-types), for editing text, dates,
restricting input to numbers, and much more.
## Scaffolding Form Fields
The ORM already has a lot of information about the data represented by a `DataObject`
through its `$db` property, so why not use it to create form fields as well?
If you call the parent implementation, the class will use `[api:FormScaffolder]`
to provide reasonable defaults based on the property type (e.g. a checkbox field for booleans).
You can then further customize those fields as required.
:::php
class MyDataObject extends DataObject {
// ...
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->fieldByName('IsActive')->setTitle('Is active?');
return $fields;
}
}
The [ModelAdmin](/reference/modeladmin) class uses this approach to provide
data management interfaces with very little custom coding.
You can also alter the fields of built-in and module `DataObject` classes through
your own [DataExtension](/reference/dataextension), and a call to `DataExtension->updateCMSFields()`.
`[api::DataObject->beforeUpdateCMSFields()]` can also be used to interact with and add to automatically
scaffolded fields prior to being passed to extensions (See [DataExtension](/reference/dataextension)).
### Searchable Fields
The `$searchable_fields` property uses a mixed array format that can be used to further customize your generated admin
system. The default is a set of array values listing the fields.
Example: Getting predefined searchable fields
:::php
$fields = singleton('MyDataObject')->searchableFields();
Example: Simple Definition
:::php
class MyDataObject extends DataObject {
private static $searchable_fields = array(
'Name',
'ProductCode'
);
}
Searchable fields will be appear in the search interface with a default form field (usually a `[api:TextField]`) and a default
search filter assigned (usually an `[api:ExactMatchFilter]`). To override these defaults, you can specify additional information
on `$searchable_fields`:
:::php
class MyDataObject extends DataObject {
private static $searchable_fields = array(
'Name' => 'PartialMatchFilter',
'ProductCode' => 'NumericField'
);
}
If you assign a single string value, you can set it to be either a `[api:FormField]` or `[api:SearchFilter]`. To specify both, you can
assign an array:
:::php
class MyDataObject extends DataObject {
private static $searchable_fields = array(
'Name' => array(
'field' => 'TextField',
'filter' => 'PartialMatchFilter',
),
'ProductCode' => array(
'title' => 'Product code #',
'field' => 'NumericField',
'filter' => 'PartialMatchFilter',
),
);
}
To include relations (`$has_one`, `$has_many` and `$many_many`) in your search, you can use a dot-notation.
:::php
class Team extends DataObject {
private static $db = array(
'Title' => 'Varchar'
);
private static $many_many = array(
'Players' => 'Player'
);
private static $searchable_fields = array(
'Title',
'Players.Name',
);
}
class Player extends DataObject {
private static $db = array(
'Name' => 'Varchar',
'Birthday' => 'Date'
);
private static $belongs_many_many = array(
'Teams' => 'Team'
);
}
### Summary Fields
Summary fields can be used to show a quick overview of the data for a specific `[api:DataObject]` record. Most common use is
their display as table columns, e.g. in the search results of a `[api:ModelAdmin]` CMS interface.
Example: Getting predefined summary fields
:::php
$fields = singleton('MyDataObject')->summaryFields();
Example: Simple Definition
:::php
class MyDataObject extends DataObject {
private static $db = array(
'Name' => 'Text',
'OtherProperty' => 'Text',
'ProductCode' => 'Int',
);
private static $summary_fields = array(
'Name',
'ProductCode'
);
}
To include relations or field manipulations in your summaries, you can use a dot-notation.
:::php
class OtherObject extends DataObject {
private static $db = array(
'Title' => 'Varchar'
);
}
class MyDataObject extends DataObject {
private static $db = array(
'Name' => 'Text',
'Description' => 'HTMLText'
);
private static $has_one = array(
'OtherObject' => 'OtherObject'
);
private static $summary_fields = array(
'Name' => 'Name',
'Description.Summary' => 'Description (summary)',
'OtherObject.Title' => 'Other Object Title'
);
}
Non-textual elements (such as images and their manipulations) can also be used in summaries.
:::php
class MyDataObject extends DataObject {
private static $db = array(
'Name' => 'Text'
);
private static $has_one = array(
'HeroImage' => 'Image'
);
private static $summary_fields = array(
'Name' => 'Name',
'HeroImage.CMSThumbnail' => 'Hero Image'
);
}
## Permissions
Models can be modified in a variety of controllers and user interfaces,
all of which can implement their own security checks. But often it makes
sense to centralize those checks on the model, regardless of the used controller.
The API provides four methods for this purpose:
`canEdit()`, `canCreate()`, `canView()` and `canDelete()`.
Since they're PHP methods, they can contain arbitrary logic
matching your own requirements. They can optionally receive a `$member` argument,
and default to the currently logged in member (through `Member::currentUser()`).
Example: Check for CMS access permissions
class MyDataObject extends DataObject {
// ...
public function canView($member = null) {
return Permission::check('CMS_ACCESS_CMSMain', 'any', $member);
}
public function canEdit($member = null) {
return Permission::check('CMS_ACCESS_CMSMain', 'any', $member);
}
public function canDelete($member = null) {
return Permission::check('CMS_ACCESS_CMSMain', 'any', $member);
}
public function canCreate($member = null) {
return Permission::check('CMS_ACCESS_CMSMain', 'any', $member);
}
}
**Important**: These checks are not enforced on low-level ORM operations
such as `write()` or `delete()`, but rather rely on being checked in the invoking code.
The CMS default sections as well as custom interfaces like
[ModelAdmin](/reference/modeladmin) or [GridField](/reference/grid-field)
already enforce these permissions.
## Indexes
It is sometimes desirable to add indexes to your data model, whether to
optimize queries or add a uniqueness constraint to a field. This is done
through the `DataObject::$indexes` map, which maps index names to descriptor
arrays that represent each index. There's several supported notations:
:::php
# Simple
private static $indexes = array(
'<column-name>' => true
);
# Advanced
private static $indexes = array(
'<index-name>' => array('type' => '<type>', 'value' => '"<column-name>"')
);
# SQL
private static $indexes = array(
'<index-name>' => 'unique("<column-name>")'
);
The `<index-name>` can be an an arbitrary identifier in order to allow for more than one
index on a specific database column.
The "advanced" notation supports more `<type>` notations.
These vary between database drivers, but all of them support the following:
* `index`: Standard index
* `unique`: Index plus uniqueness constraint on the value
* `fulltext`: Fulltext content index
In order to use more database specific or complex index notations,
we also support raw SQL for as a value in the `$indexes` definition.
Keep in mind this will likely make your code less portable between databases.
Example: A combined index on a two fields.
:::php
private static $db = array(
'MyField' => 'Varchar',
'MyOtherField' => 'Varchar',
);
private static $indexes = array(
'MyIndexName' => array('type' => 'index', 'value' => '"MyField","MyOtherField"'),
);
## API Documentation
`[api:DataObject]`

View File

@ -1,151 +0,0 @@
# DateField
## Introduction
This `FormField` subclass lets you display an editable date, either in
a single text input field, or in three separate fields for day, month and year.
It also provides a calendar datepicker.
## Adding a DateField
The following example will add a simple DateField to your Page, allowing you to
enter a date manually.
:::php
class Page extends SiteTree {
private static $db = array(
'MyDate' => 'Date',
);
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->addFieldToTab(
'Root.Main',
$myDate = new DateField('MyDate', 'Enter a date')
);
return $fields;
}
}
## Custom Dateformat
You can define a custom dateformat for your Datefield based on [Zend_Date constants](http://framework.zend.com/manual/1.12/en/zend.date.constants.html).
:::php
// will display a date in the following format: 31-06-2012
DateField::create('MyDate')->setConfig('dateformat', 'dd-MM-yyyy');
## Min and Max Dates
Set the minimum and maximum allowed datevalues using the `min` and `max`
configuration settings (in ISO format or strtotime() compatible). Example:
:::php
DateField::create('MyDate')
->setConfig('min', '-7 days')
->setConfig('max', '2012-12-31')
## Separate Day/Month/Year Fields
The following setting will display your DateField as `three input fields` for
day, month and year separately. Any custom dateformat settings will be ignored.
HTML5 placeholders 'day', 'month' and 'year' are enabled by default.
:::php
DateField::create('MyDate')
->setConfig('dmyfields', true)
->setConfig('dmyseparator', '/') // set the separator
->setConfig('dmyplaceholders', 'true'); // enable HTML 5 Placeholders
## Formatting Hints
Its often not immediate apparent which format a field accepts,
and showing the technical format (e.g. `HH:mm:ss`) is of limited
use to the average user. An alternative is to show the current date
in the desired format alongside the field description as an example.
:::php
$dateField = DateField::create('MyDate');
// Show long format as text below the field
$dateField->setDescription(sprintf(
_t('FormField.Example', 'e.g. %s', 'Example format'),
Convert::raw2xml(Zend_Date::now()->toString($dateField->getConfig('dateformat')))
));
// Alternatively, set short format as a placeholder in the field
$dateField->setAttribute('placeholder', $dateField->getConfig('dateformat'));
Note: Fields scaffolded through `[api:DataObject::scaffoldCMSFields()]` automatically
have a description attached to them.
## Calendar Field
The following setting will add a Calendar to a single DateField, using the
`jQuery UI DatePicker widget`
:::php
DateField::create('MyDate')->setConfig('showcalendar', true);
### 'Safe' Dateformats to Use with the Calendar
The jQuery DatePicker doesn't support every constant available for Zend_Date.
If you choose to use the calendar, the following constants should at least be safe:
Constant | xxxxx
-------- | -----
d | numeric day of the month (without leading zero)
dd | numeric day of the month (with leading zero)
EEE | dayname, abbreviated
EEEE | dayname
M | numeric month of the year (without leading zero)
MM | numeric month of the year (with leading zero)
MMM | monthname, abbreviated
MMMM | monthname
y | year (4 digits)
yy | year (2 digits)
yyyy | year (4 digits)
### Calendar localization issues
Unfortunately the day- and monthname values in Zend Date do not always match
those in the existing jQuery UI locale files, so constants like `EEE` or `MMM`,
for day and monthnames could break validation. To fix this we had to slightly
alter the jQuery locale files, situated in
*/framework/thirdparty/jquery-ui/datepicker/i18n/*, to match Zend_Date.
At this moment not all locale files may be present. If a locale file is
missing, the DatePicker calendar will fallback to 'yyyy-MM-dd' whenever day-
and/or monthnames are used. After saving, the correct format will be displayed.
## Contributing jQuery Locale Files
If you find the jQuery locale file for your chosen locale is missing, the
following section will explain how to create one. If you wish to contribute
your file to the SilverStripe core, please check out the guide on
['contributing code'](http://doc.silverstripe.org/framework/en/trunk/misc/contributing/code).
### 1. Get the Sourcefile
You can find a list of locale files for the jQuery UI DatePicker
[in the jQuery source code](https://github.com/jquery/jquery-ui/tree/master/ui/i18n).
### 2. Find your Zend Locale File
The Zend locale files are located in */framework/thirdparty/Zend/Locale/Data/*.
Find the one that has the information for your locale.
### 3. Find the Date Values
You're looking for the `Gregorian` date values for monthnames and daynames in
the Zend locale file. Edit the DatePicker locale File so your *full day- and
monthnames* and *short monthnames* match. For your *short daynames*, use the
first three characters of the full name. Note that Zend dates are `case
sensitive`!
### 4. Filename
Use the original jQuery UI filename 'jquery.ui.datepicker-xx.js', where xx
stands for the locale.

View File

@ -1,87 +0,0 @@
# Director
## Introduction
`[api:Director]` is the first step in the "execution pipeline". It parses the
URL, matching it to one of a number of patterns, and determines the controller,
action and any argument to be used. It then runs the controller, which will
finally run the viewer and/or perform processing steps.
## Request processing
The `[api:Director]` is the entry point in Silverstring Framework for processing
a request. You can read through the execution steps in `[api:Director]``::direct()`,
but in short
* File uploads are first analysed to remove potentially harmful uploads (this
will likely change!)
* The `[api:SS_HTTPRequest]` object is created
* The session object is created
* The `[api:Injector]` is first referenced, and asks the registered `[api:RequestProcessor]`
to pre-process the request object. This allows for analysis of the current
request, and allow filtering of parameters etc before any of the core of the
application executes.
* The request is handled and response checked
* The `[api:RequestProcessor]` is called to post-process the request to allow
further filtering before content is sent to the end user
* The response is output
The framework provides the ability to hook into the request both before and
after it is handled to allow developers to bind in their own custom pre- or
post- request logic; see the `[api:RequestFilter]` to see how this can be used
to authenticate the request before the request is handled.
## Routing
You can influence the way URLs are resolved in the following ways
1. Adding rules to `[api:Director]` in `<yourproject>/_config/routes.yml`
2. Adding rules to `[api:Director]` in `<yourproject>/_config.php (deprecated)
3. Adding rules in your extended `[api:Controller]` class via the *$url_handlers*
static variable
See [controller](/topics/controller) for examples and explanations on how the
rules get processed for those methods.
### Routing Rules
SilverStripe comes with certain rules which map a URI to a `[api:Controller]`
class (e.g. *dev/* -> DevelopmentAdmin). These routes are either stored in
a routes.yml configuration file located a `_config` directory or inside a
`_config.php` file (deprecated).
To add your own custom routes for your application create a routes.yml file
in `<yourproject>/_config/routes.yml` with the following format:
:::yaml
---
Name: customroutes
After: framework/routes#coreroutes
---
Director:
rules:
'subscriptions/$Action' : 'SubscriptionController'
The [Controller](/topics/controller) documentation has a wide range of examples
and explanations on how the rules get processed for those methods.
See:
* [framework/_config/routes.yml](https://github.com/silverstripe/silverstripe-framework/blob/master/_config/routes.yml)
* [cms/_config/routes.yml](https://github.com/silverstripe/silverstripe-cms/blob/master/_config/routes.yml)
## Best Practices
* Checking for an Ajax-Request: Use Director::is_ajax() instead of checking
for $_REQUEST['ajax'].
## Links
* See `[api:ModelAsController]` class for details on controller/model-coupling
* See [execution-pipeline](/reference/execution-pipeline) for custom routing
## API Documentation
`[api:Director]`

View File

@ -1,104 +0,0 @@
# Execution Pipeline
## Introduction
This page documents all the steps from an URL request to the delivered page.
## .htaccess and RewriteRule
Silverstripe uses **[mod_rewrite](http://httpd.apache.org/docs/2.0/mod/mod_rewrite.html)** to deal with page requests.
So instead of having your normal everyday `index.php` file which tells all, you need to look elsewhere.
The basic .htaccess file after installing SilverStripe looks like this:
<file>
### SILVERSTRIPE START ###
<Files *.ss>
Order deny,allow
Deny from all
Allow from 127.0.0.1
</Files>
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule ^vendor(/|$) - [F,L,NC]
RewriteRule silverstripe-cache(/|$) - [F,L,NC]
RewriteRule composer\.(json|lock) - [F,L,NC]
RewriteCond %{REQUEST_URI} ^(.*)$
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule .* framework/main.php?url=%1 [QSA]
</IfModule>
### SILVERSTRIPE END ###
</file>
The `<Files>` section denies direct access to the template files from anywhere but the server itself.
The next section enables the rewriting engine and rewrites requests to `framework/main.php` if they meet the following
criteria:
* URI doesn't end in .gif, .jpg, .png, .css, or .js
* The requested file doesn't exist on the filesystem `framework/main.php` is called with the REQUEST_FILENAME (%1) as the `url` parameter and also appends the original
QUERY_STRING.
See the [mod_rewrite documentation](http://httpd.apache.org/docs/2.0/mod/mod_rewrite.html) for more information on how
mod_rewrite works.
## main.php
All requests go through `main.`php, which sets up the environment and then hands control over to `Director`.
## Director and URL patterns
main.php relies on `[api:Director]` to work out which controller should handle this request. `[api:Director]` will instantiate that
controller object and then call `[api:Controller::run()]`.
In general, the URL is build up as follows: `page/action/ID/otherID` - e.g. http://localhost/mypage/addToCart/12.
This will add an object with ID 12 to the cart.
When you create a function, you can access the ID like this:
:::php
public function addToCart ($request) {
$param = $request->allParams();
echo "my ID = " . $param["ID"];
$obj = MyProduct::get()->byID($param["ID"]);
$obj->addNow();
}
## Controllers and actions
`[api:Controller]`s are the building blocks of your application.
**See:** The API documentation for `[api:Controller]`
You can access the following controller-method with /team/signup
:::php
class Team extends DataObject {}
class Team_Controller extends Controller {
private static $allowed_actions = array('signup');
public function signup($id, $otherId) {
return $this->renderWith('MyTemplate');
}
}
## SSViewer template rendering
See [templates](/reference/templates) for information on the SSViewer template system.
## Flush requests
If `?flush=1` is requested in the URL, e.g. http://mysite.com?flush=1, this will trigger a call to `flush()` on
any classes that implement the `Flushable` interface.
See [reference documentation on Flushable](/reference/flushable) for more details.

View File

@ -1,418 +0,0 @@
# GridField
Gridfield is SilverStripe's implementation of data grids. Its main purpose is to display tabular data
in a format that is easy to view and modify. It can be thought of as a HTML table with some tricks.
It's built in a way that provides developers with an extensible way to display tabular data in a
table and minimise the amount of code that needs to be written.
In order to quickly get data-focused UIs up and running,
you might also be interested in the [/reference/modeladmin](ModelAdmin) class
which is driven largely by the `GridField` class explained here.
## Overview
The `GridField` is a flexible form field for creating tables of data. It was introduced in
SilverStripe 3.0 and replaced the `ComplexTableField`, `TableListField`, and `TableField` from
previous versions of SilverStripe.
Each GridField is built from a number of components. Without any components, a GridField has almost no
functionality. The components are responsible for formatting data to be readable and also modifying it.
A gridfield with only the `GridFieldDataColumn` component will display a set of read-only columns
taken from your list, without any headers or pagination. Large datasets don't fit to one
page, so you could add a `GridFieldPaginator` to paginatate the data. Sorting is supported by adding
a `GridFieldSortableHeader` that enables sorting on fields that can be sorted.
This document aims to explain the usage of GridFields with code examples.
<div class="hint" markdown='1'>
GridField can only be used with datasets that are of the type `SS_List` such as `DataList`
or `ArrayList`
</div>
## Creating a base GridField
A gridfield is often setup from a `Controller` that will output a form to the user. Even if there
are no other HTML input fields for gathering data from users, the gridfield itself must have a
`Form` to support interactions with it.
Here is an example where we display a basic gridfield with the default settings:
:::php
class GridController extends Page_Controller {
private static $allowed_actions = array('index', 'AllPages');
public function index(SS_HTTPRequest $request) {
$this->Content = $this->AllPages();
return $this->render();
}
public function AllPages() {
$gridField = new GridField('pages', 'All pages', SiteTree::get());
return new Form($this, "AllPages", new FieldList($gridField), new FieldList());
}
}
__Note:__ This is example code and the gridfield might not be styled nicely depending on the rest of
the css included.
This gridfield will only contain a single column with the `Title` of each page. Gridfield by default
uses the `DataObject::$display_fields` for guessing what fields to display.
Instead of modifying a core `DataObject` we can tell the gridfield which fields to display by
setting the display fields on the `GridFieldDataColumns` component.
:::php
public function AllPages() {
$gridField = new GridField('pages', 'All pages', SiteTree::get());
$dataColumns = $gridField->getConfig()->getComponentByType('GridFieldDataColumns');
$dataColumns->setDisplayFields(array(
'Title' => 'Title',
'URLSegment'=> 'URL',
'LastEdited' => 'Changed'
));
return new Form($this, "AllPages", new FieldList($gridField), new FieldList());
}
We will now move onto what the `GridFieldConfig`s are and how to use them.
----
## Configuration
A gridfields's behaviour and look all depends on what config we're giving it. In the above example
we did not specify one, so it picked a default config called `GridFieldConfig_Base`.
A config object is a container for `GridFieldComponents` which contain the actual functionality and
view for the gridfield.
A config object can be either injected as the fourth argument of the GridField constructor,
`$config` or set at a later stage by using a setter:
:::php
// On initialisation:
$gridField = new GridField('pages', 'All pages', SiteTree::get(), GridFieldConfig_Base::create());
// By a setter after initialisation:
$gridField = new GridField('pages', 'All pages', SiteTree::get());
$gridField->setConfig(GridFieldConfig_Base::create());
By default the `[api:GridFieldConfig_Base]` constructor takes a single parameter to specify the number
of items displayed on each page.
:::php
// I have lots of items, so increase the page size
$myConfig = GridFieldConfig_Base::create(40);
The default page size can also be tweaked via the config. (put in your mysite/_config/config.yml)
:::yaml
// For updating all gridfield defaults system wide
GridFieldPaginator:
default_items_per_page: 40
Note that for [/reference/modeladmin](ModelAdmin) sections the default 30 number of pages can be
controlled either by setting the base `ModelAdmin.page_length` config to the desired number, or
by overriding this value in a custom subclass.
The framework comes shipped with some base GridFieldConfigs:
### Table listing with GridFieldConfig_Base
A simple read-only and paginated view of records with sortable and searchable headers.
:::php
$gridField = new GridField('pages', 'All pages', SiteTree::get(), GridFieldConfig_Base::create());
The fields displayed are from `DataObject::getSummaryFields()`
### Viewing records with GridFieldConfig_RecordViewer
Similar to `GridFieldConfig_Base` with the addition support of:
- View read-only details of individual records.
The fields displayed in the read-only view is from `DataObject::getCMSFields()`
:::php
$gridField = new GridField('pages', 'All pages', SiteTree::get(), GridFieldConfig_RecordViewer::create());
### Editing records with GridFieldConfig_RecordEditor
Similar to `GridFieldConfig_RecordViewer` with the addition support of:
- Viewing and changing an individual records data.
- Deleting a record
:::php
$gridField = new GridField('pages', 'All pages', SiteTree::get(), GridFieldConfig_RecordEditor::create());
The fields displayed in the edit form are from `DataObject::getCMSFields()`
### Editing relations with GridFieldConfig_RelationEditor
Similar to `GridFieldConfig_RecordEditor`, but adds features to work on a record's has-many or
many-many relationships. As such, it expects the list used with the `GridField` to be a
`RelationList`. That is, the list returned by a has-many or many-many getter.
The relations can be:
- Searched for existing records and add a relationship
- Detach records from the relationship (rather than removing them from the database)
- Create new related records and automatically add them to the relationship.
:::php
$gridField = new GridField('images', 'Linked images', $this->Images(), GridFieldConfig_RelationEditor::create());
The fields displayed in the edit form are from `DataObject::getCMSFields()`
## Customizing Detail Forms
The `GridFieldDetailForm` component drives the record editing form which is usually configured
through the configs `GridFieldConfig_RecordEditor` and `GridFieldConfig_RelationEditor`
described above. It takes its fields from `DataObject->getCMSFields()`,
but can be customized to accept different fields via its `[api:GridFieldDetailForm->setFields()]` method.
The component also has the ability to load and save data stored on join tables
when two records are related via a "many_many" relationship, as defined through
`[api:DataObject::$many_many_extraFields]`. While loading and saving works transparently,
you need to add the necessary fields manually, they're not included in the `getCMSFields()` scaffolding.
These extra fields act like usual form fields, but need to be "namespaced"
in order for the gridfield logic to detect them as fields for relation extradata,
and to avoid clashes with the other form fields.
The namespace notation is `ManyMany[<extradata-field-name>]`, so for example
`ManyMany[MyExtraField]`.
Example:
:::php
class Player extends DataObject {
private static $db = array('Name' => 'Text');
public static $many_many = array('Teams' => 'Team');
public static $many_many_extraFields = array(
'Teams' => array('Position' => 'Text')
);
public function getCMSFields() {
$fields = parent::getCMSFields();
if($this->ID) {
$teamFields = singleton('Team')->getCMSFields();
$teamFields->addFieldToTab(
'Root.Main',
// Please follow the "ManyMany[<extradata-name>]" convention
new TextField('ManyMany[Position]', 'Current Position')
);
$config = GridFieldConfig_RelationEditor::create();
$config->getComponentByType('GridFieldDetailForm')->setFields($teamFields);
$gridField = new GridField('Teams', 'Teams', $this->Teams(), $config);
$fields->findOrMakeTab('Root.Teams')->replaceField('Teams', $gridField);
}
return $fields;
}
}
class Team extends DataObject {
private static $db = array('Name' => 'Text');
public static $many_many = array('Players' => 'Player');
}
## GridFieldComponents
The `GridFieldComponent` classes are the actual workers in a gridfield. They can be responsible for:
- Output some HTML to be rendered
- Manipulate data
- Recieve actions
- Display links
Components are added and removed from a config by setters and getters.
:::php
$config = GridFieldConfig::create();
// Add the base data columns to the gridfield
$config->addComponent(new GridFieldDataColumns());
$gridField = new GridField('pages', 'All pages', SiteTree::get(), $config);
It's also possible to insert a component before another component.
:::php
$config->addComponent(new GridFieldFilterHeader(), 'GridFieldDataColumns');
Adding multiple components in one call:
:::php
$config->addComponents(new GridFieldDataColumns(), new GridFieldToolbarHeader());
Removing a component:
:::php
$config->removeComponentsByType('GridFieldToolbarHeader');
For more information, see the [API for GridFieldConfig](http://api.silverstripe.org/3.0/framework/GridFieldConfig.html).
Here is a list of components for generic use:
- `[api:GridFieldToolbarHeader]`
- `[api:GridFieldSortableHeader]`
- `[api:GridFieldFilterHeader]`
- `[api:GridFieldDataColumns]`
- `[api:GridFieldDeleteAction]`
- `[api:GridFieldViewButton]`
- `[api:GridFieldEditButton]`
- `[api:GridFieldExportButton]`
- `[api:GridFieldPrintButton]`
- `[api:GridFieldPaginator]`
- `[api:GridFieldDetailForm]`
## Flexible Area Assignment through Fragments
GridField layouts can contain many components other than the table itself,
for example a search bar to find existing relations, a button to add those,
and buttons to export and print the current data. The GridField has certain
defined areas called "fragments" where these components can be placed.
The goal is for multiple components to share the same space, for example a header row.
Built-in components:
- `header`/`footer`: Renders in a `<thead>`/`<tfoot>`, should contain table markup
- `before`/`after`: Renders before/after the actual `<table>`
- `buttons-before-left`/`buttons-before-right`/`buttons-after-left`/`buttons-after-right`:
Renders in a shared row before the table. Requires [api:GridFieldButtonRow].
These built-ins can be used by passing the fragment names into the constructor
of various components. Note that some [api:GridFieldConfig] classes
will already have rows added to them. The following example will add a print button
at the bottom right of the table.
:::php
$config->addComponent(new GridFieldButtonRow('after'));
$config->addComponent(new GridFieldPrintButton('buttons-after-right'));
Further down we'll explain how to write your own components using fragments.
## Creating a custom GridFieldComponent
A single component often uses a number of interfaces.
### GridField_HTMLProvider
Provides HTML for the header/footer rows in the table or before/after the template.
Examples:
- A header html provider displays a header before the table
- A pagination html provider displays pagination controls under the table
- A filter html fields displays filter fields on top of the table
- A summary html field displays sums of a field at the bottom of the table
### GridField_ColumnProvider
Add a new column to the table display body, or modify existing columns. Used once per record/row.
Examples:
- A data columns provider that displays data from the list in rows and columns.
- A delete button column provider that adds a delete button at the end of the row
### GridField_ActionProvider
Action providers runs actions, some examples are:
- A delete action provider that deletes a DataObject.
- An export action provider that will export the current list to a CSV file.
### GridField_DataManipulator
Modifies the data list. In general, the data manipulator will make use of `GridState` variables
to decide how to modify the data list.
Examples:
- A paginating data manipulator can apply a limit to a list (show only 20 records)
- A sorting data manipulator can sort the Title in a descending order.
### GridField_URLHandler
Sometimes an action isn't enough, we need to provide additional support URLs for the grid. It
has a list of URL's that it can handle and the GridField passes request on to URLHandlers on matches.
Examples:
- A pop-up form for editing a record's details.
- JSON formatted data used for javascript control of the gridfield.
## GridField_FormAction
This object is used for creating actions buttons, for example a delete button. When a user clicks on
a FormAction, the gridfield finds a `GridField_ActionProvider` that listens on that action.
`GridFieldDeleteAction` have a pretty basic implementation of how to use a Form action.
## GridField_SaveHandler
This is used to create a handler that is called when a form containing the grid
field is saved into a record. This is useful for performing actions when saving
the record.
### GridState
Gridstate is a class that is used to contain the current state and actions on the gridfield. It's
transfered between page requests by being inserted as a hidden field in the form.
A GridFieldComponent sets and gets data from the GridState.
## Permissions
Since GridField is mostly used in the CMS, the controller managing a GridField instance
will already do some permission checks for you, and can decline display or executing
any logic on your field.
If you need more granular control, e.g. to consistently deny non-admins from deleting
records, use the `DataObject->can...()` methods
(see [DataObject permissions](/reference/dataobject#permissions)).
## Creating your own Fragments
Fragments are designated areas within a GridField which can be shared between component templates.
You can define your own fragments by using a `\$DefineFragment' placeholder in your components' template.
This example will simply create an area rendered before the table wrapped in a simple `<div>`.
:::php
class MyAreaComponent implements GridField_HTMLProvider {
public function getHTMLFragments( $gridField) {
return array(
'before' => '<div class="my-area">$DefineFragment(my-area)</div>'
);
}
}
We're returning raw HTML from the component, usually this would be handled by a SilverStripe template.
Please note that in templates, you'll need to escape the dollar sign on `$DefineFragment`:
These are specially processed placeholders as opposed to native template syntax.
Now you can add other components into this area by returning them as an array from
your [api:GridFieldComponent->getHTMLFragments()] implementation:
:::php
class MyShareLinkComponent implements GridField_HTMLProvider {
public function getHTMLFragments( $gridField) {
return array(
'my-area' => '<a href>...</a>'
);
}
}
Your new area can also be used by existing components, e.g. the [api:GridFieldPrintButton]
:::php
new GridFieldPrintButton('my-component-area')
## Related
* [ModelAdmin: A UI driven by GridField](/reference/modeladmin)
* [Tutorial 5: Dataobject Relationship Management](/tutorials/5-dataobject-relationship-management)
* [How to add a custom action to a GridField row](/howto/gridfield-rowaction)

View File

@ -1,36 +0,0 @@
# Reference #
Reference articles complement our auto-generated [API docs](http://api.silverstripe.org) in providing deeper introduction into a specific API.
* [BBCode](bbcode): Extensible shortcode syntax
* [CMS Architecture](cms-architecture): A quick run down to get you started with creating your own data management interface
* [ComplexTableField](complextablefield): Manage records and their relations inside the CMS
* [Database Structure](database-structure): Conventions and best practices for database tables and fields
* [DataExtension](dataextension): A "mixin" system allowing to extend core classes
* [DataObject](dataobject): Base class for database records
* [Director](director): Routes URLs and handles HTTP requests
* [Execution Pipeline](execution-pipeline): Detailed look on the way an HTTP request takes through the system
* [Form Field Types](form-field-types): Highlevel overview of field classes
* [GridField](grid-field): The GridField is a flexible form field for creating tables of data.
* [Image](image): Represents an image object in templates and PHP code
* [Injector](injector): The [api:Injector] class is the central manager of inter-class dependencies in the SilverStripe Framework
* [Member](member): The "user" object forms the base for our security/permission moel
* [ModelAdmin](modeladmin): Manage arbitrary data in a simple CRUD (create/read/update/delete) interface
* [Partial Caching](partial-caching): Cache complex parts of templates for better performance
* [Permission](permission): Database-backed permission model
* [Requirements](requirements): Include CSS and JavaScript files in templates and controllers
* [RestfulService](restfulservice): Consume Restful APIs with this client
* [RSSFeed](rssfeed): Expose any database records as an RSS feed
* [SearchContext](searchcontext): Wraps search queries and forms into an object
* [Site Reports](site-reports): Tabular reports in a specialized CMS interface
* [SiteConfig](siteconfig): Global configuration stored in the database
* [SiteTree](sitetree): Base class for a "page" in the CMS
* [SQLQuery](sqlquery): Wrapper around a SQL query allowing modification before execution
* [StaticPublisher](staticpublisher): Export a page tree as static HTML for better performance and portability
* [TableField](tablefield): Add and edit records with inline edits in this form field
* [TableListField](tablelistfield): View and delete records in the CMS
* [Templates Formal Syntax](templates-formal-syntax): Maximum level of detail of how the template engine works
* [Templates Upgrading Guide](templates-upgrading-guide): Differences between SilverStripe 2 and SilverStripe 3 template language
* [Templates](templates): Introduction to SilverStripe templates
* [Typography](typography): CSS file to enable WYSIWYG previews in the CMS
* [urlvariabletools](urlvariabletools): Debug and maintenance switches

View File

@ -1,232 +0,0 @@
# Injector
## Introduction
The `[api:Injector]` class is the central manager of inter-class dependencies
in the SilverStripe Framework. In its simplest form it can be considered as
a replacement for Object::create and singleton() calls, but also offers
developers the ability to declare the dependencies a class type has, or
to change the nature of the dependencies defined by other developers.
Some of the goals of dependency injection are
* Simplified instantiation of objects
* Providing a uniform way of declaring and managing inter-object dependencies
* Making class dependencies configurable
* Simplifying the process of overriding or replacing core behaviour
* Improve testability of code
* Promoting abstraction of logic
A key concept of the injector is whether the object should be managed as
* A pseudo-singleton, in that only one item will be created for a particular
identifier (but the same class could be used for multiple identifiers)
* A prototype, where the same configuration is used, but a new object is
created each time
* unmanaged, in which case a new object is created and injected, but no
information about its state is managed.
These concepts will be discussed further below
## Some simple examples
The following sums up the simplest usage of the injector
Assuming no other configuration is specified
:::php
$object = Injector::inst()->create('ClassName');
Creates a new object of type ClassName
:::php
$object = Injector::inst()->create('ClassName');
$object2 = Injector::inst()->create('ClassName');
$object !== $object2;
Repeated calls to create() create a new class each time. To create a singleton
object instead, use **get()**
:::php
// sets up ClassName as a singleton
$object = Injector::inst()->get('ClassName');
$object2 = Injector::inst()->get('ClassName');
$object === $object2;
The subsequent call returns the SAME object as the first call.
:::php
class MyController extends Controller {
// both of these properties will be automatically
// set by the injector on object creation
public $permissions;
public $textProperty;
static $dependencies = array(
'textProperty' => 'a string value',
'permissions' => '%$PermissionService',
);
}
$object = Injector::inst()->get('MyController');
// results in
$object->permissions instanceof PermissionService;
$object->textProperty == 'a string value';
In this case, on creation of the MyController object, the injector will
automatically instantiate the PermissionService object and set it as
the **permissions** property.
## Configuring objects managed by the dependency injector
The above declarative style of dependency management would cover a large
portion of usecases, but more complex dependency structures can be defined
via configuration files.
Configuration can be specified for two areas of dependency management
* Defining dependency overrides for individual classes
* Injector managed 'services'
### Factories
Some services require non-trivial construction which means they must be created by a factory class. To do this, create
a factory class which implements the `[api:SilverStripe\Framework\Injector\Factory]` interface. You can then specify
the `factory` key in the service definition, and the factory service will be used.
An example using the `MyFactory` service to create instances of the `MyService` service is shown below:
:::yml
Injector:
MyService:
factory: MyFactory
MyFactory:
class: MyFactoryImplementation
:::php
class MyFactoryImplementation implements SilverStripe\Framework\Injector\Factory {
public function create($service, array $params = array()) {
return new MyServiceImplementation();
}
}
// Will use MyFactoryImplementation::create() to create the service instance.
$instance = Injector::inst()->get('MyService');
### Dependency overrides
To override the **static $dependency;** declaration for a class, you could
define the following configuration file (module/_config/MyController.yml)
name: MyController
---
MyController:
dependencies:
textProperty: a string value
permissions: %$PermissionService
At runtime, the **dependencies** configuration would be read and used in
place of that declared on the object.
### Managed objects
Simple dependencies can be specified by the **dependencies**, but more complex
configurations are possible by specifying constructor arguments, or by
specifying more complex properties such as lists.
These more complex configurations are defined in 'Injector' configuration
blocks and are read by the injector at runtime
Assuming a class structure such as
:::php
class RestrictivePermissionService {
private $database;
public function setDatabase($d) {
$this->database = $d;
}
}
class MySQLDatabase {
private $username;
private $password;
public function __construct($username, $password) {
$this->username = $username;
$this->password = $password;
}
}
and the following configuration
name: MyController
---
MyController:
dependencies:
permissions: %$PermissionService
Injector:
PermissionService:
class: RestrictivePermissionService
properties:
database: %$MySQLDatabase
MySQLDatabase
constructor:
0: 'dbusername'
1: 'dbpassword'
calling
:::php
// sets up ClassName as a singleton
$controller = Injector::inst()->get('MyController');
would
* Create an object of type MyController
* Look through the **dependencies** and call get('PermissionService')
* Load the configuration for PermissionService, and create an object of
type RestrictivePermissionService
* Look at the properties to be injected and look for the config for
MySQLDatabase
* Create a MySQLDatabase class, passing dbusername and dbpassword as the
parameters to the constructor
### Testing with Injector in a sandbox environment
In situations where injector states must be temporarily overridden, it is possible
to create nested Injector instances which may be later discarded, reverting the
application to the original state.
This is useful when writing test cases, as certain services may be necessary to
override for a single method call.
For instance, a temporary service can be registered and unregistered as below:
:::php
// Setup default service
Injector::inst()->registerService(new LiveService(), 'ServiceName');
// Test substitute service temporarily
Injector::nest();
Injector::inst()->registerService(new TestingService(), 'ServiceName');
$service = Injector::inst()->get('ServiceName');
// ... do something with $service
Injector::unnest();
// ... future requests for 'ServiceName' will return the LiveService instance
### What are Services?
Without diving too deep down the rabbit hole, the term 'Service' is commonly
used to describe a piece of code that acts as an interface between the
controller layer and model layer of an MVC architecture. Rather than having
a controller action directly operate on data objects, a service layer provides
that logic abstraction, stopping controllers from implementing business logic,
and keeping that logic packaged in a way that is easily reused from other
classes.
By default, objects are managed like a singleton, in that there is only one
object instance used for a named service, and all references to that service
are returned the same object.

View File

@ -1,301 +0,0 @@
# ModelAdmin
## Introduction
Provides a simple way to utilize the SilverStripe CMS UI with your own data models,
and create searchable list and edit views of them, and even providing import and export of your data.
It uses the framework's knowledge about the model to provide sensible defaults,
allowing you to get started in a couple of lines of code,
while still providing a solid base for customization.
The interface is mainly powered by the [GridField](/reference/grid-field) class,
which can also be used in other CMS areas (e.g. to manage a relation on a `SiteTree`
record in the standard CMS interface).
## Setup
Let's assume we want to manage a simple product listing as a sample data model:
A product can have a name, price, and a category.
:::php
class Product extends DataObject {
private static $db = array('Name' => 'Varchar', 'ProductCode' => 'Varchar', 'Price' => 'Currency');
private static $has_one = array('Category' => 'Category');
}
class Category extends DataObject {
private static $db = array('Title' => 'Text');
private static $has_many = array('Products' => 'Product');
}
To create your own `ModelAdmin`, simply extend the base class,
and edit the `$managed_models` property with the list of
data objects you want to scaffold an interface for.
The class can manage multiple models in parallel, if required.
We'll name it `MyAdmin`, but the class name can be anything you want.
:::php
class MyAdmin extends ModelAdmin {
private static $managed_models = array('Product','Category'); // Can manage multiple models
private static $url_segment = 'products'; // Linked as /admin/products/
private static $menu_title = 'My Product Admin';
}
This will automatically add a new menu entry to the CMS, and you're ready to go!
Try opening http://localhost/admin/products/?flush=all.
## Permissions
Each new `ModelAdmin` subclass creates its own [permission code](/reference/permission),
for the example above this would be `CMS_ACCESS_MyAdmin`. Users with access to the CMS
need to have this permission assigned through `admin/security/` in order to gain
access to the controller (unless they're admins).
The `DataObject` API has more granular permission control, which is enforced in ModelAdmin by default.
Available checks are `canEdit()`, `canCreate()`, `canView()` and `canDelete()`.
Models check for administrator permissions by default. For most cases,
less restrictive checks make sense, e.g. checking for general CMS access rights.
:::php
class Category extends DataObject {
// ...
public function canView($member = null) {
return Permission::check('CMS_ACCESS_CMSMain', 'any', $member);
}
public function canEdit($member = null) {
return Permission::check('CMS_ACCESS_CMSMain', 'any', $member);
}
public function canDelete($member = null) {
return Permission::check('CMS_ACCESS_CMSMain', 'any', $member);
}
public function canCreate($member = null) {
return Permission::check('CMS_ACCESS_CMSMain', 'any', $member);
}
## Search Fields
ModelAdmin uses the [SearchContext](/reference/searchcontext) class to provide
a search form, as well as get the searched results. Every DataObject can have its own context,
based on the fields which should be searchable. The class makes a guess at how those fields
should be searched, e.g. showing a checkbox for any boolean fields in your `$db` definition.
To remove, add or modify searchable fields, define a new `[api:DataObject::$searchable_fields]`
static on your model class (see [SearchContext](/reference/searchcontext) docs for details).
:::php
class Product extends DataObject {
// ...
private static $searchable_fields = array(
'Name',
'ProductCode'
// leaves out the 'Price' field, removing it from the search
);
}
For a more sophisticated customization, for example configuring the form fields
for the search form, override `DataObject->getCustomSearchContext()` on your model class.
## Result Columns
The results are shown in a tabular listing, powered by the [GridField](/reference/grid-field),
more specifically the `[api:GridFieldDataColumns]` component.
It looks for a `[api:DataObject::$summary_fields]` static on your model class,
where you can add or remove columns. To change the title, use `[api:DataObject::$field_labels]`.
:::php
class Product extends DataObject {
// ...
private static $field_labels = array(
'Price' => 'Cost' // renames the column to "Cost"
);
private static $summary_fields = array(
'Name',
'Price',
// leaves out the 'ProductCode' field, removing the column
);
}
## Results Customization
The results are retrieved from `[api:SearchContext->getResults()]`,
based on the parameters passed through the search form.
If no search parameters are given, the results will show every record.
Results are a `[api:DataList]` instance, so can be customized by additional
SQL filters, joins, etc (see [datamodel](/topics/datamodel) for more info).
For example, we might want to exclude all products without prices in our sample `MyAdmin` implementation.
:::php
class MyAdmin extends ModelAdmin {
// ...
public function getList() {
$list = parent::getList();
// Always limit by model class, in case you're managing multiple
if($this->modelClass == 'Product') {
$list = $list->exclude('Price', '0');
}
return $list;
}
}
You can also customize the search behaviour directly on your `ModelAdmin` instance.
For example, we might want to have a checkbox which limits search results to expensive products (over $100).
:::php
class MyAdmin extends ModelAdmin {
// ...
public function getSearchContext() {
$context = parent::getSearchContext();
if($this->modelClass == 'Product') {
$context->getFields()->push(new CheckboxField('q[ExpensiveOnly]', 'Only expensive stuff'));
}
return $context;
}
public function getList() {
$list = parent::getList();
$params = $this->request->requestVar('q'); // use this to access search parameters
if($this->modelClass == 'Product' && isset($params['ExpensiveOnly']) && $params['ExpensiveOnly']) {
$list = $list->exclude('Price:LessThan', '100');
}
return $list;
}
}
### GridField Customization
To alter how the results are displayed (via `[api:GridField]`), you can also overload the `getEditForm()` method. For example, to add a new component.
:::php
class MyAdmin extends ModelAdmin {
private static $managed_models = array('Product','Category');
// ...
public function getEditForm($id = null, $fields = null) {
$form = parent::getEditForm($id, $fields);
// $gridFieldName is generated from the ModelClass, eg if the Class 'Product'
// is managed by this ModelAdmin, the GridField for it will also be named 'Product'
$gridFieldName = $this->sanitiseClassName($this->modelClass);
$gridField = $form->Fields()->fieldByName($gridFieldName);
$gridField->getConfig()->addComponent(new GridFieldFilterHeader());
return $form;
}
}
The above example will add the component to all `GridField`s (of all managed models). Alternatively we can also add it to only one specific `GridField`:
:::php
class MyAdmin extends ModelAdmin {
private static $managed_models = array('Product','Category');
// ...
public function getEditForm($id = null, $fields = null) {
$form = parent::getEditForm($id, $fields);
$gridFieldName = 'Product';
$gridField = $form->Fields()->fieldByName($gridFieldName);
if ($gridField) {
$gridField->getConfig()->addComponent(new GridFieldFilterHeader());
}
return $form;
}
}
## Managing Relationships
Has-one relationships are simply implemented as a `[api:DropdownField]` by default.
Consider replacing it with a more powerful interface in case you have many records
(through customizing `[api:DataObject->getCMSFields]`).
Has-many and many-many relationships are usually handled via the [GridField](/reference/grid-field) class,
more specifically the `[api:GridFieldAddExistingAutocompleter]` and `[api:GridFieldRelationDelete]` components.
They provide a list/detail interface within a single record edited in your ModelAdmin.
The [GridField](/reference/grid-field) docs also explain how to manage
extra relation fields on join tables through its detail forms.
The autocompleter can also search attributes on relations,
based on the search fields defined through `[api:DataObject::searchableFields()]`.
## Permissions
`ModelAdmin` respects the permissions set on the model, through methods on your `DataObject` implementations:
`canView()`, `canEdit()`, `canDelete()`, and `canCreate`.
In terms of access control to the interface itself, every `ModelAdmin` subclass
creates its own "[permission code](/reference/permissions)", which can be assigned
to groups through the `admin/security` management interface. To further limit
permission, either override checks in `ModelAdmin->init()`, or define
more permission codes through the `ModelAdmin::$required_permission_codes` static.
## Data Import
The `ModelAdmin` class provides import of CSV files through the `[api:CsvBulkLoader]` API.
which has support for column mapping, updating existing records,
and identifying relationships - so its a powerful tool to get your data into a SilverStripe database.
By default, each model management interface allows uploading a CSV file
with all columns autodetected. To override with a more specific importer implementation,
use the `[api:ModelAdmin::$model_importers] static.
## Data Export
Export is also available, although at the moment only to the CSV format,
through a button at the end of a results list. You can also export search results.
It is handled through the `[api:GridFieldExportButton]` component.
To customize the exported columns, create a new method called `getExportFields` in your `ModelAdmin`:
:::php
class MyAdmin extends ModelAdmin {
// ...
public function getExportFields() {
return array(
'Name' => 'Name',
'ProductCode' => 'Product Code',
'Category.Title' => 'Category'
);
}
}
Dot syntax support allows you to select a field on a related `has_one` object.
## Extending existing ModelAdmins
Sometimes you'll work with ModelAdmins from other modules, e.g. the product management
of an ecommerce module. To customize this, you can always subclass. But there's
also another tool at your disposal: The `[api:Extension]` API.
:::php
class MyAdminExtension extends Extension {
// ...
public function updateEditForm(&$form) {
$form->Fields()->push(/* ... */)
}
}
Now enable this extension through your `[config.yml](/topics/configuration)` file.
:::yml
MyAdmin:
extensions:
- MyAdminExtension
The following extension points are available: `updateEditForm()`, `updateSearchContext()`,
`updateSearchForm()`, `updateList()`, `updateImportForm`.
## Customizing the interface
Interfaces like `ModelAdmin` can be customized in many ways:
* JavaScript behaviour (e.g. overwritten jQuery.entwine rules)
* CSS styles
* HTML markup through templates
In general, use your `ModelAdmin->init()` method to add additional requirements
through the [Requirements](/reference/requirements) API.
For an introduction how to customize the CMS templates, see our [CMS Architecture Guide](/reference/cms-architecture).
## Related
* [GridField](../reference/grid-field): The UI component powering ModelAdmin
* [Tutorial 5: Dataobject Relationship Management](../tutorials/5-dataobject-relationship-management)
* `[api:SearchContext]`
* [genericviews Module](http://silverstripe.org/generic-views-module)
* [Presentation about ModelAdmin at SupperHappyDevHouse Wellington](http://www.slideshare.net/chillu/modeladmin-in-silverstripe-23)
* [Reference: CMS Architecture](../reference/cms-architecture)
* [Howto: Extend the CMS Interface](../howto/extend-cms-interface)

View File

@ -1,210 +0,0 @@
# Requirements
## Introduction
The requirements class takes care of including CSS and JavaScript into your applications. This is preferred to
hardcoding any references in the `<head>`-tag of your template, as it enables a more flexible handling.
## Including inside PHP Code
It is common practice to include most Requirements either in the *init()*-method of your [controller](/topics/controller), or
as close to rendering as possible (e.g. in `[api:FormField]`
:::php
Requirements::javascript("cms/javascript/LeftAndMain.js");
Requirements::css("cms/css/TreeSelector.css");
If you're using the CSS method a second argument can be used. This argument defines the 'media' attribute of the `<link>`
element, so you can define 'screen' or 'print' for example.
Requirements::css("cms/css/TreeSelector.css", "screen,projection");
## Including inside Template files
If you do not want to touch the PHP (for example you are constructing a generic theme) then you can include a file via
the templates
<% require css("cms/css/TreeSelector.css") %>
<% require themedCSS("TreeSelector") %>
<% require javascript("cms/javascript/LeftAndMain.js") %>
## Combining Files
You can concatenate several CSS or javascript files into a single dynamically generated file. This increases performance
reducing HTTP requests. Note that for debugging purposes combined files is disabled in devmode.
:::php
// supports CSS + JS
Requirements::combine_files(
'foobar.js',
array(
'mysite/javascript/foo.js',
'mysite/javascript/bar.js',
)
);
By default it stores the generated file in the assets/ folder but you can configure this by pointing
the `Requirements.combined_files_folder` configuration setting to a specific folder.
If SilverStripe doesn't have permissions on your server to write these files it will default back to including them
individually .
You can also combine CSS files into a media-specific stylesheets as you would with the `Requirements::css` call - use
the third paramter of the `combine_files` function:
:::php
$printStylesheets = array(
"$themeDir/css/print_HomePage.css",
"$themeDir/css/print_Page.css",
);
Requirements::combine_files('print.css', $printStylesheets, 'print');
## Custom Inline Scripts
You can also quote custom script directly. This may seem a bit ugly, but is useful when you need to transfer some kind
of 'configuration' from the database to the javascript/css. You'll need to use the "heredoc" syntax to quote JS and
CSS, this is generally speaking the best way to do these things - it clearly marks the copy as belonging to a different
language.
:::php
Requirements::customScript(<<<JS
alert("hi there");
JS
);
Requirements::customCSS(<<<CSS
.tree li.$className {
background-image: url($icon);
}
CSS
);
## Templated javascript
A variant on the inclusion of custom javascript is the inclusion of *templated* javascript. Here, you keep your
JavaScript in a separate file and instead load, via search and replace, several PHP-generated variables into that code.
:::php
$vars = array(
"EditorCSS" => "mot/css/editor.css",
);
Requirements::javascriptTemplate("cms/javascript/editor.template.js", $vars);
## Clearing
You may want to clear all of the requirements mentioned thus far. I've used this when you've put an iframe generator as
an action on the controller that uses it. The iframe has a completely different set of scripting and styling
requirements, and it's easiest to flush all the default stuff and start again.
:::php
Requirements::clear();
You can also clear specific Requirements:
:::php
Requirements::clear('jsparty/prototype.js');
Caution: Depending on where you call this command, a Requirement might be *re-included* afterwards.
## Blocking
Requirements can also be explicitly blocked from inclusion,
which is useful to avoid conflicting JavaScript logic or CSS rules.
These blocking rules are independent of where the `block()` call is made:
It applies both for already included requirements, and ones
included after the `block()` call.
One common example is to block the core `jquery.js` include
added by various form fields and core controllers,
and use a newer version in a custom location.
:::php
Requirements::block(THIRDPARTY_DIR . '/jquery/jquery.js');
Caution: The CMS also uses the `Requirements` system, and its operation can be
affected by `block()` calls. Avoid this by limiting the scope of
your blocking operations, e.g. in `init()` of your controller.
## Inclusion Order
Requirements acts like a stack, where everything is rendered sequentially in the order it was included. There is no way
to change inclusion-order, other than using *Requirements::clear* and rebuilding (=guessing) the whole set of
requirements. Caution: Inclusion order is both relevant for CSS and Javascript files in terms of dependencies,
inheritance and overlays - please be careful when messing with the order of Requirements.
### Javascript placement
By default, SilverStripe includes all Javascript files at the bottom of the page body, unless there's another script already loaded, then, it's inserted before the first `<script>` tag. If this causes problems for you,
for example if you're using animation that ends up showing everything until the bottom of the page loads, or shows
buttons that rely on Javascript to work, you can change this behaviour. See below.
With the `Requirements.write_js_to_body`, you can configure if Javascript requirements are written to the head (false) or body (true).
With the `Requirements.force_js_to_bottom`, you can force Silverstripe to write the Javascript to the bottom of the page body, even if there is an earlier script tag. For example, when you have asynchronous scripts for Twitter earlier in your body (and stop speedtests from explaining about "scripts above the fold")
## CMS Requirements
The SilverStripe core includes a lot of Requirements by itself. Most of these are collated in `[api:LeftAndMain]` first.
## Motivation
Every page requested is made up of a number of parts, and many of those parts require their own CSS or JavaScript.
Rather than force the developer to put all of those requests into the template, or the header function, you can
reference required files anywhere in your application.
This lets you create very modular units of PHP+JavaScript+CSS, which a powerful concept but must be managed carefully.
## Managing Generic CSS styling
One of the aims of this is to create units of functionality that can be reasonably easily deployed as-is, while still
giving developers the option to customise them. The logical solution to this is to create 'generic' CSS to be applied
to these things. However, we must take great care to keep the CSS selectors very nonspecific. This precludes us from
adding any CSS that would "override customisations" in the form - for example, resetting the width of a field where 100%
width isn't appropriate.
Another solution would be to include some "generic CSS" for form elements at the very high level, so that fixed widths
on forms were applied to the generic form, and could therefore be overridden by a field's generic stylesheet. Similar
to this is mandating the use of "form div.field input" to style form input tags, whether it's a generic form or a custom
one.
Perhaps we could make use of a Requirements::disallowCSS() function, with which we could prevent the standard CSS from
being included in situations where it caused problems. But the complexity could potentially balloon, and really, it's a
bit of an admission of defeat - we shouldn't need to have to do this if our generic CSS was well-designed.
## Ideas/Problems
### Ajax
The whole "include it when you need it" thing shows some weaknesses in areas such as the CMS, where Ajax is used to load
in large pieces of the application, which potentially require more CSS and JavaScript to be included. At this stage,
the only workaround is to ensure that everything you might need is included on the first page-load.
One idea is to mention the CSS and JavaScript which should be included in the header of the Ajax response, so that the
client can load up those scripts and stylesheets upon completion of the Ajax request. This could be coded quite
cleanly, but for best results we'd want to extend prototype.js with our own changes to their Ajax system, so that every
script had consistent support for this.
### Lots of files
Because everything's quite modular, it's easy to end up with a large number of small CSS and JavaScript files. This has
problems with download time, and potentially maintainability.
We don't have any easy answers here, but here are some ideas:
* Merging the required files into a single download on the server. The flip side of this is that if every page has a
slightly different JS/CSS requirements, the whole lot will be refetched.
* Better: "Tagging" each required file for different use-cases, and creating a small set of common functionalities
(e.g. everything tagged "base" such as prototype.js would always be included)
* Do lazy fetching of scripts within an ajax-call. This seems to be possible, but very tricky due to the asynchronous
nature of an ajax-request. Needs some more research
## API Documentation
`[api:Requirements]`

View File

@ -1,177 +0,0 @@
# Restful Service
## Introduction
`[api:RestfulService]` uses the php curl library, enabling connections to remote web services which support a REST interface and consuming those web services. (Examples: [Flickr](http://www.flickr.com/services/api/), [Youtube](http://code.google.com/apis/youtube/overview.html), Amazon and etc). `[api:RestfulService]` can parse the XML response (sorry no JSON support)
returned from the web service. Further it supports caching of the response, and you can customize the cache interval.
To gain the functionality you can either create a new `[api:RestfulService]` object or create a class extending the
RestfulService (see [flickrservice](http://silverstripe.org/flickr-module/) and
[youtubeservice](http://silverstripe.org/youtube-gallery-module/) modules).
## Examples
### Creating a new RestfulObject
:::php
//example for using RestfulService to connect and retrive latest twitter status of an user.
$twitter = new RestfulService("http://twitter.com/statuses/user_timeline/user.xml", $cache_expiry );
$params = array('count' => 1);
$twitter->setQueryString($params);
$conn = $twitter->request();
$msgs = $twitter->getValues($conn, "status");
### Extending to a new class
:::php
//example for extending RestfulService
class FlickrService extends RestfulService {
public function __construct($expiry=NULL){
parent::__construct('http://www.flickr.com/services/rest/', $expiry);
$this->checkErrors = true;
}
......
### Multiple requests by using the $subURL argument on connect()
:::php
// Set up REST service
$service = new RestfulService("http://example.harvestapp.com");
$service->basicAuth('username', 'password');
$service->httpHeader('Accept: application/xml');
$service->httpHeader('Content-Type: application/xml');
$peopleXML = $service->request('/people');
$people = $service->getValues($peopleXML, 'user');
// ...
$taskXML = $service->request('/tasks');
$tasks = $service->getValues($taskXML, 'task');
## Features
### Caching
To set the cache interval you can pass it as the 2nd argument to constructor.
:::php
new RestfulService("http://twitter.com/statuses/user_timeline/user.xml", 3600 );
### Getting Values & Attributes
You can traverse throught document tree to get the values or attribute of a particular node.
for example you can traverse
:::xml
<entries>
<entry id='12'>Sally</entry>
<entry id='15'>Ted</entry>
<entry id='30'>Matt</entry>
<entry id='22'>John</entry>
</entries>
to extract the id attributes of the entries use:
:::php
$this->getAttributes($xml, "entries", "entry") //will return all attributes of each entry node
to extract the values (the names) of the entries use:
:::php
$this->getValues($xml, "entries", "entry") //will return all values of each entry node
### Searching for Values & Attributes
If you don't know the exact position of dom tree where the node will appear you can use xpath to search for the
node.Recommended for retrieving values of namespaced nodes.
:::xml
<media:guide>
<media:entry id="2030">video</media:entry>
</media:guide>
to get the value of entry node with the namespace media, use:
:::php
$this->searchValue($response, "//media:guide/media:entry")
## Best Practices
### Handling Errors
If the web service returned an error (for example, API key not available or inadequate parameters) `[api:RestfulService]`
could delgate the error handling to it's descendant class. To handle the errors define a function called errorCatch
:::php
// This will raise Youtube API specific error messages (if any).
public function errorCatch($response){
$err_msg = $response;
if(strpos($err_msg, '<') === false) {
user_error("YouTube Service Error : $err_msg", E_USER_ERROR);
}
return $response;
}
If you want to bypass error handling on your sub-classes you could define that in the constructor.
:::php
public function __construct($expiry=NULL){
parent::__construct('http://www.flickr.com/services/rest/', $expiry);
$this->checkErrors = false; //Set checkErrors to false to bypass error checking
}
## Other Uses
### How to use `[api:RestfulService]` to easily embed an RSS feed
`[api:RestfulService]` can be used to easily embed an RSS feed (since it's also an xml response) from a site
such as del.icio.us
Put something like this code in mysite/code/Page.php inside class Page_Controller
:::php
// Accepts an RSS feed URL and outputs a list of links from it
public function RestfulLinks($url){
$service = new RestfulService($url);
$request = $service->request();
$body = $request->getBody();
$items = $service->getValues($body,"channel","item");
$output = '';
foreach($items as $item) {
// Fix quote encoding
$description = str_replace('&amp;quot;', '&quot;', $item->description);
$output .= "<li><a href=\"{$item->link}\">{$item->title}</a><br />{$description}</li>";
}
return $output;
}
Put something like this code in `themes/<your-theme>/templates/Layout/HomePage.ss`:
:::ss
<h3>My Latest Del.icio.us Links</h3>
<ul>
$RestfulLinks(http://del.icio.us/rss/elijahlofgren)
</ul>
## API Documentation
`[api:RestfulService]`

View File

@ -1,181 +0,0 @@
# RSS Feed
## Introduction
Generating RSS/Atom-feeds is a matter of rendering a `[api:SS_List]` through
the `[api:RSSFeed]` class.
The `[api:RSSFeed]` class doesn't limit you to generating article based feeds,
it is just as easy to create a feed of your current staff members, comments or
any other custom `[api:DataObject]` subclasses you have defined. The only
logical limitation here is that every item in the RSS-feed should be accessible
through a URL on your website, so its advisable to just create feeds from sub
classes of `[api:SiteTree]`.
If you wish to generate an RSS feed for `[api:DataObject]` instances, ensure they
define an AbsoluteLink() method.
## Usage
:::php
RSSFeed::linkToFeed($link, $title)
This line should go in your `[api:Controller]` subclass in the action you want
to include the HTML link. Not all arguments are required, see `[api:RSSFeed]` and example below. Last Modified Time is expected in seconds like time().
:::php
$feed = new RSSFeed(
$list,
$link,
$title,
$description,
$titleField,
$descriptionField,
$authorField,
$lastModifiedTime,
$etag
);
Creates a new `[api:RSSFeed]` instance to be returned. The arguments notify
SilverStripe what values to include in the feed.
## Examples
### Showing latest blog posts
:::php
class Page_Controller extends ContentController {
private static $allowed_actions = array('rss');
public function init() {
parent::init();
// linkToFeed will add an appropriate HTML link tag to the website
// <head> tag to notify web browsers that an RSS feed is available
// for this page. You can include as many feeds on the page as you
// wish as long as each as a different link. For example:
// ('blog/rss', 'staff/rss').
//
// In this example $this->Link("rss") refers to the *rss* function
// we define below.
RSSFeed::linkToFeed($this->Link("rss"), "RSS feed of this blog");
}
public function rss() {
// Creates a new RSS Feed list
$rss = new RSSFeed(
$list = $this->getBlogPosts(), // an SS_List containing your feed items
$link = $this->Link("rss"), // a HTTP link to this feed
$title = "My feed", // title for this feed, displayed in RSS readers
$description = "This is an example feed." // description
);
// Outputs the RSS feed to the user.
return $rss->outputToBrowser();
}
public function getBlogPosts() {
return BlogPage::get()->limit(10);
}
}
### Showing the 10 most recently updated pages
You can use `[api:RSSFeed]` to easily create a feed showing your latest Page
updates. Update mysite/code/Page.php to something like this:
:::php
<?php
class Page extends SiteTree {}
class Page_Controller extends ContentController {
private static $allowed_actions = array('rss');
public function init() {
parent::init();
RSSFeed::linkToFeed($this->Link() . "rss", "10 Most Recently Updated Pages");
}
public function rss() {
$rss = new RSSFeed($this->LatestUpdates(), $this->Link(), "10 Most Recently Updated Pages", "Shows a list of the 10 most recently updated pages.");
return $rss->outputToBrowser();
}
public function LatestUpdates() {
return Page::get()->sort("LastEdited", "DESC")->limit(10);
}
}
### Rendering DataObjects in a RSSFeed
DataObjects can be rendered in the feed as well, however, since they aren't explicitly
`[api:SiteTree]` subclasses we need to include a function `AbsoluteLink` to allow the
RSS feed to link through to the item.
If the items are all displayed on a single page you may simply hard code the link to
point to a particular page.
Take an example, we want to create an RSS feed of all the Students, a DataObject we
defined in the [fifth tutorial](/tutorials/5-dataobject-relationship-management).
:::php
<?php
class Student extends DataObject {
public function AbsoluteLink() {
// see tutorial 5, students are assigned a project, so the 'link'
// to view the student is based on their projects link.
return $this->Project()->AbsoluteLink();
}
}
Then update the Page_Controller class in mysite/code/Page.php to include an RSSFeed
for all the students as we've seen before.
:::php
class Page_Controller extends ContentController {
private static $allowed_actions = array('students');
public function init() {
parent::init();
RSSFeed::linkToFeed($this->Link("students"), "Students feed");
}
public function students() {
$rss = new RSSFeed(
$list = $this->getStudents(),
$link = $this->Link("students"),
$title = "Students feed"
);
return $rss->outputToBrowser();
}
public function getStudents() {
return Student::get()->sort("Created", "DESC")->limit(10);
}
}
### Customizing the RSS Feed template
The default template used is framework/templates/RSSFeed.ss and includes
displaying titles and links to the content. If you have a particular need
for customizing the XML produced (say for additional meta data) use `setTemplate`.
Taking that last example, we would rewrite the students function to include a
unique template (write your own XML in themes/yourtheme/templates/Students.ss)
:::php
public function students() {
$rss = new RSSFeed(
$list = $this->getStudents(),
$link = $this->Link("students"),
$title = "Students feed"
);
$rss->setTemplate('Students');
return $rss->outputToBrowser();
}
## External Sources
`[api:RSSFeed]` only creates feeds from your own data. We've included the [SimplePie](http://simplepie.org) RSS-parser for
accessing feeds from external sources.
## Related
* [blog module](http://silverstripe.org/blog-module)
## API Documentation
* `[api:RSSFeed]`

View File

@ -1,192 +0,0 @@
# SearchContext
## Introduction
Manages searching of properties on one or more `[api:DataObject]` types, based on a given set of input parameters.
`[api:SearchContext]` is intentionally decoupled from any controller-logic,
it just receives a set of search parameters and an object class it acts on.
The default output of a `[api:SearchContext]` is either a `[api:SQLQuery]` object for further refinement, or a
`[api:DataObject]` instance.
In case you need multiple contexts, consider namespacing your request parameters by using `FieldList->namespace()` on
the $fields constructor parameter.
`[api:SearchContext]` is mainly used by `[ModelAdmin](/reference/modeladmin)`, our generic data administration interface. Another
implementation can be found in generic frontend search forms through the [genericviews](http://silverstripe.org/generic-views-module) module.
## Usage
Getting results
:::php
singleton('MyDataObject')->getDefaultSearchContext();
### Defining fields on your DataObject
See `[api:DataObject::$searchable_fields]`.
### Customizing fields and filters
In this example we're defining three attributes on our MyDataObject subclass: `PublicProperty`, `HiddenProperty`
and `MyDate`. The attribute `HiddenProperty` should not be searchable, and `MyDate` should only search for dates
*after* the search entry (with a `GreaterThanFilter`). Similiar to the built-in `DataObject->getDefaultSearchContext()`
method, we're building our own `getCustomSearchContext()` variant.
:::php
class MyDataObject extends DataObject {
private static $db = array(
'PublicProperty' => 'Text'
'HiddenProperty' => 'Text',
'MyDate' => 'Date'
);
public function getCustomSearchContext() {
$fields = $this->scaffoldSearchFields(array(
'restrictFields' => array('PublicProperty','MyDate')
));
$filters = array(
'PublicProperty' => new PartialMatchFilter('PublicProperty'),
'MyDate' => new GreaterThanFilter('MyDate')
);
return new SearchContext(
$this->class,
$fields,
$filters
);
}
}
### Generating a search form from the context
:::php
class Page_Controller extends ContentController {
public function SearchForm() {
$context = singleton('MyDataObject')->getCustomSearchContext();
$fields = $context->getSearchFields();
$form = new Form($this, "SearchForm",
$fields,
new FieldList(
new FormAction('doSearch')
)
);
return $form;
}
public function doSearch($data, $form) {
$context = singleton('MyDataObject')->getCustomSearchContext();
$results = $context->getResults($data);
return $this->customise(array(
'Results' => $results
))->renderWith('Page_results');
}
}
### Pagination
For pagination records on multiple pages, you need to wrap the results in a
`PaginatedList` object. This object is also passed the generated `SQLQuery`
in order to read page limit information. It is also passed the current
`SS_HTTPRequest` object so it can read the current page from a GET var.
:::php
public function getResults($searchCriteria = array()) {
$start = ($this->request->getVar('start')) ? (int)$this->request->getVar('start') : 0;
$limit = 10;
$context = singleton('MyDataObject')->getCustomSearchContext();
$query = $context->getQuery($searchCriteria, null, array('start'=>$start,'limit'=>$limit));
$records = $context->getResults($searchCriteria, null, array('start'=>$start,'limit'=>$limit));
if($records) {
$records = new PaginatedList($records, $this->request);
$records->setPageStart($start);
$records->setPageLength($limit);
$records->setTotalItems($query->unlimitedRowCount());
}
return $records;
}
notice that if you want to use this getResults function, you need to change the function doSearch for this one:
:::php
public function doSearch($data, $form) {
$context = singleton('MyDataObject')->getCustomSearchContext();
$results = $this->getResults($data);
return $this->customise(array(
'Results' => $results
))->renderWith(array('Catalogo_results', 'Page'));
}
The change is in **$results = $this->getResults($data);**, because you are using a custom getResults function.
Another thing you cant forget is to check the name of the singleton you are using in your project. the example uses
**MyDataObject**, you need to change it for the one you are using
For more information on how to paginate your results within the template, see [Tutorial: Site Search](/tutorials/4-site-search).
### The Pagination Template
to show the results of your custom search you need at least this content in your template, notice that
Results.PaginationSummary(4) defines how many pages the search will show in the search results. something like:
**Next 1 2 *3* 4 5 &hellip; 558**
:::ss
<% if $Results %>
<ul>
<% loop $Results %>
<li>$Title, $Autor</li>
<% end_loop %>
</ul>
<% else %>
<p>Sorry, your search query did not return any results.</p>
<% end_if %>
<% if $Results.MoreThanOnePage %>
<div id="PageNumbers">
<p>
<% if $Results.NotFirstPage %>
<a class="prev" href="$Results.PrevLink" title="View the previous page">Prev</a>
<% end_if %>
<span>
<% loop $Results.PaginationSummary(4) %>
<% if $CurrentBool %>
$PageNum
<% else %>
<% if $Link %>
<a href="$Link" title="View page number $PageNum">$PageNum</a>
<% else %>
&hellip;
<% end_if %>
<% end_if %>
<% end_loop %>
</span>
<% if $Results.NotLastPage %>
<a class="next" href="$Results.NextLink" title="View the next page">Next</a>
<% end_if %>
</p>
</div>
<% end_if %>
## Available SearchFilters
See `[api:SearchFilter]` API Documentation
## API Documentation
`[api:SearchContext]`
## Related
* [ModelAdmin](/reference/modeladmin)
* [RestfulServer module](https://github.com/silverstripe/silverstripe-restfulserver)
* [Tutorial: Site Search](/tutorials/4-site-search)

View File

@ -1,242 +0,0 @@
# Shortcodes: Flexible Content Embedding
## Overview
The `[api:ShortcodeParser]` API is simple parser that allows you to map specifically
formatted content to a callback to transform them into something else.
You might know this concept from forum software which don't allow you to insert
direct HTML, instead resorting to a custom syntax.
In the CMS, authors often want to insert content elements which go beyond
standard formatting, at an arbitrary position in their WYSIWYG editor.
Shortcodes are a semi-technical solution for this. A good example would
be embedding a 3D file viewer or a Google Map at a certain location.
Here's some syntax variations:
[my_shortcode]
[my_shortcode /]
[my_shortcode,myparameter="value"]
[my_shortcode,myparameter="value"]Enclosed Content[/my_shortcode]
## Usage
In its most basic form, you can invoke the `[api:ShortcodeParser]` directly:
:::php
ShortcodeParser::get_active()->parse($myvalue);
In addition, shortcodes are automatically parsed on any database field which is declared
as `[api:HTMLValue]` or `[api:HTMLText]`, when rendered into a template.
This means you can use shortcodes on common fields like `SiteTree.Content`,
and any other `[api:DataObject::$db]` definitions of these types.
In order to allow shortcodes in your own template placeholders,
ensure they're casted correctly:
:::php
class MyObject extends DataObject {
private static $db = array('Content' => 'HTMLText');
private static $casting = array('ContentHighlighted' => 'HTMLText');
public function ContentHighlighted($term) {
return str_replace($term, "<em>$term</em>", $this->Content);
}
}
There is currently no way to allow shortcodes directly in template markup
(as opposed to return values of template placeholders).
## Defining Custom Shortcodes
All you need to do to define a shortcode is to register a callback with the parser that will be called whenever a
shortcode is encountered. This callback will return a string to replace the shortcode with.
If the shortcode is used for template placeholders of type `HTMLText` or `HTMLVarchar`,
the returned value should be valid HTML
To register a shortcode you call:
ShortcodeParser::get('default')->register('my_shortcode', <callback>);
These parameters are passed to the callback:
- Any parameters attached to the shortcode as an associative array (keys are lower-case).
- Any content enclosed within the shortcode (if it is an enclosing shortcode). Note that any content within this
will not have been parsed, and can optionally be fed back into the parser.
- The ShortcodeParser instance used to parse the content.
- The shortcode tag name that was matched within the parsed content.
- An associative array of extra information about the shortcode being parsed. For example, if the shortcode is
is inside an attribute, the `element` key contains a reference to the parent `DOMElement`, and the `node`
key the attribute's `DOMNode`.
## Example: Google Maps Iframe by Address
To demonstrate how easy it is to build custom shortcodes, we'll build one to display
a Google Map based on a provided address. Format:
[googlemap,width=500,height=300]97-99 Courtenay Place, Wellington, New Zealand[/googlemap]
So we've got the address as "content" of our new `googlemap` shortcode tags,
plus some `width` and `height` arguments. We'll add defaults to those in our shortcode parser so they're optional.
:::php
ShortcodeParser::get('default')->register('googlemap', function($arguments, $address, $parser, $shortcode) {
$iframeUrl = sprintf(
'http://maps.google.com/maps?q=%s&amp;hnear=%s&amp;ie=UTF8&hq=&amp;t=m&amp;z=14&amp;output=embed',
urlencode($address),
urlencode($address)
);
$width = (isset($arguments['width']) && $arguments['width']) ? $arguments['width'] : 400;
$height = (isset($arguments['height']) && $arguments['height']) ? $arguments['height'] : 300;
return sprintf(
'<iframe width="%d" height="%d" src="%s" frameborder="0" scrolling="no" marginheight="0" marginwidth="0"></iframe>',
$width,
$height,
$iframeUrl
);
});
The hard bits are taken care of (parsing out the shortcodes), everything we need to do is a bit of string replacement.
CMS users still need to remember the specific syntax, but these shortcodes can form the basis
for more advanced editing interfaces (with visual placeholders). See the built-in `embed` shortcode as an example
for coupling shortcodes with a form to create and edit placeholders.
## Built-in Shortcodes
SilverStripe comes with several shortcode parsers already.
### Links
Internal page links keep references to their database IDs rather than
the URL, in order to make these links resilient against moving the target page to a different
location in the page tree. This is done through the `[sitetree_link]` shortcode, which
takes an `id` parameter. Example: `<a href="[sitetree_link,id=99]">`
Links to internal `File` database records work exactly the same, but with the `[file_link]` shortcode.
### Media (Photo, Video and Rich Content)
Many media formats can be embedded into websites through the `<object>`
tag, but some require plugins like Flash or special markup and attributes.
OEmbed is a standard to discover these formats based on a simple URL,
for example a Youtube link pasted into the "Insert Media" form of the CMS.
Since TinyMCE can't represent all these varations, we're showing a placeholder
instead, and storing the URL with a custom `[embed]` shortcode.
Example: `.[embed width=480 height=270 class=left thumbnail=http://i1.ytimg.com/vi/lmWeD-vZAMY/hqdefault.jpg?r=8767]http://www.youtube.com/watch?v=lmWeD-vZAMY[/embed]`
## Syntax
* Unclosed - `[shortcode]`
* Explicitly closed - `[shortcode/]`
* With parameters, mixed quoting - `[shortcode parameter=value parameter2='value2' parameter3="value3"]`
* Old style parameter separation - `[shortcode,parameter=value,parameter2='value2',parameter3="value3"]`
* With contained content & closing tag - `[shortcode]Enclosed Content[/shortcode]`
* Escaped (will output `[just] [text]` in response) - `[[just] [[text]]`
### Attribute and element scope
HTML with unprocessed shortcodes in it is still valid HTML. As a result, shortcodes can be in two places in HTML:
- In an attribute value, like so: `<a title="[title]">link</a>`
- In an element's text, like so: `<p>Some text [shortcode] more text</p>`
The first is called "element scope" use, the second "attribute scope"
You may not use shortcodes in any other location. Specifically, you can not use shortcodes to generate attributes or
change the name of a tag. These usages are forbidden:
<[paragraph]>Some test</[paragraph]>
<a [titleattribute]>link</a>
You may need to escape text inside attributes `>` becomes `&gt;`,
You can include HTML tags inside a shortcode tag, but you need to be careful of nesting to ensure you don't
break the output
Good:
<div>
[shortcode]
<p>Caption</p>
[/shortcode]
</div>
Bad:
<div>
[shortcode]
</div>
<p>
[/shortcode]
</p>
### Location
Element scoped shortcodes have a special ability to move the location they are inserted at to comply with
HTML lexical rules. Take for example this basic paragraph tag:
<p><a href="#">Head [figure,src="assets/a.jpg",caption="caption"] Tail</a></p>
When converted naively would become
<p><a href="#">Head <figure><img src="assets/a.jpg" /><figcaption>caption</figcaption></figure> Tail</a></p>
However this is not valid HTML - P elements can not contain other block level elements.
To fix this you can specify a "location" attribute on a shortcode. When the location attribute is "left" or "right"
the inserted content will be moved to immediately before the block tag. The result is this:
<figure><img src="assets/a.jpg" /><figcaption>caption</figcaption></figure><p><a href="#">Head Tail</a></p>
When the location attribute is "leftAlone" or "center" then the DOM is split around the element. The result is this:
<p><a href="#">Head </a></p><figure><img src="assets/a.jpg" /><figcaption>caption</figcaption></figure><p><a href="#"> Tail</a></p>
### Parameter values
Here is a summary of the callback parameter values based on some example shortcodes.
Short
[my_shortcodes]
$attributes => array()
$enclosedContent => null
$parser => ShortcodeParser instance
$tagName => 'my_shortcode'
Short with attributes
[my_shortcode,attribute="foo",other="bar"]
$attributes => array ('attribute' => 'foo', 'other' => 'bar')
$enclosedContent => null
$parser => ShortcodeParser instance
$tagName => 'my_shortcode'
Long with attributes
[my_shortcode,attribute="foo"]content[/my_shortcode]
$attributes => array('attribute' => 'foo')
$enclosedContent => 'content'
$parser => ShortcodeParser instance
$tagName => 'my_shortcode'
## Limitations
Since the shortcode parser is based on a simple regular expression it cannot properly handle nested shortcodes. For
example the below code will not work as expected:
[shortcode]
[shortcode][/shortcode]
[/shortcode]
The parser will raise an error if it can not find a matching opening tag for any particular closing tag
## Related
* [Wordpress implementation](http://codex.wordpress.org/Shortcode_API)

View File

@ -1,68 +0,0 @@
# SiteConfig: Global database content
## Introduction
The `[api:SiteConfig]` panel provides a generic interface for managing site wide settings or
functionality which is used throughout the site. Out of the box it provides 2 fields 'Site Name' and 'Site Tagline'.
## Accessing `[api:SiteConfig]` Options
You can access `[api:SiteConfig]` options from any SS template by using the function $SiteConfig.FieldName
:::ss
$SiteConfig.Title
$SiteConfig.Tagline
// or
<% with $SiteConfig %>
$Title $AnotherField
<% end_with %>
Or if you want to access variables in the PHP you can do
:::php
$config = SiteConfig::current_site_config();
$config->Title
## Extending `[api:SiteConfig]`
To extend the options available in the panel you can define your own fields via an Extension.
Create a mysite/code/CustomSiteConfig.php file.
:::php
<?php
class CustomSiteConfig extends DataExtension {
private static $db = array(
'FooterContent' => 'HTMLText'
);
public function updateCMSFields(FieldList $fields) {
$fields->addFieldToTab("Root.Main", new HTMLEditorField("FooterContent", "Footer Content"));
}
}
Then activate your extension in your [config.yml](/topics/configuration) file.
:::yml
SiteConfig:
extensions:
- CustomSiteConfig
This tells SilverStripe to add the CustomSiteConfig extension to the `[api:SiteConfig]` class.
After adding those two pieces of code, rebuild your database by visiting http://localhost/dev/build and then reload
the admin interface. You may need to reload it with a ?flush=1 on the end.
You can define as many extensions for `[api:SiteConfig]` as you need. For example if you are developing a module you can define
your own global settings for the dashboard.
## API Documentation
`[api:SiteConfig]`

View File

@ -1,153 +0,0 @@
# Sitetree
## Introduction
Basic data-object representing all pages within the site tree.
The omnipresent *Page* class (located in `mysite/code/Page.php`) is based on this class.
## Creating, Modifying and Finding Pages
See the ["datamodel" topic](/topics/datamodel).
## Linking
:::php
// wrong
$mylink = $mypage->URLSegment;
// right
$mylink = $mypage->Link(); // alternatively: AbsoluteLink(), RelativeLink()
In a nutshell, the nested URLs feature means that your site URLs now reflect the actual parent/child page structure of
your site. The URLs map directly to the chain of parent and child pages. The
below table shows a quick summary of what these changes mean for your site:
![url table](http://silverstripe.org/assets/screenshots/Nested-URLs-Table.png)
## Querying
Use *SiteTree::get_by_link()* to correctly retrieve a page by URL, as it taked nested URLs into account (a page URL
might consist of more than one *URLSegment*).
:::php
// wrong
$mypage = SiteTree::get()->filter("URLSegment", '<mylink>')->First();
// right
$mypage = SiteTree::get_by_link('<mylink>');
### Versioning
The `SiteTree` class automatically has an extension applied to it: `[Versioned](api:Versioned)`.
This provides the basis for the CMS to operate on different stages,
and allow authors to save their changes without publishing them to
website visitors straight away.
`Versioned` is a generic extension which can be applied to any `DataObject`,
so most of its functionality is explained in the `["versioning" topic](/topics/versioning)`.
Since `SiteTree` makes heavy use of the extension, it adds some additional
functionality and helpers on top of it.
Permission control:
:::php
class MyPage extends Page {
public function canPublish($member = null) {
// return boolean from custom logic
}
public function canDeleteFromLive($member = null) {
// return boolean from custom logic
}
}
Stage operations:
* `$page->doUnpublish()`: removes the "Live" record, with additional permission checks,
as well as special logic for VirtualPage and RedirectorPage associations
* `$page->doPublish()`: Inverse of doUnpublish()
* `$page->doRevertToLive()`: Reverts current record to live state (makes sense to save to "draft" stage afterwards)
* `$page->doRestoreToStage()`: Restore the content in the active copy of this SiteTree page to the stage site.
Hierarchy operations (defined on `[api:Hierarchy]`:
* `$page->liveChildren()`: Return results only from live table
* `$page->stageChildren()`: Return results from the stage table
* `$page->AllHistoricalChildren()`: Return all the children this page had, including pages that were deleted from both stage & live.
* `$page->AllChildrenIncludingDeleted()`: Return all children, including those that have been deleted but are still in live.
## Allowed Children, Default Child and Root-Level
By default, any page type can be the child of any other page type.
However, there are static properties that can be
used to set up restrictions that will preserve the integrity of the page hierarchy.
Example: Restrict blog entry pages to nesting underneath their blog holder
:::php
class BlogHolder extends Page {
// Blog holders can only contain blog entries
private static $allowed_children = array("BlogEntry");
private static $default_child = "BlogEntry";
// ...
}
class BlogEntry extends Page {
// Blog entries can't contain children
private static $allowed_children = "none";
private static $can_be_root = false;
// ...
}
class Page extends SiteTree {
// Don't let BlogEntry pages be underneath Pages. Only underneath Blog holders.
private static $allowed_children = array("*Page,", "BlogHolder");
}
* **allowed_children:** This can be an array of allowed child classes, or the string "none" - indicating that this page
type can't have children. If a classname is prefixed by "*", such as "*Page", then only that class is allowed - no
subclasses. Otherwise, the class and all its subclasses are allowed.
* **default_child:** If a page is allowed more than 1 type of child, you can set a default. This is the value that
will be automatically selected in the page type dropdown when you create a page in the CMS.
* **can_be_root:** This is a boolean variable. It lets you specify whether the given page type can be in the top
level.
Note that there is no allowed_parents` control. To set this, you will need to specify the `allowed_children` of all other page types to exclude the page type in question.
## Tree Limitations
SilverStripe limits the amount of initially rendered nodes in order to avoid
processing delays, usually to a couple of dozen. The value can be configured
through `[api:Hierarchy::$node_threshold_total]`.
If a website has thousands of pages, the tree UI metaphor can become an inefficient way
to manage them. The CMS has an alternative "list view" for this purpose, which allows
sorting and paging through large numbers of pages in a tabular view.
To avoid exceeding performance constraints of both the server and browser,
SilverStripe places hard limits on the amount of rendered pages in
a specific tree leaf, typically a couple of hundred pages.
The value can be configured through `[api:Hierarchy::$node_threshold_leaf]`.
## Tree Display (Description, Icons and Badges)
The page tree in the CMS is a central element to manage page hierarchies,
hence its display of pages can be customized as well.
On a most basic level, you can specify a custom page icon
to make it easier for CMS authors to identify pages of this type,
when navigating the tree or adding a new page:
:::php
class StaffPage extends Page {
private static $singular_name = 'Staff Directory';
private static $plural_name = 'Staff Directories';
private static $description = 'Two-column layout with a list of staff members';
private static $icon = 'mysite/images/staff-icon.png';
// ...
}
You can also add custom "badges" to each page in the tree,
which denote status. Built-in examples are "Draft" and "Deleted" flags.
This is detailed in the ["Customize the CMS Tree" howto](/howto/customize-cms-tree).

View File

@ -1,143 +0,0 @@
# SQL Query
## Introduction
An object representing a SQL query, which can be serialized into a SQL statement.
It is easier to deal with object-wrappers than string-parsing a raw SQL-query.
This object is used by the SilverStripe ORM internally.
Dealing with low-level SQL is not encouraged, since the ORM provides
powerful abstraction APIs (see [datamodel](/topics/datamodel).
Starting with SilverStripe 3, records in collections are lazy loaded,
and these collections have the ability to run efficient SQL
such as counts or returning a single column.
For example, if you want to run a simple `COUNT` SQL statement,
the following three statements are functionally equivalent:
:::php
// Through raw SQL
$count = DB::query('SELECT COUNT(*) FROM "Member"')->value();
// Through SQLQuery abstraction layer
$query = new SQLQuery();
$count = $query->setFrom('Member')->setSelect('COUNT(*)')->value();
// Through the ORM
$count = Member::get()->count();
If you do use raw SQL, you'll run the risk of breaking
various assumptions the ORM and code based on it have:
* Custom getters/setters (object property can differ from database column)
* DataObject hooks like onBeforeWrite() and onBeforeDelete()
* Automatic casting
* Default values set through objects
* Database abstraction
We'll explain some ways to use *SELECT* with the full power of SQL,
but still maintain a connection to the ORM where possible.
<div class="warning" markdown="1">
Please read our ["security" topic](/topics/security) to find out
how to sanitize user input before using it in SQL queries.
</div>
## Usage
### SELECT
:::php
$sqlQuery = new SQLQuery();
$sqlQuery->setFrom('Player');
$sqlQuery->selectField('FieldName', 'Name');
$sqlQuery->selectField('YEAR("Birthday")', 'Birthyear');
$sqlQuery->addLeftJoin('Team','"Player"."TeamID" = "Team"."ID"');
$sqlQuery->addWhere('YEAR("Birthday") = 1982');
// $sqlQuery->setOrderBy(...);
// $sqlQuery->setGroupBy(...);
// $sqlQuery->setHaving(...);
// $sqlQuery->setLimit(...);
// $sqlQuery->setDistinct(true);
// Get the raw SQL (optional)
$rawSQL = $sqlQuery->sql();
// Execute and return a Query object
$result = $sqlQuery->execute();
// Iterate over results
foreach($result as $row) {
echo $row['BirthYear'];
}
The result is an array lightly wrapped in a database-specific subclass of `[api:Query]`.
This class implements the *Iterator*-interface, and provides convenience-methods for accessing the data.
### DELETE
:::php
$sqlQuery->setDelete(true);
### INSERT/UPDATE
Currently not supported through the `SQLQuery` class, please use raw `DB::query()` calls instead.
:::php
DB::query('UPDATE "Player" SET "Status"=\'Active\'');
### Value Checks
Raw SQL is handy for performance-optimized calls,
e.g. when you want a single column rather than a full-blown object representation.
Example: Get the count from a relationship.
:::php
$sqlQuery = new SQLQuery();
$sqlQuery->setFrom('Player');
$sqlQuery->addSelect('COUNT("Player"."ID")');
$sqlQuery->addWhere('"Team"."ID" = 99');
$sqlQuery->addLeftJoin('Team', '"Team"."ID" = "Player"."TeamID"');
$count = $sqlQuery->execute()->value();
Note that in the ORM, this call would be executed in an efficient manner as well:
:::php
$count = $myTeam->Players()->count();
### Mapping
Creates a map based on the first two columns of the query result.
This can be useful for creating dropdowns.
Example: Show player names with their birth year, but set their birth dates as values.
:::php
$sqlQuery = new SQLQuery();
$sqlQuery->setFrom('Player');
$sqlQuery->setSelect('Birthdate');
$sqlQuery->selectField('CONCAT("Name", ' - ', YEAR("Birthdate")', 'NameWithBirthyear');
$map = $sqlQuery->execute()->map();
$field = new DropdownField('Birthdates', 'Birthdates', $map);
Note that going through SQLQuery is just necessary here
because of the custom SQL value transformation (`YEAR()`).
An alternative approach would be a custom getter in the object definition.
:::php
class Player extends DataObject {
private static $db = array(
'Name' => 'Varchar',
'Birthdate' => 'Date'
);
function getNameWithBirthyear() {
return date('y', $this->Birthdate);
}
}
$players = Player::get();
$map = $players->map('Name', 'NameWithBirthyear');
## Related
* [datamodel](/topics/datamodel)
* `[api:DataObject]`
* [database-structure](database-structure)

View File

@ -1,81 +0,0 @@
# TableField
## Introduction
`[api:TableField]` behaves in the same manner as `[api:TableListField]`, however allows the editing of existing and adding of
new rows. The data is saved back by the surrounding form-saving (mostly EditForm->save).
See `[api:TableListField]` for more documentation on the base-class
## Usage
### Add hidden default data
Please use **TableField->setExtraData()** to specify additional (non-editable) data. You might use the following code
that shows the Player of Team with a particular Team ID and automatically saves new Players into this Team.
In this example, you'll note that we're setting TeamID to $this->ID. This works well if you're including a TableField
as an editable field on a getCMSFields() call.
:::php
$myTableField = new TableField(
'MyTableField', // fieldName
'Player', // sourceType
array(
'FirstName'=>'First Name',
'Surname'=>'Surname'
), // fieldList
array(
'FirstName'=>'TextField',
'Surname'=>'TextField'
), // fieldTypes
null, // filterField (legacy)
"Player.TeamID",
$this->ID
);
// add some HiddenFields thats saved with each new row
$myTableField->setExtraData(array(
'TeamID' => $this->ID ? $this->ID : '$RecordID'
));
The '$RecordID' value is used when building forms that create new records. It will be populated with whatever record id
is created.
### Row Transformation
You can apply a `[api:FormTransformation]` to any given field,
based on a eval()ed php-rule. You can access all columns on the generated DataObjects here.
:::php
$myTF->setTransformationConditions(array(
"PlayerName" => array(
"rule" => '$PlayerStatus == "Retired" || $PlayerStatus == "Injured"',
"transformation" => "performReadonlyTransformation"
)
));
### Required Fields
Due to the nested nature of this fields dataset, you can't set any required columns as usual with the
`[api:RequiredFields]`** on the TableField-instance for this.
Note: You still have to attach some form of `[api:Validator]` to the form to trigger any validation on this field.
### Nested Table Fields
When you have `[api:TableField]` inside a `[api:ComplexTableField]`, the parent ID may not be known in your
getCMSFields() method. In these cases, you can set a value to '$RecordID' in your `[api:TableField]` extra data, and this
will be populated with the newly created record id upon save.
## Known Issues
* A `[api:TableField]` doesn't reload any submitted form-data if the saving is interrupted by a failed validation. After
refreshing the form with the validation-errors, the `[api:TableField]` will be blank again.
* You can't add **visible default data** to columns in a `[api:TableField]`, please use *setExtraData*
## API Documentation
`[api:TableField]`

View File

@ -1,292 +0,0 @@
# TableListField
## Introduction
<div class="warning" markdown="1">
This field is deprecated in favour of the new [GridField](/reference/grid-field) API.
</div>
Form field that embeds a list of `[api:DataObject]`s into a form, such as a member list or a file list.
Provides customizeable columns, record-deletion by ajax, paging, sorting, CSV-export, printing, input by
`[api:DataObject]` or raw SQL.
## Example
Here's an example of a full featured `[api:TableListField]` implementation. It features editing members in the database
directly as a button on each record, as well as filtering, and sorting. It also makes use of the 'export' permission,
allowing export of data as a CSV.
:::php
public function getReportField() {
$resultSet = new DataObjectSet();
$filter = `;
$sort = "Member.ID ASC";
$join = `;
$instance = singleton('Member');
$query = $instance->buildSQL($filter, $sort, null, $join);
$query->groupby[] = 'Member.ID';
$report = new TableListField(
'CorporateReport',
'Member',
array(
'ID' => 'ID',
'FirstName' => 'First Name',
'Surname' => 'Surname',
'Email' => 'Email',
'MembershipType' => 'Membership Type',
'MembershipStatus' => 'Membership Status',
'DateJoined' => 'Date Joined',
'PaidUntil' => 'Paid Until',
'Edit' => ''
)
);
$report->setCustomQuery($query);
$report->setFieldFormatting(array(
'Email' => '<a href=\"mailto: $Email\" title=\"Email $FirstName\">$Email</a>',
'Edit' => '<a href=\"admin/security/index/1?executeForm=EditForm&ID=1&ajax=1&action_callfieldmethod&fieldName=Members&ctf[childID]=$ID&ctf[ID]=1&ctf[start]=0&methodName=edit\"><img src=\"cms/images/edit.gif\" alt=\"Edit this member\" /></a>'
));
$report->setFieldCasting(array(
'DateJoined' => 'Date->Nice',
'PaidUntil' => 'Date->Nice'
));
$report->setShowPagination(true);
if(isset($_REQUEST['printable'])) {
$report->setPageSize(false);
} else {
$report->setPageSize(20);
}
$report->setPermissions(array(
'export',
'delete',
'print'
));
return $report;
}
For more information on each of the features used in the example, you can read below.
## Usage
### Source Input
:::php
// default: DataObject selection (e.g. all 'Product's)
$myTableListField = new TableListField(
'MyName',
'Product',
array('Price', 'Code')
);
// custom DataObjectSet
$myProducts = Product::get()->filter('Code', "MyCode");
$myTableListField->setCustomSourceItems($myProducts);
// custom SQL
$customCsvQuery = singleton('Product')->buildSQL();
$customCsvQuery->select[] = "CONCAT(col1,col2) AS MyCustomSQLColumn";
$myTableListField->setCustomCsvQuery($customQuery);
`[api:TableListField]` also tries to resolve Component-relations(has_one, has_many) and custom getters automatically:
:::php
$myTableListField = new TableListField(
'MyName',
'Product',
array(
'Buyer.LastName',
'PriceWithShipping'
)
);
// Product.php Example
class Product extends DataObject {
$has_one = array('Buyer'=>'Member');
public function getPriceWithShipping() {
return $this->Price + $this->Shipping;
}
}
### Pagination
Paging works by AJAX, but also works without javascript on link-basis.
:::php
$myTableListField->setPageSize(100); // defaults to 20
### Sorting
The easiest method is to add the sorting criteria as a constructor parameter. Sorting should be applied manually, where
appropriate. Only direct columns from the produced SQL-query are supported.
Example (sorting by "FirstName" column):
:::php
$report = new TableListField(
'CorporateReport', // name
'Member', // sourceClass
array(
'ID' => 'ID',
'FirstName' => 'First Name',
'LastName' => 'Last Name',
), // fieldList
null, // sourceFilter
'FirstName' // sourceSort
);
If you want to sort by custom getters in your `[api:DataObject]`, please reformulate them to a custom SQL column. This
restriction is needed to avoid performance-hits by caching and sorting potentially large datasets on PHP-level.
### Casting
Column-values can be casted, based on the casting-types available through DBObject (framework/core/model/fieldtypes).
:::php
$myTableListField->setFieldCasting(array(
"MyCustomDate"=>"Date",
"MyShortText"=>"Text->FirstSentence"
));
### Permissions
Permissions vary in different `[api:TableListField]`-implementations, and are evaluated in the template.
By default, all listed permissions are enabled.
:::php
$myTableListField->setPermissions(array(
'delete',
'export',
'print'
));
### Formatting
Specify custom formatting for fields, e.g. to render a link instead of pure text.
Caution: Make sure to escape special php-characters like in a normal php-statement.
:::php
$myTableListField->setFieldFormatting(array(
"myFieldName" => '<a href=\"custom-admin/$ID\">$ID</a>'
));
### Highlighting
"Highlighting" is similiar to "Formatting", but applies to the whole row rather than a column.
Definitions for highlighting table-rows with a specific CSS-class. You can use all column-names
in the result of a query. Use in combination with {@setCustomQuery} to select custom properties and joined objects.
:::php
$myTableListField->setHighlightConditions(array(
array(
"rule" => '$Flag == "red"',
"class" => "red"
),
array(
"rule" => '$Flag == "orange"',
"class" => "orange"
)
));
### Export
Export works only to CSV currently, with following specs:
* Line delimiter: "\n"
* Separator: ";"
* Column-quotes: none
:::php
$myTableListField->setPermissions(array('export'));
$myTableListField->setFieldListCsv(array(
'Price' => 'Price',
'ItemCount' => 'Item Count',
'ModelNumber' => 'Model Number'
));
You can influence the exported values by adjusting the generated SQL.
:::php
$customCsvQuery = singleton('Product')->buildSQL();
$customCsvQuery->select[] = "CONCAT(col1,col2) AS MyCustomSQLColumn";
$myTableListField->setCustomCsvQuery($customQuery);
$myTableListField->setFieldListCsv(array(
'MyCustomSQLColumn'
));
### Row-Summaries
You can summarize specific columns in your result-set. The term "summary" is used in a broad sense, you can also
implement averages etc.
:::php
$myTableListField->addSummary(
'Total Revenue and Sales Count',
array(
"Price" => array("sum","Currency->Nice"),
"ItemCount" => "sum"
)
);
In `[api:TableListField]`-implementation, these summaries also react to changes in input-fields by javascript.
Available methods:
* sum
* avg
### Grouping
Used to group by a specific column in the `[api:DataObject]` and create partial summaries.
Please use only together with addSummary().
(Automatically disables sorting).
:::php
$myTableListField->groupByField = 'MyColumnName';
## Best Practices
### Custom Sorting
Please subclass `[api:TableListField]` to implement custom sorting, following the naming-convention
"`colFunction_<yourFunctionName>`".
:::php
class CustomTableListField extends TableListField {
// referenced through "dateAverage"
public function colFunction_dateAverage($values) {
// custom date summaries
}
}
### Adding Utility-functions
In case you want to perform utility-functions like "export" or "print" through action-buttons,
make sure to subclass Utility() which collates all possible actions.
### Customizing Look & Feel
You can exchange the used template, e.g. to change applied CSS-classes or the HTML-markup:
:::php
$myTableListField->setTemplate("MyPrettyTableListField");
## API Documentation
`[api:TableListField]`

View File

@ -1,96 +0,0 @@
# Formal Treatment of the Template Language
This document gives you the maximum level of detail of how the template engine works. If you just want to know how to write templates, you probably want to read [this page](/reference/templates) instead.
## White space
## Expressions
### Literals
Definition:
literal := number | stringLiteral
number := digit { digit }*
stringLiteral := "\"" { character } * "\"" |
"'" { character } * "'"
Notes:
* digits in a number comprise a single token, so cannot have spaces
* any printable character can be included inside a string literal except the opening quote, unless
prefixed by a backslash (TODO: check this assumption with Hamish - its probably not right)
### Words
A word is used to identify a name of something, e.g. a property or method name. Words must start
with an alphabetic character or underscore, with subsequent characters being alphanumeric or underscore:
word := A-Za-z_ { A-Za-z0-9_ }*
### Properties and methods (variables)
Examples:
$PropertyName.Name
$MethodName
$MethodName("foo").SomeProperty
$MethodName("foo", "bar")
$MethodName("foo", $PropertyName)
Definition:
injection := "$" lookup | "{" "$" lookup "}"
call := word [ "(" argument { "," argument } ")" ]
lookup := call { "." call }*
argument := literal |
lookup |
"$" lookup
Notes:
* A word encountered with no parameter can be parsed as either a property or a method. TODO:
document the exact rules for resolution.
* TODO: include Up and Top in here. Not syntactic elements, but worth mentioning their semantics.
* TODO: consider the 2.4 syntax literals. These have been excluded as we don't want to encourage their
use.
### Operators
<exp> == <exp>
<exp> = <exp>
<exp> != <exp>
<exp> || <exp>
<exp> && <exp>
* TODO: document operator precedence, and include a descripton of what the operators do. || and && are short-circuit? Diff
between = and == or are they equivalent.
## Comments
## If
if := ifPart elseIfPart* elsePart endIfPart
ifPart := "<%" "if" ifCondition "%>" template
elseIfPart := "<%" "else_if" ifCondition "%>" template
elsePart := "<%" "else" "%>" template
endIfPart := "<%" "end_if" "%>"
ifCondition := TODO
template := TODO
## Require
## Loop
## With
## Translation
## Cache block
TODO to be elaborated
<cached arguments>...<end_cached>
<cacheblock arguments>...<end_cacheblock>
<uncached>...<uncached>

View File

@ -1,70 +0,0 @@
# Moving from SilverStripe 2 to SilverStripe 3
These are the main changes to the SiverStripe 3 template language.
## Control blocks: Loops vs. Scope
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.
In SilverStripe 3, the first usage (iteration) is replaced by `<% loop $var %>`. The second usage (scoping) is replaced by `<% with $var %>`
## Literals in Expressions
Prior to SilverStripe 3, literal values can appear in certain parts of an expression. For example, in the expression `<% if mydinner=kipper %>`, `mydinner` is treated as a property or method on the page or controller, and `kipper` is treated as a literal. This is fairly limited in use.
Literals can now be quoted, so that both literals and non-literals can be used in contexts where only literals were allowed before. This makes it possible to write the following:
* `<% if $mydinner=="kipper" %>...` which compares to the literal "kipper"
* `<% if $mydinner==$yourdinner %>...` which compares to another property or method on the page called `yourdinner`
Certain forms that are currently used in SilverStripe 2.x are still supported in SilverStripe 3 for backwards compatibility:
* `<% if mydinner==yourdinner %>...` is still interpreted as `mydinner` being a property or method, and `yourdinner` being a literal. It is strongly recommended to change to the new syntax in new implementations. The 2.x syntax is likely to be deprecated in the future.
Similarly, in SilverStripe 2.x, method parameters are treated as literals: `MyMethod(foo)` is now equivalent to `$MyMethod("foo")`. `$MyMethod($foo)` passes a variable to the method, which is only supported in SilverStripe 3.
## Method Parameters
Methods can now take an arbitrary number of parameters:
$MyMethod($foo,"active", $CurrentMember.FirstName)
Parameter values can be arbitrary expressions, including a mix of literals, variables, and even other method calls.
## Less sensitivity around spaces
Within a tag, a single space is equivalent to multiple consequetive spaces. e.g.
<% if $Foo %>
is equivalent to
<% if $Foo %>
## Removed view-specific accessors
Several methods in ViewableData that were originally added to expose values to the template language were moved,
in order to stop polluting the namespace. These were sometimes called by project-specific PHP code too, and that code
will need re-working.
#### Globals
Some of these methods were wrappers which simply called other static methods. These can simply be replaced with a call
to the wrapped method. The list of these methods is:
- CurrentMember() -> Member::currentUser()
- getSecurityID() -> SecurityToken::inst()->getValue()
- HasPerm($code) -> Permission::check($code)
- BaseHref() -> Director::absoluteBaseURL()
- AbsoluteBaseURL() -> Director::absoluteBaseURL()
- IsAjax() -> Director::is_ajax()
- i18nLocale() -> i18n::get_locale()
- CurrentPage() -> Controller::curr()
#### Scope-exposing
Some of the removed methods exposed access to the various scopes. These currently have no replacement. The list of
these methods is:
- Top

View File

@ -1,719 +0,0 @@
# Templates
## Introduction
SilverStripe templates consist of HTML code augmented with special control codes, described below. Because of this, you
can have as much control of your site's HTML code as you like.
Because the SilverStripe templating language is a string processing language it can therefore be used to make other
text-based data formats, such as XML or RTF.
Here is a very simple template:
:::ss
<html>
<head>
<% base_tag %>
<title>$Title</title>
<% require themedCSS("screen") %>
</head>
<body>
<header>
<h1>Bob's Chicken Shack</h1>
</header>
<% with $CurrentMember %>
<p>Welcome $FirstName $Surname.</p>
<% end_with %>
<% if $Dishes %>
<ul>
<% loop $Dishes %>
<li>$Title ($Price.Nice)</li>
<% end_loop %>
</ul>
<% end_if %>
<% include Footer %>
</body>
</html>
More sophisticated use of templates for pages managed in the CMS,
including template inheritance and navigation loops
is documented in the [page types](/topics/page-types) topic.
# Template elements
## Variables
Variables are things you can use in a template that grab data from the page and put in the HTML document. For example:
:::ss
$Title
This inserts the value of the Title field of the page being displayed in place of `$Title`. This type of variable is called a **property**. It is often something that can be edited in the CMS. Variables can be chained together, and include arguments.
:::ss
$Property
$Property(param)
$Property.SubProperty
These **variables** will call a method/field on the object and insert the returned value as a string into the template.
* `$Property` will call `$obj->Property()` (or the field `$obj->Property`)
* `$Property(param)` will call `$obj->Property("param")`
* `$Property.SubProperty` will call `$obj->Property()->SubProperty()` (or field equivalents)
If a variable returns a string, that string will be inserted into the template. If the variable returns an object, then
the system will attempt to render the object through its forTemplate() method. If the `forTemplate()` method has not been
defined, the system will return an error.
SilverStripe provides many additional properties on the `SiteTree` class,
see [Page Type Templates](/topics/page-type-templates) for details.
### Escaping
Sometimes you will have template tags which need to roll into one another. This can often result in SilverStripe looking
for a "FooBar" value rather than a "Foo" and then "Bar" value or when you have a string directly before or after the
variable you will need to escape the specific variable. In the following example `$Foo` is `3`.
:::ss
$Foopx // returns "" (as it looks for a Foopx value)
{$Foo}px // returns "3px" (CORRECT)
Or when having a `$` sign in front of the variable
:::ss
$$Foo // returns ""
${$Foo} // returns "$3"
You can also use a backslash to escape the name of the variable, such as:
:::ss
$Foo // returns "3"
\$Foo // returns "$Foo"
## Includes
Within SilverStripe templates we have the ability to include other templates from the Includes directory using the SS
'include' tag. For example, the following code would include the `Includes/SideBar.ss` code:
:::ss
<% include SideBar %>
The "include" tag can be particularly helpful for nested functionality. In this example, the include only happens if
a variable is true
:::ss
<% if $CurrentMember %>
<% include MembersOnlyInclude %>
<% end_if %>
Includes can't directly access the parent scope of the scope active when the include is included. However you can
pass arguments to the include, which are available on the scope top within the include
:::ss
<% with $CurrentMember %>
<% include MemberDetails PageTitle=$Top.Title, PageID=$Top.ID %>
<% end_with %>
You can also perform includes using the Requirements Class via the template controls. See the section on
[Includes in Templates](requirements#including_inside_template_files) for more details and examples.
:::ss
<% require themedCSS("LeftNavMenu") %>
### Including CSS and JavaScript files (a.k.a "Requirements")
See [CSS](/topics/css) and [Javascript](/topics/javascript) topics for individual including of files and
[requirements](reference/requirements) for good examples of including both Javascript and CSS files.
## Conditional Logic
You can conditionally include markup in the output. That is, test for something
that is true or false, and based on that test, control what gets output.
The simplest if block is to check for the presence of a value.
:::ss
<% if $CurrentMember %>
<p>You are logged in as $CurrentMember.FirstName $CurrentMember.Surname.</p>
<% end_if %>
The following compares a page property called `MyDinner` with the value in
quotes, `kipper`, which is a **literal**. If true, the text inside the if-block
is output.
:::ss
<% if $MyDinner=="kipper" %>
Yummy, kipper for tea.
<% end_if %>
Note that inside a tag like this, variables should have a '$' prefix, and
literals should have quotes. SilverStripe 2.4 didn't include the quotes or $
prefix, and while this still works, we recommend the new syntax as it is less
ambiguous.
This example shows the use of the `else` option. The markup after `else` is
output if the tested condition is *not* true.
:::ss
<% if $MyDinner=="kipper" %>
Yummy, kipper for tea
<% else %>
I wish I could have kipper :-(
<% end_if %>
This example shows the user of `else_if`. There can be any number of `else_if`
clauses. The conditions are tested from first to last, until one of them is true,
and the markup for that condition is used. If none of the conditions are true,
the markup in the `else` clause is used, if that clause is present.
:::ss
<% if $MyDinner=="quiche" %>
Real men don't eat quiche
<% else_if $MyDinner==$YourDinner %>
We both have good taste
<% else %>
Can I have some of your chips?
<% end_if %>
This example shows the use of `not` to negate the test.
:::ss
<% if not $DinnerInOven %>
I'm going out for dinner tonight.
<% end_if %>
You can combine two or more conditions with `||` ("or"). The markup is used if
*either* of the conditions is true.
:::ss
<% if $MyDinner=="kipper" || $MyDinner=="salmon" %>
yummy, fish for tea
<% end_if %>
You can combine two or more conditions with `&&` ("and"). The markup is used if
*both* of the conditions are true.
:::ss
<% if $MyDinner=="quiche" && $YourDinner=="kipper" %>
Lets swap dinners
<% end_if %>
You can use inequalities like `<`, `<=`, `>`, `>=` to compare numbers.
:::ss
<% if $Number>="5" && $Number<="10" %>
Number between 5 and 10
<% end_if %>
## Looping Over Lists
The `<% loop %>...<% end_loop %>` tag is used to **iterate** or loop over a
collection of items. For example:
:::ss
<ul>
<% loop $Children %>
<li>$Title</li>
<% end_loop %>
</ul>
This loops over the children of a page, and generates an unordered list showing
the `Title` property from each one. Note that `$Title` *inside* the loop refers
to the `Title` property on each object that is looped over, not the current page.
This is know as the `Scope` of the template. For more information about `Scope`
see the section below.
To refer to the current page's `Title` property inside the loop, you can do
`$Up.Title`. More about `Up` later.
`$Me` can be used to refer to the current object context the template is rendered
with.
### Position Indicators
Inside the loop scope, there are many variables at your disposal to determine the
current position in the list and iteration:
* `$Even`, `$Odd`: Returns boolean, handy for zebra striping
* `$EvenOdd`: Returns a string, either 'even' or 'odd'. Useful for CSS classes.
* `$First`, `$Last`, `$Middle`: Booleans about the position in the list
* `$FirstLast`: Returns a string, "first", "last", or "". Useful for CSS classes.
* `$Pos`: The current position in the list (integer). Will start at 1.
* `$TotalItems`: Number of items in the list (integer)
### Altering the list
`<% loop %>` statements iterate over a `[api:DataList]` instance. As the
template has access to the list object, templates can call `[api:DataList]`
functions. For instance, see the following examples:
Providing a custom sort.
:::ss
<ul>
<% loop $Children.Sort(Title) %>
<li>$Title</li>
<% end_loop %>
</ul>
Limiting the number of items displayed.
:::ss
<ul>
<% loop $Children.Limit(10) %>
<li>$Title</li>
<% end_loop %>
</ul>
Reversing the loop.
:::ss
<ul>
<% loop $Children.Reverse %>
<li>$Title</li>
<% end_loop %>
</ul>
The `DataList` class also supports chaining methods. For example, to reverse
the list and output the last 3 items we would write:
:::ss
<ul>
<% loop $Children.Reverse.Limit(3) %>
<li>$Title</li>
<% end_loop %>
</ul>
### Modulus and MultipleOf
$Modulus and $MultipleOf can help to build column layouts.
:::ss
$Modulus(value, offset) // returns an int
$MultipleOf(factor, offset) // returns a boolean.
The following example demonstrates how you can use $Modulus(4) to generate
custom column names based on your loop statement. Note that this works for any
control statement (not just children).
:::ss
<% loop $Children %>
<div class="column-{$Modulus(4)}">
...
</div>
<% end_loop %>
Will return you column-3, column-2, column-1, column-0, column-3 etc. You can
use these as styling hooks to float, position as you need.
You can also use $MultipleOf(value, offset) to help build columned layouts. In
this case we want to add a <br> after every 3th item.
:::ss
<% loop $Children %>
<% if $MultipleOf(3) %>
<br>
<% end_if %>
<% end_loop %>
## Scope
In the `<% loop %>` section, we saw an example of two **scopes**. Outside the
`<% loop %>...<% end_loop %>`, we were in the scope of the page. But inside the
loop, we were in the scope of an item in the list. The scope determines where
the value comes from when you refer to a variable. Typically the outer scope of
a page type's layout template is the page that is currently being rendered.
The outer scope of an included template is the scope that it was included into.
### Up
When we are in a scope, we sometimes want to refer to the scope outside the
<% loop %> or <% with %>. We can do that easily by using `$Up`. `$Up` takes
the scope back to the previous level. Take the following example:
:::ss
$Title
--
<% loop $Children %>
$Title
$Up.Title
--
<% loop $Children %>
$Title
$Up.Title
<% end_loop %>
<% end_loop %>
With a page structure (Blog -> Blog entry -> Child blog entry) the
above will produce:
:::ss
Blog
--
Blog entry
Blog
--
Child blog entry
Blog entry
### Top
While `$Up` provides us a way to go up 1 scope, `$Top` is a shortcut to jump to
the top most scope of the page. Using the previous example but expanded to
include `$Top`:
:::ss
$Title
--
<% loop $Children %>
$Title
$Up.Title
$Top.Title
--
<% loop $Children %>
$Title
$Up.Title
$Top.Title
<% end_loop %>
<% end_loop %>
Will produce
:::ss
Blog
--
Blog entry
Blog
Blog
--
Child blog entry
Blog entry
Blog
### With
The `<% with %>...<% end_with %>` tag lets you introduce a new scope. Consider
the following example:
:::ss
<% with $CurrentMember %>
Hello $FirstName, welcome back. Your current balance is $Balance.
<% end_with %>
Outside the `<% with %>...<% end_with %>`, we are in the page scope. Inside it,
we are in the scope of `$CurrentMember`. We can refer directly to properties and
methods of that member. So $FirstName is equivalent to $CurrentMember.FirstName.
This keeps the markup clean, and if the scope is a complicated expression we don't
have to repeat it on each reference of a property.
`<% with %>` also lets us use a collection as a scope, so we can access
properties of the collection itself, instead of iterating over it. For example:
:::ss
$Children.Count
returns the number of items in the $Children collection.
## Pagination
Lists can be paginated, and looped over to generate pagination. For this to
work, the list needs to be wrapped in a `[api:PaginatedList]`. The process is
explained in detail on the ["pagination" howto](/howto/pagination).
The list is split up in multiple "pages", each . Note that "page" is this context
does not necessarily refer to a `Page` class (although it often happens to be one).
* `$MoreThanOnePage`: Returns true when we have a multi-page list, restricted with a limit.
* `$NextLink`, `$PrevLink`: This returns links to the next and previous page in a multi-page datafeed. They will return blank if there's no appropriate page to go to, so `$PrevLink` will return blank when you're on the first page.
* `$CurrentPage`: Current page iterated on
* `$TotalPages`: Total number of pages
* `$TotalItems`: This returns the total number of items across all pages.
* `$Pages`: The actual (limited) list of records, use in an inner loop
* `$PageNum`: Page number, starting at 1 (within `$Pages`)
* `$Link`: Links to the current controller URL, setting this page as current via a GET parameter (within `$Pages`)
* `$CurrentBool`: Returns true if you're currently on that page (within `$Pages`)
## Formatting and Casting
Properties are usually auto-escaped in templates to ensure consistent representation,
and avoid format clashes like displaying unescaped ampersands in HTML.
By default, values are escaped as `XML`, which is equivalent to `HTML` for this purpose.
There's some exceptions to this rule, see the ["security" topic](/topics/security).
In case you want to explicitly allow unescaped 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.
:::ss
$Content.XML // transforms e.g. "<em>alert</em>" to "&lt;em&gt;alert&lt;/em&gt;"
Apart from value formatting, there's many methods to transform them as well,
For example, the built in `$Now` placeholder is an instance of `[api:Date]`,
and returns the current date in a standard system format.
Since its an object, you can use the helper methods to return other formats:
:::ss
$Now.Year // Current year
$Now.Nice // Localized date, based on i18n::get_locale()
See [data-types](/topics/data-types) for more information.
## Translations
Translations are easy to use with a template, and give access to SilverStripe's translation facilities. Here is an example:
<%t Member.WELCOME 'Welcome {name} to {site}' name=$Member.Name site="Foobar.com" %>
Pulling apart this example we see:
* `Member.WELCOME` is an identifier in the translation system, for which different translations may be available. This string may include named placeholders, in braces.
* `'Welcome {name} to {site}'` is the default string used, if there is no translation for Member.WELCOME in the current locale. This contains named placeholders.
* `name=$Member.Name` assigns a value to the named placeholder `name`. This value is substituted into the translation string wherever `{name}` appears in that string. In this case, it is assigning a value from a property `Member.Name`
* `site="Foobar.com"` assigns a literal value to another named placeholder, `site`.
## Comments
Using standard HTML comments is supported. These comments will be included in the published site.
:::ss
$EditForm <!-- Some public comment about the form -->
However you can also use special SilverStripe comments which will be stripped out of the published site. This is useful
for adding notes for other developers but for things you don't want published in the public html.
:::ss
$EditForm <%-- Some hidden comment about the form --%>
## Partial Caching
Partial caching lets you define blocks of your template that are cached for better performance. See [Partial Caching](/reference/partial-caching) for more information.
### Base Tag
The `<% base_tag %>` placeholder is replaced with the HTML base element. Relative links within a document (such as `<img
src="someimage.jpg" />`) will become relative to the URI specified in the base tag. This ensures the browser knows where
to locate your sites images and css files. So it is a must for templates!
It renders in the template as `<base href="http://www.mydomain.com" /><!--[if lte IE 6]></base><![endif]-->`
## CurrentMember
Returns the currently logged in member, if there is one.
All of their details or any special Member page controls can be called on this.
Alternately, you can use `<% if $CurrentMember %>` to detect whether someone has logged
in.
:::ss
<% if $CurrentMember %>
Welcome Back, $CurrentMember.FirstName
<% end_if %>
## Custom Template Variables and Controls
There are two ways you can extend the template variables you have available. You can create a new database field in your
`$db` or if you do not need the variable to be editable in the cms you can create a function which returns a value in your
`Page.php` class.
:::php
// mysite/code/Page.php
public function MyCustomValue() {
return "Hi, this is my site";
}
Will give you the ability to call `$MyCustomValue` from anywhere in your template.
:::ss
I've got one thing to say to you: <i>$MyCustomValue</i>
// output "I've got one thing to say to you: <i>Hi, this is my site</i>"
Your function could return a single value as above or it could be a subclass of `[api:ArrayData]` for example a
`[api:DataObject]` with many values then each of these could be accessible via a control loop
:::php
// ...
public function MyCustomValues() {
return new ArrayData(array("Hi" => "Kia Ora", "Name" => "John Smith"));
}
And now you could call these values by using
:::ss
<% with $MyCustomValues %>
$Hi , $Name
<% end_with %>
// output "Kia Ora , John Smith"
Or by using the dot notation you would have
:::ss
$MyCustomValues.Hi , $MyCustomValues.Name
// output "Kia Ora , John Smith"
### Side effects
All functions that provide data to templates must have no side effects, as the value is cached after first access. For example, this controller method
:::php
private $counter = 0;
public function Counter() {
$this->counter += 1;
return $this->counter;
}
and this template
:::ss
$Counter, $Counter, $Counter
will render as "1, 1, 1", not "1, 2, 3"
## .typography style
By default, SilverStripe includes the `theme/css/typography.css` file into the Content area. So you should always include the
typography style around the main body of the site so both styles appear in the CMS and on the template. Where the main body of
the site is can vary, but usually it is included in the /Layout files. These files are included into the main Page.ss template
by using the `$Layout` variable so it makes sense to add the .typography style around $Layout.
:::ss
<div class="typography">
$Layout
</div>
## Calling templates from PHP code
This is all very well and good, but how do the templates actually get called?
Templates do nothing on their own. Rather, they are used to render *a particular object*. All of the `<% if %>`, `<%control %>`,
and variable codes are methods or parameters that are called *on that object*. All that is necessary is
that the object is an instance of `[api:ViewableData]` (or one of its subclasses).
The key is `[api:ViewableData::renderWith()]`. This method is passed a For example, within the controller's default action,
there is an instruction of the following sort:
:::php
$controller->renderWith("TemplateName");
Here's what this line does:
* First `renderWith()` constructs a new object: `$template = new SSViewer("TemplateName");`
* `[api:SSViewer]` will take the content of `TemplateName.ss`, and turn it into PHP code.
* Then `renderWith()` passes the controller to `$template->process($controller);`
* `SSViewer::process()` will execute the PHP code generated from `TemplateName.ss` and return the results.
`renderWith()` returns a string - the populated template. In essence, it uses a template to cast an object to a string.
`renderWith()` can also be passed an array of template names. If this is done, then `renderWith()` will use the first
available template name.
Below is an example of how to implement renderWith. In the example below the page is rendered using the myAjaxTemplate
if the page is called by an ajax function (using `[api:Director::is_ajax()]`). Note that the index function is called by
default if it exists and there is no action in the url parameters.
:::php
class MyPage_Controller extends Page_Controller {
private static $allowed_actions = array('index');
public function init(){
parent::init();
}
public function index() {
if(Director::is_ajax()) {
return $this->renderWith("myAjaxTemplate");
} else {
return Array();// execution as usual in this case...
}
}
}
## Anchor Link rewriting
Anchor links are links with a "#" in them. A frequent use-case is to use anchor links to point to different
sections of the current page. For example, we might have this in our template.
For, example, we might have this on http://www.example.com/my-long-page/
:::ss
<ul>
<li><a href="#section1">Section 1</a></li>
<li><a href="#section2">Section 2</a></li>
</ul>
So far, so obvious. However, things get tricky because of we have set our `<base>` tag to point to the root of your
site. So, when you click the first link you will be sent to http://www.example.com/#section1 instead of
http://www.example.com/my-long-page/#section1
In order to prevent this situation, the SSViewer template renderer will automatically rewrite any anchor link that
doesn't specify a URL before the anchor, prefixing the URL of the current page. For our example above, the following
would be created:
:::ss
<ul>
<li><a href="my-long-page/#section1">Section 1</a></li>
<li><a href="my-long-page/#section2">Section 2</a></li>
</ul>
There are cases where this can be unhelpful. HTML anchors created from Ajax responses are the most common. In these
situations, you can disable anchor link rewriting by setting the
`SSViewer.rewrite_hash_links` configuration value to `false`.
### More Advanced Controls
Template variables and controls are just PHP properties and methods
on the underlying controllers and model classes.
We've just shown you the most common once, in practice
you can use any public API on those classes, and [extend](/reference/dataextension) them
with your own. To get an overview on what's available to you,
we recommend that you dive into the API docs for the following classes:
* `[api:Controller]`: Generic controller class
* `[api:DataObject]`: Generic model class
* `[api:ViewableData]`: Underlying object class for pretty much anything displayable
## Designing reusable templates
Although SilverStripe is ultimately flexible in how you create your templates, there's a couple of best practices. These
will help you to design templates for modules, and make it easier for other site developers to integrate them into their
own base templates.
* Most of your templates should be `Layout` templates
* Build your templates as a [Theme](/topics/themes) so you can easily re-use and exchange them
* Your layout template should include a standard markup structure (`<div id="Layout">$Layout</div>`)
* Layout templates only include content that could be completely replaced by another module (e.g. a forum thread). It
might be infeasible to do this 100%, but remember that every piece of navigation that needs to appear inside `$Layout`
will mean that you have to customise templates when integrating the module.
* Any CSS applied to layout templates should be flexible width. This means the surrounding root template can set its
width independently.
* Don't include any navigation elements in your `Layout` templates, they should be contained in the root template.
* Break down your templates into groups of includes. Site integrators would then have the power to override individual
includes, rather than entire templates.
## Related
* [Built in page controls](/reference/built-in-page-controls)
* [Page Type Templates](/topics/page-type-templates)
* [Typography](/reference/typography)
* [Themes](/topics/themes)
* [Widgets](/topics/widgets)
* [Images](/reference/image)
* [Tutorial 1: Building a basic site](/tutorials/1-building-a-basic-site)
* [Tutorial 2: Extending a basic site](/tutorials/2-extending-a-basic-site)
* [Developing Themes](/topics/theme-development)
* [Templates: formal syntax description](/reference/templates-formal-syntax)

View File

@ -1,106 +0,0 @@
# Typography
## Introduction
SilverStripe lets you customise the style of content in the CMS.
## Usage
This is done by setting up a CSS file called (projectname)/css/typography.css.
You also need to create a file called (projectname)/css/editor.css with the following content:
:::css
/**
* This support file is used to style the WYSIWYG editor in the CMS
*/
@import "typography.css";
body.mceContentBody {
min-height: 200px;
font-size: 62.5%;
}
body.mceContentBody a.broken {
background-color: #FF7B71;
border: 1px red solid;
}
In typography.css you can define styles of any of the tags that will get created by the editor:
* P, BLOCKQUOTE
* H1-6
* UL, OL, LI
* TABLE
* STRONG, EM, U
* A
It's important to realise that this CSS file is included directly into the CMS system, and if you aren't careful, you
can alter the styling of other parts of the interface. While this is novel, it can be dangerous and is probably not
what you're after.
The way around this is to limit all your styling selectors to elements inside something with `class="typography"`. The
other half of this is to put `class="typography"` onto any area in your template where you would like the styling to be
applied.
**WRONG**
:::css
CSS:
h1, h2 {
color: #F77;
}
Template:
<div>
$Content
</div>
**RIGHT**
:::css
CSS:
.typography h1, .typography h2 {
color: #F77;
}
Template:
<div class="typography">
$Content
</div>
If you would to include different styles for different sections of your site, you can use class names the same as the
name of the data fields. This example sets up different paragraph styles for 2 HTML editor fields called Content and
OtherContent:
:::css
.Content.typography p {
font-size: 12px;
}
.OtherContent.typography p {
font-size: 10px;
}
### Removing the typography class
Sometimes, it's not enough to add a class, you also want to remove the typography class. You can use the
`[api:HTMLEditorField]` method setCSSClass.
This example sets another CSS class typographybis:
:::php
public function getCMSFields() {
...
$htmleditor = new HTMLEditorField("ContentBis", "Content Bis");
$htmleditor->setCSSClass('typographybis');
$fields->addFieldToTab("Root.Content", $htmleditor);
...
return $fields;
}
**Note:** This functionality will be available in the version 2.0.2 of the CMS.

View File

@ -1,395 +0,0 @@
# UploadField
## Introduction
The UploadField will let you upload one or multiple files of all types,
including images. But that's not all it does - it will also link the
uploaded file(s) to an existing relation and let you edit the linked files
as well. That makes it flexible enough to sometimes even replace the Gridfield,
like for instance in creating and managing a simple gallery.
## Usage
The field can be used in three ways: To upload a single file into a `has_one` relationship,
or allow multiple files into a `has_many` or `many_many` relationship, or to act as a stand
alone uploader into a folder with no underlying relation.
## Validation
Although images are uploaded and stored on the filesystem immediately after selection,
the value (or values) of this field will not be written to any related record until
the record is saved and successfully validated. However, any invalid records will still
persist across form submissions until explicitly removed or replaced by the user.
Care should be taken as invalid files may remain within the filesystem until explicitly
removed.
### Single fileupload
The following example adds an UploadField to a page for single fileupload,
based on a has_one relation:
:::php
class GalleryPage extends Page {
private static $has_one = array(
'SingleImage' => 'Image'
);
function getCMSFields() {
$fields = parent::getCMSFields();
$fields->addFieldToTab(
'Root.Upload',
$uploadField = new UploadField(
$name = 'SingleImage',
$title = 'Upload a single image'
)
);
return $fields;
}
}
The UploadField will autodetect the relation based on it's `name` property, and
save it into the GalleyPages' `SingleImageID` field. Setting the
`setAllowedMaxFileNumber` to 1 will make sure that only one image can ever be
uploaded and linked to the relation.
### Multiple fileupload
Enable multiple fileuploads by using a many_many (or has_many) relation. Again,
the `UploadField` will detect the relation based on its $name property value:
:::php
class GalleryPage extends Page {
private static $many_many = array(
'GalleryImages' => 'Image'
);
function getCMSFields() {
$fields = parent::getCMSFields();
$fields->addFieldToTab(
'Root.Upload',
$uploadField = new UploadField(
$name = 'GalleryImages',
$title = 'Upload one or more images (max 10 in total)'
)
);
$uploadField->setAllowedMaxFileNumber(10);
return $fields;
}
}
class GalleryPage_Controller extends Page_Controller {
}
class GalleryImageExtension extends DataExtension {
private static $belongs_many_many = array('Galleries' => 'GalleryPage);
}
Image::add_extension('GalleryImageExtension');
<div class="notice" markdown='1'>
In order to link both ends of the relationship together it's usually advisable to
extend Image with the necessary $has_one, $belongs_to, $has_many or $belongs_many_many.
In particular, a DataObject with $has_many Images will not work without this specified explicitly.
</div>
## Configuration
### Overview
The field can either be configured on an instance level with the various
getProperty and setProperty functions, or globally by overriding the YAML defaults.
See the [Configuration Reference](uploadfield#configuration-reference) section for possible values.
Example: mysite/_config/uploadfield.yml
after: framework#uploadfield
---
UploadField:
defaultConfig:
canUpload: false
### Set a custom folder
This example will save all uploads in the `/assets/customfolder/` folder. If
the folder doesn't exist, it will be created.
:::php
$fields->addFieldToTab(
'Root.Upload',
$uploadField = new UploadField(
$name = 'GalleryImages',
$title = 'Please upload one or more images' )
);
$uploadField->setFolderName('customfolder');
### Limit the allowed filetypes
`AllowedExtensions` defaults to the `File.allowed_extensions` configuration setting,
but can be overwritten for each UploadField:
:::php
$uploadField->setAllowedExtensions(array('jpg', 'jpeg', 'png', 'gif'));
Entire groups of file extensions can be specified in order to quickly limit types
to known file categories.
:::php
// This will limit files to the following extensions:
// "bmp" ,"gif" ,"jpg" ,"jpeg" ,"pcx" ,"tif" ,"png" ,"alpha","als" ,"cel" ,"icon" ,"ico" ,"ps"
// 'doc','docx','txt','rtf','xls','xlsx','pages', 'ppt','pptx','pps','csv', 'html','htm','xhtml', 'xml','pdf'
$uploadField->setAllowedFileCategories('image', 'doc');
`AllowedExtensions` can also be set globally via the [YAML configuration](/topics/configuration#setting-configuration-via-yaml-files), for example you may add the following into your mysite/_config/config.yml:
:::yaml
File:
allowed_extensions:
- 7zip
- xzip
### Limit the maximum file size
`AllowedMaxFileSize` is by default set to the lower value of the 2 php.ini configurations: `upload_max_filesize` and `post_max_size`
The value is set as bytes.
NOTE: this only sets the configuration for your UploadField, this does NOT change your server upload settings, so if your server is set to only allow 1 MB and you set the UploadFIeld to 2 MB, uploads will not work.
:::php
$sizeMB = 2; // 2 MB
$size = $sizeMB * 1024 * 1024; // 2 MB in bytes
$this->getValidator()->setAllowedMaxFileSize($size);
### Preview dimensions
Set the dimensions of the image preview. By default the max width is set to 80
and the max height is set to 60.
:::php
$uploadField->setPreviewMaxWidth(100);
$uploadField->setPreviewMaxHeight(100);
### Disable attachment of existing files
This can force the user to upload a new file, rather than link to the already
existing file librarry
:::php
$uploadField->setCanAttachExisting(false);
### Disable uploading of new files
Alternatively, you can force the user to only specify already existing files
in the file library
:::php
$uploadField->setCanUpload(false);
### Automatic or manual upload
By default, the UploadField will try to automatically upload all selected files.
Setting the `autoUpload` property to false, will present you with a list of
selected files that you can then upload manually one by one:
:::php
$uploadField->setAutoUpload(false);
### Change Detection
The CMS interface will automatically notify the form containing
an UploadField instance of changes, such as a new upload,
or the removal of an existing upload (through a `dirty` event).
The UI can then choose an appropriate response (e.g. highlighting the "save" button).
If the UploadField doesn't save into a relation, there's
technically no saveable change (the upload has already happened),
which is why this feature can be disabled on demand.
:::php
$uploadField->setConfig('changeDetection', false);
### Build a simple gallery
A gallery most times needs more then simple images. You might want to add a
description, or maybe some settings to define a transition effect for each slide.
First create a
[DataExtension](http://doc.silverstripe.org/framework/en/reference/dataextension)
like this:
:::php
class GalleryImage extends DataExtension {
private static $db = array(
'Description' => 'Text'
);
private static $belongs_many_many = array(
'GalleryPage' => 'GalleryPage'
);
}
Now register the DataExtension for the Image class in your _config.php:
:::php
Image::add_extension('GalleryImage');
<div class="notice" markdown='1'>
Note: Although you can subclass the Image class instead of using a DataExtension,
this is not advisable. For instance: when using a subclass, the 'From files'
button will only return files that were uploaded for that subclass, it won't
recognize any other images!
</div>
### Edit uploaded images
By default the UploadField will let you edit the following fields: *Title,
Filename, Owner and Folder*. The `fileEditFields` configuration setting allows
you you alter these settings. One way to go about this is create a
`getCustomFields` function in your GalleryImage object like this:
:::php
class GalleryImage extends DataExtension {
...
function getCustomFields() {
$fields = new FieldList();
$fields->push(new TextField('Title', 'Title'));
$fields->push(new TextareaField('Description', 'Description'));
return $fields;
}
}
Then, in your GalleryPage, tell the UploadField to use this function:
:::php
$uploadField->setFileEditFields('getCustomFields');
In a similar fashion you can use 'setFileEditActions' to set the actions for the
editform, or 'fileEditValidator' to determine the validator (eg RequiredFields).
### Configuration Reference
- `setAllowedMaxFileNumber`: (int) php validation of allowedMaxFileNumber
only works when a db relation is available, set to null to allow
unlimited if record has a has_one and allowedMaxFileNumber is null, it will be set to 1
- `setAllowedFileExtensions`: (array) List of file extensions allowed
- `setAllowedFileCategories`: (array|string) List of types of files allowed.
May be any of 'image', 'audio', 'mov', 'zip', 'flash', or 'doc'
- `setAutoUpload`: (boolean) Should the field automatically trigger an upload once
a file is selected?
- `setCanAttachExisting`: (boolean|string) Can the user attach existing files from the library.
String values are interpreted as permission codes.
- `setCanPreviewFolder`: (boolean|string) Can the user preview the folder files will be saved into?
String values are interpreted as permission codes.
- `setCanUpload`: (boolean|string) Can the user upload new files, or just select from existing files.
String values are interpreted as permission codes.
- `setDownloadTemplateName`: (string) javascript template used to display already
uploaded files, see javascript/UploadField_downloadtemplate.js
- `setFileEditFields`: (FieldList|string) FieldList $fields or string $name
(of a method on File to provide a fields) for the EditForm (Example: 'getCMSFields')
- `setFileEditActions`: (FieldList|string) FieldList $actions or string $name
(of a method on File to provide a actions) for the EditForm (Example: 'getCMSActions')
- `setFileEditValidator`: (string) Validator (eg RequiredFields) or string $name
(of a method on File to provide a Validator) for the EditForm (Example: 'getCMSValidator')
- `setOverwriteWarning`: (boolean) Show a warning when overwriting a file.
- `setPreviewMaxWidth`: (int)
- `setPreviewMaxHeight`: (int)
- `setTemplateFileButtons`: (string) Template name to use for the file buttons
- `setTemplateFileEdit`: (string) Template name to use for the file edit form
- `setUploadTemplateName`: (string) javascript template used to display uploading
files, see javascript/UploadField_uploadtemplate.js
- `setCanPreviewFolder`: (boolean|string) Is the upload folder visible to uploading users?
String values are interpreted as permission codes.
Certain default values for the above can be configured using the YAML config system.
:::yaml
UploadField:
defaultConfig:
autoUpload: true
allowedMaxFileNumber:
canUpload: true
canAttachExisting: 'CMS_ACCESS_AssetAdmin'
canPreviewFolder: true
previewMaxWidth: 80
previewMaxHeight: 60
uploadTemplateName: 'ss-uploadfield-uploadtemplate'
downloadTemplateName: 'ss-uploadfield-downloadtemplate'
overwriteWarning: true # Warning before overwriting existing file (only relevant when Upload: replaceFile is true)
The above settings can also be set on a per-instance basis by using `setConfig` with the appropriate key.
You can also configure the underlying `[api:Upload]` class, by using the YAML config system.
:::yaml
Upload:
# Globally disables automatic renaming of files and displays a warning before overwriting an existing file
replaceFile: true
uploads_folder: 'Uploads'
## Using the UploadField in a frontend form
The UploadField can be used in a frontend form, given that sufficient attention is given
to the permissions granted to non-authorised users.
By default Image::canDelete and Image::canEdit do not require admin privileges, so
make sure you override the methods in your Image extension class.
For instance, to generate an upload form suitable for saving images into a user-defined
gallery the below code could be used:
:::php
// In GalleryPage.php
class GalleryPage extends Page {}
class GalleryPage_Controller extends Page_Controller {
private static $allowed_actions = array('Form');
public function Form() {
$fields = new FieldList(
new TextField('Title', 'Title', null, 255),
$field = new UploadField('Images', 'Upload Images')
);
$field->setCanAttachExisting(false); // Block access to Silverstripe assets library
$field->setCanPreviewFolder(false); // Don't show target filesystem folder on upload field
$field->relationAutoSetting = false; // Prevents the form thinking the GalleryPage is the underlying object
$actions = new FieldList(new FormAction('submit', 'Save Images'));
return new Form($this, 'Form', $fields, $actions, null);
}
public function submit($data, Form $form) {
$gallery = new Gallery();
$form->saveInto($gallery);
$gallery->write();
return $this;
}
}
// In Gallery.php
class Gallery extends DataObject {
private static $db = array(
'Title' => 'Varchar(255)'
);
private static $many_many = array(
'Images' => 'Image'
);
}
// In ImageExtension.php
class ImageExtension extends DataExtension {
private static $belongs_many_many = array(
'Gallery' => 'Gallery'
);
function canEdit($member) {
// This part is important!
return Permission::check('ADMIN');
}
}
Image::add_extension('ImageExtension');

View File

@ -1,153 +0,0 @@
# Commandline Usage via "sake"
## Introduction
SilverStripe can call controllers through commandline `php` just as easily as through a web browser.
This can be handy to automate tasks with cron jobs, run unit tests and maintenance tasks,
and a whole bunch of other scripted goodness.
The main entry point for any commandline execution is `cli-script.php`. For example, to run a database rebuild
from the commandline, use this command:
cd your-webroot/
php framework/cli-script.php dev/build
Make sure that your commandline php version uses the same configuration as your webserver (run `php -i` to find out more).
## GET parameters as arguments
You can add parameters to the command by using normal form encoding.
All parameters will be available in `$_GET` within SilverStripe.
cd your-webroot/
php framework/cli-script.php myurl myparam=1 myotherparam=2
## SAKE: SilverStripe make
Sake is a simple wrapper around `cli-script.php`. It also tries to detect which `php` executable to use
if more than one are available.
**If you are using a debian server:** Check you have the php-cli package installed for sake to work.
If you get an error when running the command php -v, then you may not have php-cli installed so sake won't work.
### Installation
You can copy the `sake` file into `/usr/bin/sake` for easier access (this is optional):
cd your-webroot/
sudo ./framework/sake installsake
Note: This currently only works on unix-like systems, not on Windows.
## Configuration
Sometimes SilverStripe needs to know the URL of your site, for example, when sending an email. When you're visiting
your site in a web browser this is easy to work out, but if you're executing scripts on the command-line, it has no way
of knowing.
To work this out, you should add lines of this form to your [_ss_environment.php](/topics/environment-management) file.
:::php
global $_FILE_TO_URL_MAPPING;
$_FILE_TO_URL_MAPPING['/Users/sminnee/Sites'] = 'http://localhost';
What the line says is that any Folder under /Users/sminnee/Sites/ can be accessed in a web browser from
http://localhost. For example, /Users/sminnee/Sites/mysite will be available at http://localhost/mysite.
You can add multiple file to url mapping definitions. The most specific mapping will be used. For example:
:::php
global $_FILE_TO_URL_MAPPING;
$_FILE_TO_URL_MAPPING['/Users/sminnee/Sites'] = 'http://localhost';
$_FILE_TO_URL_MAPPING['/Users/sminnee/Sites/mysite'] = 'http://mysite.localhost';
Using this example, /Users/sminnee/Sites/mysite/ would be accessed at http://mysite.localhost/, and
/Users/sminnee/Sites/othersite/ would be accessed at http://localhost/othersite/
## Usage
Sake will either run `./framework/cli-script.php` or `./cli-script.php`, depending on what's available.
It's particularly useful for running build tasks...
cd /your/site/folder
sake dev/build "flush=1"
sake dev/tests/all
It can also be handy if you have a long running script.
cd /your/site/folder
sake dev/tasks/MyReallyLongTask
### Running processes
You can use sake to make daemon processes for your application.
Step 1: Make a task or controller class that runs a loop. Because SilverStripe has memory leaks, you should make the PHP
process exit when it hits some reasonable memory limit. Sake will automatically restart your process whenever it exits.
The other thing you should do is include some appropriate sleep()s so that your process doesn't hog the system. The
best thing to do is to have a short sleep when the process is in the middle of doing things, and a long sleep when
doesn't have anything to do.
This code provides a good template:
:::php
class MyProcess extends Controller {
private static $allowed_actions = array(
'index'
);
function index() {
set_time_limit(0);
while(memory_get_usage() < 32*1024*1024) {
if($this->somethingToDo()) {
$this->doSomething();
sleep(1)
} else {
sleep(300);
}
}
}
}
Step 2: Install the "daemon" command-line tool on your server.
Step 3: Use sake to start and stop your process
sake -start MyProcess
sake -stop MyProcess
Note that sake processes are currently a little brittle, in that the pid and log
files are placed in the site root directory, rather than somewhere sensible like
/var/log or /var/run.
### Running Regular Tasks With Cron
On a unix machine, you can typically run a scheduled task with a [cron job](http://en.wikipedia.org/wiki/Cron),
using one of the following command-line calls:
```
/path/to/site_root/framework/sake dev/tasks/MyTask
php /path/to/site_root/framework/cli-script.php dev/tasks/MyTask
```
If you find that your cron job appears to be retrieving the login screen, then you may need to use `php-cli`
instead. This is typical of a cPanel-based setup.
```
php-cli /path/to/site_root/framework/cli-script.php dev/tasks/MyTask
```
A good approach to setting up and testing your task to run with cron is:
1. Try running the task via the command-line on your server. `/path/to/site_root/framework/sake dev/tasks/MyTask`
2. Set up a cron job to run the task every minute. `* * * * * /path/to/site_root/framework/sake dev/tasks/MyTask`
3. Finally, set the task to run when you want it to. `0 2 * * * /path/to/site_root/framework/sake dev/tasks/MyTask` (2am)

View File

@ -1,370 +0,0 @@
# Configuration in SilverStripe
## Introduction
SilverStripe 3 comes with a comprehensive code based configuration system.
It primarily relies on declarative [YAML](http://en.wikipedia.org/wiki/YAML) files,
and falls back to procedural PHP code, as well as PHP static variables.
Configuration can be seen as separate from other forms of variables (such as per-member or per-site settings) in the
SilverStripe system due to three properties:
- Configuration is per class, not per instance
- Configuration is normally set once during initialisation and then not changed
- Configuration is normally set by a knowledgeable technical user, such as a developer, not the end user
In SilverStripe 3, each class has it's configuration specified as set of named properties and associated values. The
values at any given time are calculated by merging several sources using rules explained below.
These sources are as follows (in highest -> lowest priority order):
- Values set via a call to `[api:Config::update()]`
- Values taken from YAML files in specially named directories
- Statics set on an "extra config source" class (such as an extension) named the same as the name of the property
(optionally)
- Statics set on the class named the same as the name of the property
- The parent of the class (optionally)
Like statics, configuration values may only contain a literal or constant; neither objects nor expressions are allowed.
## Finding Configuration
Since configuration settings are just static properties on any SilverStripe class,
there's no exhaustive list. All configurable statics are marked with a `@config` docblock
though, so you can search for them in the codebase. The same docblock will also contain
a description of the configuration setting.
## Customizing Configuration (through merging)
Each named class configuration property can contain either an array or a non-array value.
If the value is an array, each value in the array may also be one of those three types
As mentioned, the value of any specific class configuration property comes from several sources. These sources do not
override each other (except in one specific circumstance) - instead the values from each source are merged together
to give the final configuration value, using these rules:
- If the value is an array, each array is added to the _beginning_ of the composite array in ascending priority order.
If a higher priority item has a non-integer key which is the same as a lower priority item, the value of those items
is merged using these same rules, and the result of the merge is located in the same location the higher priority item
would be if there was no key clash. Other than in this key-clash situation, within the particular array, order is preserved.
- If the value is not an array, the highest priority value is used without any attempt to merge
It is an error to have mixed types of the same named property in different locations (but an error will not necessarily
be raised due to optimisations in the lookup code).
The exception to this is "false-ish" values - empty arrays, empty strings, etc. When merging a non-false-ish value with a
false-ish value, the result will be the non-false-ish value regardless of priority. When merging two false-ish values
the result will be the higher priority false-ish value.
The locations that configuration values are taken from in highest -> lowest priority order are:
- Any values set via a call to Config#update
- The configuration values taken from the YAML files in `_config/` directories (internally sorted in before / after order, where
the item that is latest is highest priority)
- Any static set on an "additional static source" class (such as an extension) named the same as the name of the property
- Any static set on the class named the same as the name of the property
- The composite configuration value of the parent class of this class
At some of these levels you can also set masks. These remove values from the composite value at their priority point rather than add.
They are much simpler. They consist of a list of key / value pairs. When applied against the current composite value
- If the composite value is a sequential array, any member of that array that matches any value in the mask is removed
- If the composite value is an associative array, any member of that array that matches both the key and value of any pair in the mask is removed
- If the composite value is not an array, if that value matches any value in the mask it is removed
## Reading and updating via the Config class
The Config class is both the primary manner of getting configuration values and one of the locations you can set
configuration values.
Note: There is no way currently to restrict read or write access to any configuration property,
or influence/check the values being read or written.
### Global access
The first thing you need to do to use the Config class is to get the singleton instance of that class. This can be
done by calling the static method `[api:Config::inst()]`, like so:
$config = Config::inst();
There are then three public methods available on the instance so obtained:
- Config#get() returns the value of a specified classes' property
- Config#remove() removes information from the value of a specified classes' property.
To remove all values, use the `Config::anything()` placeholder.
- Config#update() adds additional information into the value of a specified classes' property
Note that there is no "set" method. Because of the merge, it is not possible to completely set the value of a classes'
property (unless you're setting it to a true-ish literal). Update adds new values that are treated as the highest
priority in the merge, and remove adds a merge mask that filters out values.
### Short-hand access
Within any subclass of Object you can call the config() instance method to get an instance of a proxy object
which accesses the Config class with the class parameter already set.
For instance, instead of writing:
:::php
Config::inst()->get($this->class, 'my_property');
Config::inst()->update($this->class, 'my_other_property', 2);
You can write:
:::php
$this->config()->get('my_property');
$this->config()->update('my_other_property', 2);
Or even shorter:
:::php
$this->config()->my_property; // getter
$this->config()->my_other_property = 2; // setter
This also works statically:
MyClass::config()->my_property; // getter
MyClass::config()->my_property = 2; // setter
## Setting configuration via YAML files
Each module can (in fact, should - see below for why) have a directory immediately underneath the main module
directory called `_config/`.
Inside this directory you can add yaml files that contain values for the configuration system.
The structure of each yaml file is a series of headers and values separated by YAML document separators. If there
is only one set of values the header can be omitted.
### The header
Each value section of a YAML file has:
- A reference path, made up of the module name, the config file name, and a fragment identifier
- A set of rules for the value section's priority relative to other value sections
- A set of rules that might exclude the value section from being used
The fragment identifier component of the reference path and the two sets of rules are specified for each
value section in the header section that immediately preceeds the value section.
#### Reference paths and fragment identifiers
Each value section has a reference path. Each path looks a little like a URL,
and is of this form: `module/file#fragment`.
- "module" is the name of the module this YAML file is in.
- "file" is the name of this YAML file, stripped of the extension (so for routes.yml, it would be routes).
- "fragment" is a specified identifier. It is specified by putting a `Name: {fragment}` key / value pair into the header.
section. If you don't specify a name, a random one will be assigned.
This reference path has no affect on the value section itself, but is how other header sections refer to this value
section in their priority chain rules.
#### Priorities
Values for a specific class property can be specified in several value sections across several modules. These values are
merged together using the same rules as the configuration system as a whole.
However unlike the configuration system itself, there is no inherent priority amongst the various value sections.
Instead, each value section can have rules that indicate priority. Each rule states that this value section
must come before (lower priority than) or after (higher priority than) some other value section.
To specify these rules you add an "After" and/or "Before" key to the relevant header section. The value for these
keys is a list of reference paths to other value sections. A basic example:
:::yml
---
Name: adminroutes
After:
- '#rootroutes'
- '#coreroutes'
---
Director:
rules:
'admin': 'AdminRootController'
---
You do not have to specify all portions of a reference path. Any portion may be replaced with a wildcard "\*", or left
out all together. Either has the same affect - that portion will be ignored when checking a value section's reference
path, and will always match. You may even specify just "\*", which means "all value sections".
When a particular value section matches both a Before _and_ an After rule, this may be a problem. Clearly
one value section can not be both before _and_ after another. However when you have used wildcards, if there
was a difference in how many wildcards were used, the one with the least wildcards will be kept and the other one
ignored.
A more complex example, taken from framework/_config/routes.yml:
:::yml
---
Name: adminroutes
Before: '*'
After:
- '#rootroutes'
- '#coreroutes'
- '#modelascontrollerroutes'
---
Director:
rules:
'admin': 'AdminRootController'
---
The value section above has two rules:
- It must be merged in before (lower priority than) all other value sections
- It must be merged in after (higher priority than) any value section with a fragment name of "rootroutes"
In this case there would appear to be a problem - adminroutes can not be both before all other value sections _and_
after value sections with a name of `rootroutes`. However because `\*` has three wildcards
(it is the equivalent of `\*/\*#\*`) but `#rootroutes` only has two (it is the equivalent of `\*/\*#rootroutes`).
In this case `\*` means "every value section _except_ ones that have a fragment name of rootroutes".
One important thing to note: it is possible to create chains that are unsolvable. For instance, A must be before B,
B must be before C, C must be before A. In this case you will get an error when accessing your site.
#### Exclusionary rules
Some value sections might only make sense under certain environmental conditions - a class exists, a module is installed,
an environment variable or constant is set, or SilverStripe is running in a certain environment mode (live, dev, etc).
To accommodate this, value sections can be filtered to only be used when either a rule matches or doesn't match the
current environment.
To achieve this you add a key to the related header section, either "Only" when the value section should be included
only when all the rules contained match, or "Except" when the value section should be included except when all of the
rules contained match.
You then list any of the following rules as sub-keys, with informational values as either a single value or a list.
- 'classexists', in which case the value(s) should be classes that must exist
- 'moduleexists', in which case the value(s) should be modules that must exist
- 'environment', in which case the value(s) should be one of "live", "test" or "dev" to indicate the SilverStripe
mode the site must be in
- 'envvarset', in which case the value(s) should be environment variables that must be set
- 'constantdefined', in which case the value(s) should be constants that must be defined
For instance, to add a property to "foo" when a module exists, and "bar" otherwise, you could do this:
:::yml
---
Only:
moduleexists: 'MyFineModule'
---
MyClass:
property: 'foo'
---
Except:
moduleexists: 'MyFineModule'
---
MyClass:
property: 'bar'
---
Note than when you have more than one rule for a nested fragment, they're joined like
FRAGMENT_INCLUDED = (ONLY && ONLY) && !(EXCEPT && EXCEPT)
That is, the fragment will be included if all Only rules match, except if all Except rules match.
Also, due to YAML limitations, having multiple conditions of the same kind (say, two `EnvVarSet` in one "Only" block)
will result in only the latter coming through.
### The values
The values section of a YAML configuration file is quite simple - it is simply a nested key / value pair structure
where the top level key is the class name to set the property on, and the sub key / value pairs are the properties
and values themselves (where values, of course, can themselves be nested hashes).
A simple example setting a property called "foo" to the scalar "bar" on class "MyClass", and a property called "baz"
to a nested array on class "MyOtherClass":
:::yml
MyClass:
foo: 'bar'
MyOtherClass:
baz:
a: 1
b: 2
Notice that we can leave out the header in this case because there is only a single value section within the file.
## Setting configuration via statics
The final location that a property can get it's value from is a static set on the associated class.
Statics should be considered immutable, and therefore the majority of statics in SilverStripe
are marked `private`.
They should primarily be used to set the initial or default value for any given configuration property. It's also
a handy place to hand a docblock to indicate what a property is for. However, it's worth noting that you
do not have to define a static for a property to be valid.
## Configuration as a module marker
Configuration files also have a secondary sub-role. Modules are identified by the `[api:ManifestBuilder]` by the
presence of a `_config/` directory (or a `_config.php` file) as a top level item in the module directory.
Although your module may choose not to set any configuration, it must still have a _config directory to be recognised
as a module by the `[api:ManifestBuilder]`, which is required for features such as autoloading of classes and template
detection to work.
## Complex configuration through _config.php
In addition to the configuration system described above, each module can provide a file called `_config.php`
immediately within the module top level directory.
These `_config.php` files will be included at initialisation, and are a useful way to set legacy configuration
or set configuration based on rules that are more complex than can be encoded in YAML files.
However they should generally be avoided when possible, as they slow initialisation.
Please note that this is the only place where you can put in procedural code - all other functionality is wrapped in
classes (see [common-problems](/installation/common-problems)).
## Configuration through the CMS
SilverStripe framework does not provide a method to set configuration via a web panel.
This lack of a configuration-GUI is on purpose, as we'd like to keep developer-level options where they belong (into
code), without cluttering up the interface. See this core forum discussion ["The role of the
CMS"](http://www.silverstripe.org/archive/show/532) for further reasoning.
The GUI-based configuation is limited to the following:
* Author-level configuration like interface language or date/time formats can be performed in the CMS "My Profile" section
* Group-related configuration like `[api:HTMLEditorField]` settings can be found in the "Security" section
* Site-wide settings like page titles can be set (and extended) on the root tree element in the CMS "Content" section (through the [siteconfig](/reference/siteconfig) API).
* Any configuration interfaces added by custom code, for example through `getCMSFields()`
## Constants and the _ss_environment.php File
See [environment-management](/topics/environment-management).
## User preferences in the `Member` class
All user-related preferences are stored as a property of the `[api:Member]`-class (and as a database-column in the
*Member*-table). You can "mix in" your custom preferences by using `[api:DataObject]` for details.
## Permissions
See [security](/topics/security) and [permission](/reference/permission)
## Resource Usage (Memory and CPU)
SilverStripe tries to keep its resource usage within the documented limits (see our [server requirements](../installation/server-requirements)).
These limits are defined through `memory_limit` and `max_execution_time` in the PHP configuration.
They can be overwritten through `ini_set()`, unless PHP is running with the [Suhoshin Patches](http://www.hardened-php.net/)
or in "[safe mode](http://php.net/manual/en/features.safe-mode.php)".
Most shared hosting providers will have maximum values that can't be altered.
For certain tasks like synchronizing a large `assets/` folder with all file and folder entries in the database,
more resources are required temporarily. In general, we recommend running resource intensive tasks
through the [commandline](../topics/commandline), where configuration defaults for these settings are higher or even unlimited.
SilverStripe can request more resources through `increase_memory_limit_to()` and `increase_time_limit_to()`.
If you are concerned about resource usage on a dedicated server (without restrictions imposed through shared hosting providers), you can set a hard limit to these increases through
`set_increase_memory_limit_max()` and `set_increase_time_limit_max()`.
These values will just be used for specific scripts (e.g. `[api:Filesystem::sync()]`),
to raise the limits for all executed scripts please use `ini_set('memory_limit', <value>)`
and `ini_set('max_execution_time', <value>)` in your own `_config.php`.

View File

@ -1,298 +0,0 @@
# Controller
Base controller class. You will extend this to take granular control over the
actions and url handling of aspects of your SilverStripe site.
## Usage
The following example is for a simple `[api:Controller]` class. If you're using
the cms module and looking at Page_Controller instances you won't need to setup
your own routes since the cms module handles these routes.
`mysite/code/Controllers/FastFood.php`
:::php
<?php
class FastFood_Controller extends Controller {
private static $allowed_actions = array('order');
public function order(SS_HTTPRequest $request) {
print_r($request->allParams());
}
}
## Routing
`mysite/_config/routes.yml`
:::yaml
---
Name: myroutes
After: framework/routes#coreroutes
---
Director:
rules:
'fastfood//$Action/$ID/$Name': 'FastFood_Controller'
Request for `/fastfood/order/24/cheesefries` would result in the following to
the $arguments above. If needed, use `?flush=1` on the end of request after
making any code changes to your controller.
:::ss
Array
(
[Action] => order
[ID] => 24
[Name] => cheesefries
)
<div class="warning" markdown='1'>
SilverStripe automatically adds a URL routing entry based on the controller's class name,
so a `MyController` class is accessible through `http://localhost/MyController`.
</div>
## Linking to a controller
Each controller has a built-in `Link()` method,
which can be used to avoid hardcoding your routing in views etc.
The method should return a value that makes sense with your custom route (see above):
:::php
<?php
class FastFood_Controller extends Controller {
public function Link($action = null) {
return Controller::join_links('fastfood', $action);
}
}
The [api:Controller::join_links()] invocation 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. `join_links()` also supports
## Access Control
### Through $allowed_actions
All public methods on a controller are accessible by their name through the `$Action`
part of the URL routing, so a `MyController->mymethod()` is accessible at
`http://localhost/MyController/mymethod`. This is not always desireable,
since methods can return internal information, or change state in a way
that's not intended to be used through a URL endpoint.
SilverStripe strongly recommends securing your controllers
through defining a `$allowed_actions` array on the class,
which allows whitelisting of methods, as well as a concise
way to perform checks against permission codes or custom logic.
:::php
class MyController extends Controller {
private static $allowed_actions = array(
// someaction can be accessed by anyone, any time
'someaction',
// So can otheraction
'otheraction' => true,
// restrictedaction can only be people with ADMIN privilege
'restrictedaction' => 'ADMIN',
// complexaction can only be accessed if $this->canComplexAction() returns true
'complexaction' '->canComplexAction'
);
}
There's a couple of rules guiding these checks:
* Each class is only responsible for access control on the methods it defines
* If `$allowed_actions` is an empty array or undefined, only the `index` action is allowed
* Access checks on parent classes need to be overwritten via the Config API
* Only public methods can be made accessible
* If a method on a parent class is overwritten, access control for it has to be redefined as well
* An action named "index" is whitelisted by default,
unless allowed_actions is defined as an empty array,
or the action is specifically restricted in there.
* Methods returning forms also count as actions which need to be defined
* Form action methods (targets of `FormAction`) should NOT be included in `$allowed_actions`,
they're handled separately through the form routing (see the ["forms" topic](/topics/forms))
* `$allowed_actions` can be defined on `Extension` classes applying to the controller.
If the permission check fails, SilverStripe will return a "403 Forbidden" HTTP status.
You can overwrite the default behaviour on undefined `$allowed_actions` to allow all actions,
by setting the `RequestHandler.require_allowed_actions` config value to `false` (not recommended).
### Through the action
Each method responding to a URL can also implement custom permission checks,
e.g. to handle responses conditionally on the passed request data.
:::php
class MyController extends Controller {
private static $allowed_actions = array('myaction');
public function myaction($request) {
if(!$request->getVar('apikey')) {
return $this->httpError(403, 'No API key provided');
}
return 'valid';
}
}
Unless you transform the response later in the request processing,
it'll look pretty ugly to the user. Alternatively, you can use
`ErrorPage::response_for(<status-code>)` to return a more specialized layout.
Note: This is recommended as an addition for `$allowed_actions`, in order to handle
more complex checks, rather than a replacement.
### Through the init() method
After checking for allowed_actions, each controller invokes its `init()` method,
which is typically used to set up common state in the controller, and
include JavaScript and CSS files in the output which are used for any action.
If an `init()` method returns a `SS_HTTPResponse` with either a 3xx or 4xx HTTP
status code, it'll abort execution. This behaviour can be used to implement
permission checks.
:::php
class MyController extends Controller {
private static $allowed_actions = array();
public function init() {
parent::init();
if(!Permission::check('ADMIN')) return $this->httpError(403);
}
}
## URL Handling
In the above example the URLs were configured using the `[api:Director]` rules
in the **routes.yml** file. Alternatively you can specify these in your
Controller class via the **$url_handlers** static array (which gets processed
by the `[api:RequestHandler]`).
This is useful when you want to subvert the fixed action mapping of `fastfood/order/*`
to the function **order**. In the case below we also want any orders coming
through `/fastfood/drivethrough/` to use the same order function.
`mysite/code/Controllers/FastFood.php`
:::php
class FastFood_Controller extends Controller {
private static $allowed_actions = array(
'drivethrough'
);
private static $url_handlers = array(
'drivethrough/$Action/$ID/$Name' => 'order'
);
## URL Patterns
The `[api:RequestHandler]` class will parse all rules you specify against the
following patterns.
**A rule must always start with alphabetical ([A-Za-z]) characters or a $Variable
declaration**
| Pattern | Description |
| ----------- | --------------- |
| `$` | **Param Variable** - Starts the name of a paramater variable, it is optional to match this unless ! is used |
| `!` | **Require Variable** - Placing this after a parameter variable requires data to be present for the rule to match |
| `//` | **Shift Point** - Declares that only variables denoted with a $ are parsed into the $params AFTER this point in the regex |
## Examples
See maetl's article in the Links below of a detailed explanation.
`$Action/$ID/$OtherID` - Standard URL handler for a Controller. Take whatever `URLSegment` it is set to, find
the Action to match a function in the controller, and parse two optional `$param` variables that will be named `ID` and
`OtherID`.
`admin/help//$Action/$ID` - Match an url starting with `/admin/help/`, but don't include `/help/` as part of the
action (the shift point is set to start parsing variables and the appropriate controller action AFTER the `//`)
`tag/$Tag!` - Match an URL starting with `/tag/` after the controller's `URLSegment` and require it to have something
after it. If the URLSegment is **order** then `/order/tag/34` and `/order/tag/asdf` match but `/order/tag/` will not
You can use the `debug_request=1` switch from the [urlvariabletools](/reference/urlvariabletools) to see these in action.
## Redirection
Controllers facilitate HTTP redirection.
Note: These methods have been formerly located on the `[api:Director]` class.
* `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.
* `redirect("relative/url")`: If there is a slash in the URL, it's taken to be a normal URL. Relative URLs
will are assumed to be relative to the site-root.
* `redirect("http://www.absoluteurl.com")`: Of course, you can pass `redirect()` absolute URLs too.
* `redirectBack()`: This will return you to the previous page.
The `redirect()` method takes an optional HTTP status code,
either `301` for permanent redirects, or `302` for temporary redirects (default).
## Access control
You can also limit access to actions on a controller using the static `$allowed_actions` array. This allows you to always allow an action, or restrict it to a specific permission or to call a method that checks if the action is allowed.
For example, the default `Controller::$allowed_actions` is
private static $allowed_actions = array(
'handleAction',
'handleIndex',
);
which allows the `handleAction` and `handleIndex` methods to be called via a URL.
To allow any action on your controller to be called you can either leave your `$allowed_actions` array empty or not have one at all. This is the default behaviour, however it is not recommended as it allows anything on your controller to be called via a URL, including view-specific methods.
The recommended approach is to explicitly state the actions that can be called via a URL. Any action not in the `$allowed_actions` array, excluding the default `index` method, is then unable to be called.
To always allow an action to be called, you can either add the name of the action to the array or add a value of `true` to the array, using the name of the method as its index. For example
private static $allowed_actions = array(
'MyAwesomeAction',
'MyOtherAction' => true
);
To require that the current user has a certain permission before being allowed to call an action you add the action to the array as an index with the value being the permission code that user must have. For example
private static $allowed_actions = array(
'MyAwesomeAction',
'MyOtherAction' => true,
'MyLimitedAction' => 'CMS_ACCESS_CMSMain',
'MyAdminAction' => 'ADMIN'
);
If neither of these are enough to decide if an action should be called, you can have the check use a method. The method must be on the controller class and return true if the action is allowed or false if it isn't. To do this add the action to the array as an index with the value being the name of the method to called preceded by '->'. You are able to pass static arguments to the method in much the same way as you can with extensions. Strings are enclosed in quotes, numeric values are written as numbers and true and false are written as true and false. For example
private static $allowed_actions = array(
'MyAwesomeAction',
'MyOtherAction' => true,
'MyLimitedAction' => 'CMS_ACCESS_CMSMain',
'MyAdminAction' => 'ADMIN',
'MyRestrictedAction' => '->myCheckerMethod("MyRestrictedAction", false, 42)',
'MyLessRestrictedAction' => '->myCheckerMethod'
);
In this example, `MyAwesomeAction` and `MyOtherAction` are always allowed, `MyLimitedAction` requires access to the CMS for the current user and `MyAdminAction` requires the current user to be an admin. `MyRestrictedAction` calls the method `myCheckerMethod`, passing in the string "MyRestrictedAction", the boolean false and the number 42. `MyLessRestrictedAction` simply calls the method `myCheckerMethod` with no arguments.
## API Documentation
`[api:Controller]`
## Links
* `[api:Director]` class
* [execution-pipeline](/reference/execution-pipeline)
* [URL Handling in Controllers](http://maetl.net/silverstripe-url-handling) by maetl

View File

@ -1,94 +0,0 @@
# CSS #
## Introduction ##
SilverStripe strives to keep out of a template designer's way as much as possible -
this also extends to how you want to write your CSS.
## Adding CSS to your template ##
You are free to add `<style>` and `<link>` tags to your template (typically `themes/yourtheme/templates/Page.ss`).
SilverStripe provides a management layer for javascript and CSS files on top of that with the [Requirements](/reference/requirements) class,
by adding some simple PHP calls to your controller or template. Some files are automatically included,
depending on what functionality you are using (e.g. SilverStripe forms automatically include `framework/css/Form.css`).
In your controller (e.g. `mysite/code/Page.php`):
:::php
class Page_Controller {
public function init() {
// either specify the css file manually
Requirements::css("mymodule/css/my.css", "screen,projection");
// or mention the css filename and SilverStripe will get the file from the current theme and add it to the template
Requirements::themedCSS('print', null,'print');
}
}
Or in your template (e.g. `themes/yourtheme/templates/Page.ss`):
:::ss
<% require css("mymodule/css/my.css") %>
Management through the `Requirements` class has the advantage that modules can include their own CSS files without modifying
your template. On the other hand, you as a template developer can "block" or change certain CSS files that are included from
thirdparty code.
## WYSIWYG editor: typography.css and editor.css
This stylesheet is used to "namespace" rules which just apply in a rendered site and the WYSIWYG-editor of the CMS. This
is needed to get custom styles in the editor without affecting the remaining CMS-styles.
An example of a good location to use `class="typography"` is the container element to your WYSIWYG-editor field. The
`$Content` WYSIWYG editor field already comes with SilverStripe out-of-the-box:
:::html
<!--
This is a good way, you're only applying class="typography"
to where the WYSIWYG editor is, and not the entire layout.
-->
`<div id="Main">`
`<div id="LeftContent">`
`<p>`We have a lot of content to go here.`</p>`
`</div>`
`<div id="Content" class="typography">`
$Content
`</div>`
`</div>`
The `typography.css` file should contain only styling for these elements (related to the WYSIWYG editor):
* **Headers (h1 - h6)**
* **Text (p, blockquote, pre)**
* **Lists (ul, li)**
* **CSS alignment (img.left, .left, .right etc)**
* **Tables**
* **Miscellaneous (hr etc)**
The advantages are that it's fully styled, as a default starting point which you can easily tweak. It also doesn't
affect the CMS styling at all (except for the WYSIWYG), which is what we want.
It's also separated from the rest of the layout. If you wanted to change typography only, for where you usually edit the
content you don't need to go wading through other CSS files related to the actual layout.
To get these styles working in the CMS. Eg you use a font you want in the CMS area you need to create an editor.css
file. Then with this file you can define styles for the CMS or just import the styles from typography. Unlike
`typography.css`, `editor.css` is NOT included in the front end site. So this is `themes/your_theme/css/editor.css`
:::css
/* Import the common styles from typography like link colors. */
@import 'typography.css';
/* We want the backend editor to have a bigger font as well though */
.typography * { font-size: 200% }
See [typography](/reference/typography) for more information.
## Related ##
* [javascript](javascript)
* ["Compass" module](http://silverstripe.org/compass-module/): Allows writing CSS in SASS/LESS syntax, with better code management through mixins, includes and variables
* [Reference: CMS Architecture](../reference/cms-architecture)
* [Howto: Extend the CMS Interface](../howto/extend-cms-interface)

View File

@ -1,99 +0,0 @@
# Data Types and Casting
Properties on any SilverStripe object can be type casted automatically,
by transforming its scalar value into an instance of the `[api:DBField]` class,
providing additional helpers. For example, a string can be cast as
a `[api:Text]` type, which has a `FirstSentence()` method to retrieve the first
sentence in a longer piece of text.
## Available Types
* `[api:Boolean]`: A boolean field.
* `[api:Currency]`: A number with 2 decimal points of precision, designed to store currency values.
* `[api:Date]`: A date field
* `[api:Decimal]`: A decimal number.
* `[api:Enum]`: An enumeration of a set of strings
* `[api:HTMLText]`: A variable-length string of up to 2MB, designed to store HTML
* `[api:HTMLVarchar]`: A variable-length string of up to 255 characters, designed to store HTML
* `[api:Int]`: An integer field.
* `[api:Percentage]`: A decimal number between 0 and 1 that represents a percentage.
* `[api:SS_Datetime]`: A date / time field
* `[api:Text]`: A variable-length string of up to 2MB, designed to store raw text
* `[api:Time]`: A time field
* `[api:Varchar]`: A variable-length string of up to 255 characters, designed to store raw text
## Casting arbitrary values
On the most basic level, the class can be used as simple conversion class
from one value to another, e.g. to round a number.
:::php
DBField::create_field('Double', 1.23456)->Round(2); // results in 1.23
Of course that's much more verbose than the equivalent PHP call.
The power of `[api:DBField]` comes with its more sophisticated helpers,
like showing the time difference to the current date:
:::php
DBField::create_field('Date', '1982-01-01')->TimeDiff(); // shows "30 years ago"
## Casting ViewableData
Most objects in SilverStripe extend from `[api:ViewableData]`,
which means they know how to present themselves in a view context.
Through a `$casting` array, arbitrary properties and getters can be casted:
:::php
class MyObject extends ViewableData {
static $casting = array(
'MyDate' => 'Date'
);
public function getMyDate() {
return '1982-01-01';
}
}
$obj = new MyObject;
$obj->getMyDate(); // returns string
$obj->MyDate; // returns string
$obj->obj('MyDate'); // returns object
$obj->obj('MyDate')->InPast(); // returns boolean
## Casting DataObject
The `[api:DataObject]` class uses `DBField` to describe the types of its
properties which are persisted in database columns, through the `[$db](api:DataObject::$db)` property.
In addition to type information, the `DBField` class also knows how to
define itself as a database column. See the ["datamodel" topic](/topics/datamodel#casting) for more details.
<div class="warning" markdown="1">
Since we're dealing with a loosely typed language (PHP)
as well as varying type support by the different database drivers,
type conversions between the two systems are not guaranteed to be lossless.
Please take particular care when casting booleans, null values, and on float precisions.
</div>
## Casting in templates
In templates, casting helpers are available without the need for an `obj()` call.
Example: Flagging an object of type `MyObject` (see above) if it's date is in the past.
:::ss
<% if $MyObjectInstance.MyDate.InPast %>Outdated!<% end_if %>
## Casting HTML Text
The database field types `[api:HTMLVarchar]`/`[api:HTMLText]` and `[api:Varchar]`/`[api:Text]`
are exactly the same in the database. However, the templating engine knows to escape
fields without the `HTML` prefix automatically in templates,
to prevent them from rendering HTML interpreted by browsers.
This escaping prevents attacks like CSRF or XSS (see "[security](/topics/security)"),
which is important if these fields store user-provided data.
You can disable this auto-escaping by using the `$MyField.RAW` escaping hints,
or explicitly request escaping of HTML content via `$MyHtmlField.XML`.
## Related
* ["datamodel" topic](/topics/datamodel)
* ["security" topic](/topics/security)

View File

@ -1,938 +0,0 @@
# Datamodel
SilverStripe uses an [object-relational model](http://en.wikipedia.org/wiki/Object-relational_model)
that assumes the following connections:
* Each database-table maps to a PHP class
* Each database-row maps to a PHP object
* Each database-column maps to a property on a PHP object
All data tables in SilverStripe are defined as subclasses of `[api:DataObject]`.
Inheritance is supported in the data model: separate tables will be linked
together, the data spread across these tables. The mapping and saving/loading
logic is handled by SilverStripe, you don't need to worry about writing SQL most
of the time.
Most of the ORM customizations are possible through [PHP5 Object
Overloading](http://www.onlamp.com/pub/a/php/2005/06/16/overloading.html)
handled in the `[api:Object]`-class.
See [database-structure](/reference/database-structure) for in-depth information
on the database-schema and the ["sql queries" topic](/reference/sqlquery) in
case you need to drop down to the bare metal.
## Generating the Database Schema
The SilverStripe database-schema is generated automatically by visiting the URL.
`http://localhost/dev/build`
<div class="notice" markdown='1'>
Note: You need to be logged in as an administrator to perform this command,
unless your site is in "[dev mode](/topics/debugging)", or the command is run
through CLI.
</div>
## Querying Data
Every query to data starts with a `DataList::create(<class>)` or `<class>::get()`
call. For example, this query would return all of the `Member` objects:
:::php
$members = Member::get();
The ORM uses a "fluent" syntax, where you specify a query by chaining together
different methods. Two common methods are `filter()` and `sort()`:
:::php
$members = Member::get()->filter(array(
'FirstName' => 'Sam'
))->sort('Surname');
Those of you who know a bit about SQL might be thinking "it looks like you're
querying all members, and then filtering to those with a first name of 'Sam'.
Isn't this very slow?" Is isn't, because the ORM doesn't actually execute the
SQL query until you iterate on the result with a `foreach()` or `<% loop %>`.
The ORM is smart enough to generate a single efficient query at the last moment
in time without needing to post process the result set in PHP. In MySQL the
query generated by the ORM may look something like this for the previous query.
:::
SELECT * FROM Member WHERE FirstName = 'Sam' ORDER BY Surname
An example of the query process in action:
:::php
// The SQL query isn't executed here...
$members = Member::get();
// ...or here
$members = $members->filter(array('FirstName' => 'Sam'));
// ...or even here
$members = $members->sort('Surname');
// *This* is where the query is executed
foreach($members as $member) {
echo "<p>$member->FirstName $member->Surname</p>";
}
This also means that getting the count of a list of objects will be done with a
single, efficient query.
:::php
$members = Member::get()->filter(array(
'FirstName' => 'Sam'
))->sort('Surname');
// This will create an single SELECT COUNT query similar to -
// SELECT COUNT(*) FROM Members WHERE FirstName = 'Sam'
echo $members->Count();
### Returning a single DataObject
There are a couple of ways of getting a single DataObject from the ORM. If you
know the ID number of the object, you can use `byID($id)`:
:::php
$member = Member::get()->byID(5);
If you have constructed a query that you know should return a single record, you
can call `First()`:
:::php
$member = Member::get()->filter(array(
'FirstName' => 'Sam', 'Surname' => 'Minnee'
))->First();
### Sort
Quite often you would like to sort a list. Doing this on a list could be done in
a few ways.
If would like to sort the list by `FirstName` in a ascending way (from A to Z).
:::php
$members = Member::get()->sort('FirstName', 'ASC'); // ASC or DESC
$members = Member::get()->sort('FirstName'); // Ascending is implied
To reverse the sort
:::php
$members = Member::get()->sort('FirstName', 'DESC');
// or..
$members = Member::get()->sort('FirstName', 'ASC')->reverse();
However you might have several entries with the same `FirstName` and would like
to sort them by `FirstName` and `LastName`
:::php
$member = Member::get()->sort(array(
'FirstName' => 'ASC',
'LastName'=>'ASC'
));
You can also sort randomly
:::php
$member = Member::get()->sort('RAND()')
### Filter
As you might expect, the `filter()` method filters the list of objects that gets
returned. The previous example included this filter, which returns all Members
with a first name of "Sam".
:::php
$members = Member::get()->filter(array('FirstName' => 'Sam'));
In SilverStripe 2, we would have passed `"\"FirstName\" = 'Sam'` to make this
query. Now, we pass an array, `array('FirstName' => 'Sam')`, to minimize the
risk of SQL injection bugs. The format of this array follows a few rules:
* Each element of the array specifies a filter. You can specify as many
filters as you like, and they **all** must be true.
* The key in the filter corresponds to the field that you want to filter by.
* The value in the filter corresponds to the value that you want to filter to.
So, this would return only those members called "Sam Minnée".
:::php
$members = Member::get()->filter(array(
'FirstName' => 'Sam',
'Surname' => 'Minnée',
));
There is also a short hand way of getting Members with the FirstName of Sam.
:::php
$members = Member::get()->filter('FirstName', 'Sam');
Or if you want to find both Sam and Sig.
:::php
$members = Member::get()->filter(
'FirstName', array('Sam', 'Sig')
);
Then there is the most complex task when you want to find Sam and Sig that has
either Age 17 or 74.
:::php
$members = Member::get()->filter(array(
'FirstName' => array('Sam', 'Sig'),
'Age' => array(17, 74)
));
// SQL: WHERE ("FirstName" IN ('Sam', 'Sig) AND "Age" IN ('17', '74))
In case you want to match multiple criteria non-exclusively (with an "OR"
disjunctive),use the `filterAny()` method instead:
:::php
$members = Member::get()->filterAny(array(
'FirstName' => 'Sam',
'Age' => 17,
));
// SQL: WHERE ("FirstName" = 'Sam' OR "Age" = '17')
You can also combine both conjunctive ("AND") and disjunctive ("OR") statements.
:::php
$members = Member::get()
->filter(array(
'LastName' => 'Minnée'
))
->filterAny(array(
'FirstName' => 'Sam',
'Age' => 17,
));
// WHERE ("LastName" = 'Minnée' AND ("FirstName" = 'Sam' OR "Age" = '17'))
### Filter with PHP / filterByCallback
It is also possible to filter by a PHP callback, however this will force the
data model to fetch all records and loop them in PHP, thus `filter()` or `filterAny()`
are to be preferred over `filterByCallback()`.
Please note that because `filterByCallback()` has to run in PHP, it will always return
an `ArrayList` (even if called on a `DataList`, this however might change in future).
The first parameter to the callback is the item, the second parameter is the list itself.
The callback will run once for each record, if the callback returns true, this record
will be added to the list of returned items.
The below example will get all Members that have an expired or not encrypted password.
:::php
$membersWithBadPassword = Member::get()->filterByCallback(function($item, $list) {
if ($item->isPasswordExpired() || $item->PasswordEncryption = 'none') {
return true;
}
});
### Exclude
The `exclude()` method is the opposite to the filter in that it removes entries
from a list.
If we would like to remove all members from the list with the FirstName of Sam.
:::php
$members = Member::get()->exclude('FirstName', 'Sam');
Remove both Sam and Sig is as easy as.
:::php
$members = Member::get()->exclude('FirstName', array('Sam','Sig'));
As you can see it follows the same pattern as filter, so for removing only Sam
Minnée from the list:
:::php
$members = Member::get()->exclude(array(
'FirstName' => 'Sam',
'Surname' => 'Minnée',
));
And removing Sig and Sam with that are either age 17 or 74.
:::php
$members = Member::get()->exclude(array(
'FirstName' => array('Sam', 'Sig'),
'Age' => array(17, 43)
));
This would be equivalent to a SQL query of
:::
... WHERE ("FirstName" NOT IN ('Sam','Sig) OR "Age" NOT IN ('17', '74));
### Search Filter Modifiers
The where clauses showcased in the previous two sections (filter and exclude)
specify exact matches by default. However, there are a number of suffixes that
you can put on field names to change this behavior such as `":StartsWith"`,
`":EndsWith"`, `":PartialMatch"`, `":GreaterThan"`, `":GreaterThanOrEqual"`, `":LessThan"`, `":LessThanOrEqual"`.
Each of these suffixes is represented in the ORM as a subclass of
`[api:SearchFilter]`. Developers can define their own SearchFilters if needing
to extend the ORM filter and exclude behaviors.
These suffixes can also take modifiers themselves. The modifiers currently
supported are `":not"`, `":nocase"` and `":case"`. These negate the filter,
make it case-insensitive and make it case-sensitive respectively. The default
comparison uses the database's default. For MySQL and MSSQL, this is
case-insensitive. For PostgreSQL, this is case-sensitive.
The following is a query which will return everyone whose first name doesn't
start with S, who has logged in since 1/1/2011.
:::php
$members = Member::get()->filter(array(
'FirstName:StartsWith:not' => 'S',
'LastVisited:GreaterThan' => '2011-01-01'
));
### Subtract
You can subtract entries from a DataList by passing in another DataList to
`subtract()`
:::php
$allSams = Member::get()->filter('FirstName', 'Sam');
$allMembers = Member::get();
$noSams = $allMembers->subtract($allSams);
Though for the above example it would probably be easier to use `filter()` and
`exclude()`. A better use case could be when you want to find all the members
that does not exist in a Group.
:::php
// ... Finding all members that does not belong to $group.
$otherMembers = Member::get()->subtract($group->Members());
### Limit
You can limit the amount of records returned in a DataList by using the
`limit()` method.
:::php
// Returning the first 5 members, sorted alphabetically by Surname
$members = Member::get()->sort('Surname')->limit(5);
`limit()` accepts two arguments, the first being the amount of results you want
returned, with an optional second parameter to specify the offset, which allows
you to tell the system where to start getting the results from. The offset, if
not provided as an argument, will default to 0.
:::php
// Return 10 members with an offset of 4 (starting from the 5th result).
// Note that the argument order is different from a MySQL LIMIT clause
$members = Member::get()->sort('Surname')->limit(10, 4);
### Raw SQL options for advanced users
Occasionally, the system described above won't let you do exactly what you need
to do. In these situations, we have methods that manipulate the SQL query at a
lower level. When using these, please ensure that all table & field names are
escaped with double quotes, otherwise some DB back-ends (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 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 you need it to, you may also
consider extending the ORM with new data types or filter modifiers (that
documentation still needs to be written)
#### Where clauses
You can specify a WHERE clause fragment (that will be combined with other
filters using AND) with the `where()` method:
:::php
$members = Member::get()->where("\"FirstName\" = 'Sam'")
#### Joining
You can specify a join with the innerJoin and leftJoin methods. Both of these
methods have the same arguments:
* The name of the table to join to
* The filter clause for the join
* An optional alias
For example:
:::php
// Without an alias
$members = Member::get()
->leftJoin("Group_Members", "\"Group_Members\".\"MemberID\" = \"Member\".\"ID\"");
$members = Member::get()
->innerJoin("Group_Members", "\"Rel\".\"MemberID\" = \"Member\".\"ID\"", "Rel");
Passing a *$join* statement to DataObject::get will filter results further by
the JOINs performed against the foreign table. **It will NOT return the
additionally joined data.** The returned *$records* will always be a
`[api:DataObject]`.
## Properties
### Definition
Data is defined in the static variable $db on each class, in the format:
`<property-name>` => "data-type"
:::php
class Player extends DataObject {
private static $db = array(
"FirstName" => "Varchar",
"Surname" => "Varchar",
"Description" => "Text",
"Status" => "Enum(array('Active', 'Injured', 'Retired'))",
"Birthday" => "Date"
);
}
See [data-types](data-types) for all available types.
### Overloading
"Getters" and "Setters" are functions that help us save fields to our data
objects. By default, the methods getField() and setField() are used to set data
object fields. They save to the protected array, $obj->record. We can overload
the default behavior by making a function called "get`<fieldname>`" or
"set`<fieldname>`".
:::php
class Player extends DataObject {
private static $db = array(
"Status" => "Enum(array('Active', 'Injured', 'Retired'))"
);
// access through $myPlayer->Status
public function getStatus() {
// check if the Player is actually... born already!
return (!$this->obj("Birthday")->InPast()) ? "Unborn" : $this->Status;
}
### Customizing
We can create new "virtual properties" which are not actually listed in
`private static $db` or stored in the database-row.
Here we combined a Player's first name and surname, accessible through
$myPlayer->Title.
:::php
class Player extends DataObject {
public function getTitle() {
return "{$this->FirstName} {$this->Surname}";
}
// access through $myPlayer->Title = "John Doe";
// just saves data on the object, please use $myPlayer->write() to save
// the database-row
public function setTitle($title) {
list($firstName, $surName) = explode(' ', $title);
$this->FirstName = $firstName;
$this->Surname = $surName;
}
}
<div class="warning" markdown='1'>
**CAUTION:** It is common practice to make sure that pairs of custom
getters/setter deal with the same data, in a consistent format.
</div>
<div class="warning" markdown='1'>
**CAUTION:** Custom setters can be hard to debug: Please double check if you
could transform your data in more straight-forward logic embedded to your custom
controller or form-saving.
</div>
### Default Values
Define the default values for all the $db fields. This example sets the
"Status"-column on Player to "Active" whenever a new object is created.
:::php
class Player extends DataObject {
private static $defaults = array(
"Status" => 'Active',
);
}
<div class="notice" markdown='1'>
Note: Alternatively you can set defaults directly in the database-schema (rather
than the object-model). See [data-types](data-types) for details.
</div>
### Casting
Properties defined in *static $db* are automatically casted to their
[data-types](data-types) when used in templates.
You can also cast the return-values of your custom functions (e.g. your "virtual
properties"). Calling those functions directly will still return whatever type
your PHP code generates, but using the *obj()*-method or accessing through a
template will cast the value according to the $casting-definition.
:::php
class Player extends DataObject {
private static $casting = array(
"MembershipFee" => 'Currency',
);
// $myPlayer->MembershipFee() returns a float (e.g. 123.45)
// $myPlayer->obj('MembershipFee') returns a object of type Currency
// In a template: <% loop $MyPlayer %>MembershipFee.Nice<% end_loop %>
// returns a casted string (e.g. "$123.45")
public function getMembershipFee() {
return $this->Team()->BaseFee * $this->MembershipYears;
}
}
## Relations
Relations are built through static array definitions on a class, in the format
`<relationship-name> => <classname>`.
### has_one
A 1-to-1 relation creates a database-column called "`<relationship-name>`ID", in
the example below this would be "TeamID" on the "Player"-table.
:::php
// access with $myPlayer->Team()
class Player extends DataObject {
private static $has_one = array(
"Team" => "Team",
);
}
SilverStripe's `[api:SiteTree]` base-class for content-pages uses a 1-to-1
relationship to link to its parent element in the tree:
:::php
// access with $mySiteTree->Parent()
class SiteTree extends DataObject {
private static $has_one = array(
"Parent" => "SiteTree",
);
}
### has_many
Defines 1-to-many joins. A database-column named ""`<relationship-name>`ID""
will to be created in the child-class.
<div class="warning" markdown='1'>
**CAUTION:** Please specify a $has_one-relationship on the related child-class
as well, in order to have the necessary accessors available on both ends.
</div>
:::php
// access with $myTeam->Players() or $player->Team()
class Team extends DataObject {
private static $has_many = array(
"Players" => "Player",
);
}
class Player extends DataObject {
private static $has_one = array(
"Team" => "Team",
);
}
To specify multiple $has_manys to the same object you can use dot notation to
distinguish them like below
:::php
class Person extends DataObject {
private static $has_many = array(
"Managing" => "Company.Manager",
"Cleaning" => "Company.Cleaner",
);
}
class Company extends DataObject {
private static $has_one = array(
"Manager" => "Person",
"Cleaner" => "Person"
);
}
Multiple $has_one relationships are okay if they aren't linking to the same
object type.
:::php
/**
* THIS IS BAD
*/
class Team extends DataObject {
private static $has_many = array(
"Players" => "Player",
);
}
class Player extends DataObject {
private static $has_one = array(
"Team" => "Team",
"AnotherTeam" => "Team",
);
}
### belongs_to
Defines a 1-to-1 relationship with another object, which declares the other end
of the relationship with a corresponding $has_one. A single database column named
`<relationship-name>ID` will be created in the object with the $has_one, but
the $belongs_to by itself will not create a database field. This field will hold
the ID of the object declaring the $belongs_to.
Similarly with $has_many, dot notation can be used to explicitly specify the $has_one
which refers to this relation. This is not mandatory unless the relationship would
be otherwise ambiguous.
:::php
class Torso extends DataObject {
// HeadID will be generated on the Torso table
private static $has_one = array(
'Head' => 'Head'
);
}
class Head extends DataObject {
// No database field created. The '.Head' suffix could be omitted
private static $belongs_to = array(
'Torso' => 'Torso.Head'
);
}
### many_many
Defines many-to-many joins. A new table, (this-class)_(relationship-name), will
be created with a pair of ID fields.
<div class="warning" markdown='1'>
**CAUTION:** Please specify a $belongs_many_many-relationship on the related
class as well, in order to have the necessary accessors available on both ends.
</div>
:::php
// access with $myTeam->Categories() or $myCategory->Teams()
class Team extends DataObject {
private static $many_many = array(
"Categories" => "Category",
);
}
class Category extends DataObject {
private static $belongs_many_many = array(
"Teams" => "Team",
);
}
### Adding relations
Adding new items to a relations works the same, regardless if you're editing a
*has_many*- or a *many_many*. They are encapsulated by `[api:HasManyList]` and
`[api:ManyManyList]`, both of which provide very similar APIs, e.g. an `add()`
and `remove()` method.
:::php
class Team extends DataObject {
// see "many_many"-description for a sample definition of class "Category"
private static $many_many = array(
"Categories" => "Category",
);
public function addCategories(SS_List $cats) {
foreach($cats as $cat) $this->Categories()->add($cat);
}
}
### Custom Relations
You can use the flexible datamodel 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.
:::php
class Team extends DataObject {
private static $has_many = array(
"Players" => "Player"
);
// can be accessed by $myTeam->ActivePlayers()
public function ActivePlayers() {
return $this->Players()->filter('Status', 'Active');
}
}
Note: Adding new records to a filtered `RelationList` like in the example above
doesn't automatically set the filtered criteria on the added record.
### Relations on Unsaved Objects
You can also set *has_many* and *many_many* relations before the `DataObject` is
saved. This behaviour uses the `[api:UnsavedRelationList]` and converts it into
the correct `RelationList` when saving the `DataObject` for the first time.
This unsaved lists will also recursively save any unsaved objects that they
contain.
As these lists are not backed by the database, most of the filtering methods on
`DataList` cannot be used on a list of this type. As such, an
`UnsavedRelationList` should only be used for setting a relation before saving
an object, not for displaying the objects contained in the relation.
## Validation and Constraints
Traditionally, validation in SilverStripe has been mostly handled on the
controller through [form validation](/topics/forms#form-validation).
While this is a useful approach, it can lead to data inconsistencies if the
record is modified outside of the 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.
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 hook through `[api:DataExtension]`.
Invalid objects won't be able to be written - a [api:ValidationException]` will
be thrown and no write will occur.
It is expected that you call validate() in your own application to test that an
object is valid before attempting a write, and respond appropriately if it isn't.
The return value of `validate()` is a `[api:ValidationResult]` object.
You can append your own errors in there.
Example: Validate postcodes based on the selected country
:::php
class MyObject extends DataObject {
private static $db = array(
'Country' => 'Varchar',
'Postcode' => 'Varchar'
);
public function validate() {
$result = parent::validate();
if($this->Country == 'DE' && $this->Postcode && strlen($this->Postcode) != 5) {
$result->error('Need five digits for German postcodes');
}
return $result;
}
}
## Maps
A map is an array where the array indexes contain data as well as the values.
You can build a map from any DataList like this:
:::php
$members = Member::get()->map('ID', 'FirstName');
This will return a map where the keys are Member IDs, and the values are the
corresponding FirstName values. Like everything else in the ORM, these maps are
lazy loaded, so the following code will only query a single record from the
database:
:::php
$members = Member::get()->map('ID', 'FirstName');
echo $member[5];
This functionality is provided by the `SS_Map` class, which can be used to build
a map around any `SS_List`.
:::php
$members = Member::get();
$map = new SS_Map($members, 'ID', 'FirstName');
Note: You can also retrieve a single property from all contained records
through [SS_List->column()](api:SS_List#_column).
## Data Handling
When saving data through the object model, you don't have to manually escape
strings to create SQL-safe commands. You have to make sure though that certain
properties are not overwritten, e.g. *ID* or *ClassName*.
### Creation
:::php
$myPlayer = new Player();
$myPlayer->Firstname = "John"; // sets property on object
$myPlayer->write(); // writes row to database
### Update
:::php
$myPlayer = Player::get()->byID(99);
if($myPlayer) {
$myPlayer->Firstname = "John"; // sets property on object
$myPlayer->write(); // writes row to database
}
### Batch Update
:::php
$myPlayer->update(
ArrayLib::filter_keys(
$_REQUEST,
array('Birthday', 'Firstname')
)
);
Alternatively you can use *castedUpdate()* to respect the
[data-types](/topics/data-types). This is preferred to manually casting data
before saving.
:::php
$myPlayer->castedUpdate(
ArrayLib::filter_keys(
$_REQUEST,
array('Birthday', 'Firstname')
)
);
### onBeforeWrite
You can customize saving-behaviour for each DataObject, e.g. for adding workflow
or data customization. The function is triggered when calling *write()* to save
the object to the database. This includes saving a page in the CMS or altering a
ModelAdmin record.
Example: Disallow creation of new players if the currently logged-in player is
not a team-manager.
:::php
class Player extends DataObject {
private static $has_many = array(
"Teams"=>"Team"
);
public function onBeforeWrite() {
// check on first write action, aka "database row creation"
// (ID-property is not set)
if(!$this->ID) {
$currentPlayer = Member::currentUser();
if(!$currentPlayer->IsTeamManager()) {
user_error('Player-creation not allowed', E_USER_ERROR);
exit();
}
}
// check on every write action
if(!$this->record['TeamID']) {
user_error('Cannot save player without a valid team', E_USER_ERROR);
exit();
}
// CAUTION: You are required to call the parent-function, otherwise
// SilverStripe will not execute the request.
parent::onBeforeWrite();
}
}
<div class="notice" markdown='1'>
Note: There are no separate methods for *onBeforeCreate* and *onBeforeUpdate*.
Please check for the existence of $this->ID to toggle these two modes, as shown
in the example above.
</div>
### onBeforeDelete
Triggered before executing *delete()* on an existing object.
Example: Checking for a specific [permission](/reference/permission) to delete
this type of object. It checks if a member is logged in who belongs to a group
containing the permission "PLAYER_DELETE".
:::php
class Player extends DataObject {
private static $has_many = array(
"Teams"=>"Team"
);
public function onBeforeDelete() {
if(!Permission::check('PLAYER_DELETE')) {
Security::permissionFailure($this);
exit();
}
parent::onBeforeDelete();
}
}
### Saving data with forms
See [forms](/topics/forms).
### Saving data with custom SQL
See the ["sql queries" topic](/reference/sqlquery) for custom *INSERT*,
*UPDATE*, *DELETE* queries.
## Extending DataObjects
You can add properties and methods to existing `[api:DataObjects]`s like
`[api:Member]` (a core class) without hacking core code or subclassing. See
`[api:DataExtension]` for a general description, and `[api:Hierarchy]` for the
most popular examples.
## FAQ
### What's the difference between DataObject::get() and a relation-getter?
You can work with both in pretty much the same way, but relationship-getters
return a special type of collection:
A `[api:HasManyList]` or a `[api:ManyManyList]` with relation-specific
functionality.
:::php
$myTeams = $myPlayer->Team(); // returns HasManyList
$myTeam->add($myOtherPlayer);

View File

@ -1,151 +0,0 @@
# Debugging
## Environment Types
Silverstripe knows three different environment-types (or "debug-levels"). Each of the levels gives you different tools
and functionality. "dev", "test" and "live". You can either configure the environment of the site in your
[config.yml file](/topics/configuration) or in your [environment configuration file](/topics/environment-management).
The definition of setting an environment in your `config.yml` looks like
:::yml
Director:
environment_type: 'dev'
### Dev Mode
When developing your websites, adding page types or installing modules you should run your site in devmode. In this mode
you will be able to view full error backtraces and view the development tools without logging in as admin.
To set your site to dev mode set this in your `config.yml` file
:::yml
Director:
environment_type: 'dev'
Please note **devmode should not be enabled long term on live sites for security reasons**. In devmode by outputting
backtraces of function calls a hacker can gain information about your environment (including passwords) so you should
use devmode on a public server very very carefully
### Test Mode
Test mode is designed for staging environments or other private collaboration sites before deploying a site live. You do
not need to use test mode if you do not have a staging environment or a place for testing which is on a public server)
In this mode error messages are hidden from the user and it includes `[api:BasicAuth]` integration if you want to password
protect the site.
To set your site to test mode set this in your `config.yml` file
:::yml
Director:
environment_type: 'test'
A common situation is to enable password protected site viewing on your test site only.
You can enable that but adding this to your `config.yml` file:
:::yml
---
Only:
environment: 'test'
---
BasicAuth:
entire_site_protected: true
### Live Mode
Live sites should always run in live mode. Error messages are suppressed from the user but can be optionally configured
to email the developers. This enables near real time reporting of any fatal errors or warnings on the site and can help
find any bugs users run into.
To set your site to live mode set this in your `config.yml` file
:::yml
Director:
environment_type: 'live'
### Checking Environment Types
You can check for the current environment type in [config files](/topics/configuration) through the "environment" variant.
This is useful for example when you have various API keys on your site and separate ones for dev / live or for configuring
environment settings based on type .
---
Only:
environment: 'test'
---
MyClass:
myvar: myval
In addition, you can use the following methods in PHP code:
:::php
Director::isDev();
Director::isTest();
Director::isLive();
## Email Errors
:::yml
Debug:
send_errors_to: 'your@email.com'
## Customizing Error-Output
You can customize "friendly error messages" in test/live-mode by creating *assets/error-500.html*.
## URL Variable Tools
You can get lots of information on the current rendering context without writing any code or launching a debugger: Just
attach some [Debug Parameters](/reference/urlvariabletools) to your current URL to see the compiled template, or all performed
SQL-queries.
## Debugging methods
The Debug class contains a number of static methods
* *Debug::show($myVariable)*: performs a kind of *print_r($myVariable)*, but shows it in a more useful format.
* *Debug::message("Wow, that's great")*: prints a short debugging message.
* *SS_Backtrace::backtrace()*: prints a calls-stack
### Error handling
On development sites, we deal harshly with any warnings or errors: a full call-stack is shown and execution stops. This
is basically so that we deal with them promptly, since most warnings are indication that **something** is broken.
On live sites, all errors are emailed to the address specified in the `Debug.send_errors_to` config setting.
### Debugging techniques
Since we don't have a decent interactive debugger going, we use the following debugging techniques:
* Putting *Debug::show()* and *Debug::message()* at key places in the code can help you know what's going on.
Sometimes, it helps to put this debugging information into the core modules, although, if possible, try and get what you
need by using [url querystring variables](/reference/urlvariabletools).
* Calling *user_error("breakpoint", E_USER_ERROR)* will kill execution at that point and give you a call stack to see
where you came from. Alternatively, *SS_Backtrace::backtrace()* gives you similar information without killing
execution.
* There are some special [url querystring variables](/reference/urlvariabletools) that can be helpful in seeing what's going on
with core modules, such as the templates.
* You can also use *$Debug* with *ViewableData* in templates.
#### Unit Testing
A good way to avoid writing the same test stubs and var_dump() commands over and over again is to codify them as [unit
tests](testing-guide). This way you integrate the debugging process right into your quality control, and eventually in
the development effort itself as "test-driven development".
#### Profiling
Profiling is the best way to identify bottle necks and other slow moving parts of your application prime for optimization. SilverStripe
does not include any profiling tools out of the box, but we recommend the use of existing tools such as [XHProf](https://github.com/facebook/xhprof/)
and [XDebug](http://xdebug.org/).
* [Profiling with XHProf](http://techportal.inviqa.com/2009/12/01/profiling-with-xhprof/)
* [Profiling PHP Applications With xdebug](http://devzone.zend.com/1139/profiling-php-applications-with-xdebug/)

View File

@ -1,121 +0,0 @@
# Email
SilverStripe has emailing functionality using the built-in mail() function in PHP.
Features include sending plaintext- and HTML emails, sending bulk emails, subscription, handling bounced back emails.
## Configuration
Your PHP configuration needs to include the SMTP module for sending emails.
If you are not running an SMTP server together with your webserver, you might need to setup PHP with the credentials for
an external SMTP server (see [PHP documentation for mail()](http://php.net/mail)).
## Usage
### Sending combined HTML and Plaintext
By default, emails are sent in both HTML and Plaintext format.
A plaintext representation is automatically generated from the system
by stripping HTML markup, or transforming it where possible
(e.g. `<strong>text</strong>` is converted to `*text*`).
:::php
$email = new Email($from, $to, $subject, $body);
$email->send();
The default HTML template is located in `framework/templates/email/GenericEmail.ss`.
### Sending Plaintext only
:::php
$email = new Email($from, $to, $subject, $body);
$email->sendPlain();
### Templates
* Create a SS-template file called, in this example we will use 'MyEmail.ss' inside `mysite/templates/email`.
* Fill this out with the body text for your email. You can use any [SS-template syntax](/topics/templates) (e.g. `<% loop %>`,
`<% if %>`, $FirstName etc)
* Choose your template with **setTemplate()**
* Populate any custom data into the template before sending with **populateTemplate()**
Example:
:::php
$email = new Email($from, $to, $subject, $body);
$email->setTemplate('MyEmail');
// You can call this multiple times or bundle everything into an array, including DataSetObjects
$email->populateTemplate(Member::currentUser());
$welcomeMsg = 'Thank you for joining on '.date('Y-m-d'.'!');
$email->populateTemplate(array(
'WelcomeMessage' => $welcomeMsg, // Accessible in template via $WelcomeMessage
));
$email->send();
### Subclassing
Class definition:
:::php
<?php
class MyEmail extends Email{
protected
$to = '$Email', // Be sure to encase this in single-quotes, as it is evaluated later by the template parser
$from = 'email@email.com',
$ss_template = 'MyEmail';
}
Usage:
:::php
<?php
$email = new MyEmail();
$email->populateTemplate(Member::currentUser()); // This will populate the template, $to, $from etc variables if they exist
$email->send(); // Will immediately send an HTML email with appropriate plain-text content
### Administrator Emails
You can influence the default sender address of emails through the `Email.admin_email`
[configuration setting](/topics/configuration). This address is used if the `from` field is empty.
### Redirecting Emails
Further [configuration settings](/topics/configuration) relating to email rewriting:
* `Email.send_all_emails_to` will redirect all emails sent to the given address. Handy for testing!
* `Email.cc_all_emails_to` and `Email.bcc_all_emails_to` will keep the email going to its original recipients, but
add an additional recipient in the BCC/CC header. Good for monitoring system-generated correspondence on the live
systems.
:::php
if(Director::isLive()) Config::inst()->update('Email', 'bcc_all_emails_to', "client@example.com");
else Config::inst()->update('Email', 'send_all_emails_to', "developer@example.com");
### Setting Custom Headers
For email headers which do not have getters or setters (like setTo(), setFrom()) you can use **addCustomHeader($header,
$value)**
:::php
$email = new Email(...);
$email->addCustomHeader('HeaderName', 'HeaderValue');
..
See [Wikipedia E-mail Message header](http://en.wikipedia.org/wiki/E-mail#Message_header) for a list of header names.
### Newsletters
The [newsletter module](http://silverstripe.org/newsletter-module) provides a UI and logic to send batch emails.
## API Documentation
`[api:Email]`

View File

@ -1,87 +0,0 @@
# Error Handling
SilverStripe has its own error trapping and handling support.
## Error Levels
SilverStripe recognises two basic levels of error:
* **WARNING:** Something strange has happened; the system has attempted to continue as best it can, but the developers
need to look at this. This category also include areas where a newer version of SilverStripe requires changes to the
site's customised code.
* **FATAL ERROR:** There is no way that the system can attempt to continue with the particular operation; it would be
dangerous to report success to the user.
You should use [user_error](http://www.php.net/user_error) to throw errors where appropriate. The more information we
have about what's not right in the system, the better we can make the application.
* **E_USER_WARNING:** Err on the side of over-reporting warnings. The more warnings we have, the less chance there is
of a developer leaving a bug. Throwing warnings provides a means of ensuring that developers know whow
* Deprecated functions / usage patterns
* Strange data formats
* Things that will prevent an internal function from continuing. Throw a warning and return null.
* **E_USER_ERROR:** Throwing one of these errors is going to take down the production site. So you should only throw
E_USER_ERROR if it's going to be **dangerous** or **impossible** to continue with the request.
Note that currently, the SilverStripe core doesn't follow these standards perfectly.
* Right now, **every** failed SQL statement throws a fatal error. Many 'select' queries could probably be reduced to
warnings.
* A lot of assertion checking in the system that throws errors when it should throw warnings.
## Friendly Website Errors
An HTTP 500 error will be sent when there has been a fatal error on either a test or production site. You can make this
friendlier - much like the 404 page, the error content can be edited within the CMS.
* Create a page of type `[api:ErrorPage]`
* Set the error code to 500
* Publish the page.
**HOW IT WORKS: **The publication script for `[api:ErrorPage]` will write the full HTML content, including the template styling,
to assets/error-500.html. The fatal error handler looks for the presence of this file, and if it exists, dumps the
content. This means that database access isn't required to provide a 500 error page.
## Filesystem Logs
### From SilverStripe
You can indicate a log file relative to the site root. The named file will have a terse log sent to it, and the full log
(an encoded file containing backtraces and things) will go to a file of a similar name, but with the suffix ".full"
added.
`mysite/_config.php`:
:::php
// log errors and warnings
SS_Log::add_writer(new SS_LogFileWriter('/my/logfile/path'), SS_Log::WARN, '<=');
// or just errors
SS_Log::add_writer(new SS_LogFileWriter('/my/logfile/path'), SS_Log::ERR);
### From PHP
In addition to SilverStripe-integrated logging, it is adviseable to fall back to PHPs native logging functionality. A
script might terminate before it reaches the SilverStripe errorhandling, for example in the case of a fatal error.
`mysite/_config.php`:
:::php
ini_set("log_errors", "On");
ini_set("error_log", "/my/logfile/path");
## Email Logs
You can send both fatal errors and warnings in your code to a specified email-address.
`mysite/_config.php`:
:::php
// log errors and warnings
SS_Log::add_writer(new SS_LogEmailWriter('admin@domain.com'), SS_Log::WARN, '<=');
// or just errors
SS_Log::add_writer(new SS_LogEmailWriter('admin@domain.com'), SS_Log::ERR);

View File

@ -1,586 +0,0 @@
# Forms
HTML forms are in practice the most used way to interact with a user.
SilverStripe provides classes to generate and handle the actions and data from a
form.
## Overview
A fully implemented form in SilverStripe includes a couple of classes that
individually have separate concerns.
* Controller — Takes care of assembling the form and receiving data from it.
* Form — Holds sets of fields, actions and validators.
* FormField — Fields that receive data or displays them, e.g input fields.
* FormActions — Buttons that execute actions.
* Validators — Validate the whole form.
Depending on your needs you can customize and override any of the above classes;
the defaults, however, are often sufficient.
## The Controller
Forms start at the controller. Here is a simple example on how to set up a form
in a controller.
**Page.php**
:::php
class Page_Controller extends ContentController {
private static $allowed_actions = array(
'HelloForm'
);
// Template method
public function HelloForm() {
$fields = new FieldList();
$actions = new FieldList(
FormAction::create("doSayHello")->setTitle("Say hello")
);
$form = new Form($this, 'HelloForm', $fields, $actions);
// Load the form with previously sent data
$form->loadDataFrom($this->request->postVars());
return $form;
}
public function doSayHello($data, Form $form) {
// Do something with $data
return $this->render();
}
}
The name of the form ("HelloForm") is passed into the `Form` constructor as a
second argument. It needs to match the method name.
Because forms need a URL, the `HelloForm()` method needs to be handled like any
other controller action. To grant it access through URLs, we add it to the
`$allowed_actions` array.
Form actions ("doSayHello"), on the other hand, should _not_ be included in
`$allowed_actions`; these are handled separately through
`Form->httpSubmission()`.
You can control access on form actions either by conditionally removing a
`FormAction` from the form construction, or by defining `$allowed_actions` in
your own `Form` class (more information in the
["controllers" topic](/topics/controllers)).
**Page.ss**
:::ss
<%-- place where you would like the form to show up --%>
<div>$HelloForm</div>
<div class="warning" markdown='1'>
Be sure to add the Form name 'HelloForm' to your controller's $allowed_actions
array to enable form submissions.
</div>
<div class="notice" markdown='1'>
You'll notice that we've used a new notation for creating form fields, using
`create()` instead of the `new` operator. These are functionally equivalent, but
allows PHP to chain operations like `setTitle()` without assigning the field
instance to a temporary variable. For in-depth information on the create syntax,
see the [Injector](/reference/injector) documentation or the API documentation
for `[api:Object]`::create().
</div>
## The Form
Form is the base class of all forms in a SilverStripe application. Forms in your
application can be created either by instantiating the Form class itself, or by
subclassing it.
### Instantiating a form
Creating a form is a matter of defining a method to represent that form. This
method should return a form object. The constructor takes the following
arguments:
* `$controller`: This must be an instance of the controller that contains the
form, often `$this`.
* `$name`: This must be the name of the method on that controller that is
called to return the form. The first two arguments allow the form object
to be re-created after submission. **It's vital that they be properly
set—if you ever have problems with a form action handler not working,
check that these values are correct.**
* `$fields`: A `[api:FieldList]` containing `[api:FormField]` instances make
up fields in the form.
* `$actions`: A `[api:FieldList]` containing the `[api:FormAction]` objects -
the buttons at the bottom.
* `$validator`: An optional `[api:Validator]` for validation of the form.
Example:
:::php
// Controller action
public function MyCustomForm() {
$fields = new FieldList(
EmailField::create("Email"),
PasswordField::create("Password")
);
$actions = new FieldList(FormAction::create("login")->setTitle("Log in"));
return new Form($this, "MyCustomForm", $fields, $actions);
}
## Subclassing a form
It's the responsibility of your subclass's constructor to call
:::php
parent::__construct()
with the right parameters. You may choose to take $fields and $actions as
arguments if you wish, but $controller and $name must be passed—their values
depend on where the form is instantiated.
:::php
class MyForm extends Form {
public function __construct($controller, $name) {
$fields = new FieldList(
EmailField::create("Email"),
PasswordField::create("Password")
);
$actions = new FieldList(FormAction::create("login")->setTitle("Log in"));
parent::__construct($controller, $name, $fields, $actions);
}
}
The real difference, however, is that you can then define your controller
methods within the form class itself. This means that the form takes
responsibilities from the controller and manage how to parse and use the form
data.
**Page.php**
:::php
class Page_Controller extends ContentController {
private static $allowed_actions = array(
'HelloForm',
);
// Template method
public function HelloForm() {
return new MyForm($this, 'HelloForm');
}
}
**MyForm.php**
:::php
class MyForm extends Form {
public function __construct($controller, $name) {
$fields = new FieldList(
EmailField::create("Email"),
PasswordField::create("Password")
);
$actions = new FieldList(FormAction::create("login")->setTitle("Log in"));
parent::__construct($controller, $name, $fields, $actions);
}
public function login(array $data, Form $form) {
// Authenticate the user and redirect the user somewhere
Controller::curr()->redirectBack();
}
}
## The FormField classes
There are many classes extending `[api:FormField]`. There is a full overview at
[form field types](/reference/form-field-types).
### Using Form Fields
To get these fields automatically rendered into a form element, all you need to
do is create a new instance of the class, and add it to the `FieldList` of the
form.
:::php
$form = new Form(
$this, // controller
"SignupForm", // form name
new FieldList( // fields
TextField::create("FirstName")->setTitle('First name'),
TextField::create("Surname")->setTitle('Last name')->setMaxLength(50),
EmailField::create("Email")->setTitle("Email address")->setAttribute('type', 'email')
),
new FieldList( // actions
FormAction::create("signup")->setTitle("Sign up")
),
new RequiredFields( // validation
"Email", "FirstName"
)
);
## Readonly
You can turn a form or individual fields into a readonly version. This is handy
in the case of confirmation pages or when certain fields cannot be edited due to
permissions.
Readonly on a Form
:::php
$myForm->makeReadonly();
Readonly on a FieldList
:::php
$myFieldList->makeReadonly();
Readonly on a FormField
:::php
$myReadonlyField = $myField->transform(new ReadonlyTransformation());
// shortcut
$myReadonlyField = $myField->performReadonlyTransformation();
## Custom form templates
You can use a custom form template to render with, instead of *Form.ss*
It's recommended you do this only if you have a lot of presentation text or
graphics that surround the form fields. This is better than defining those as
*LiteralField* objects, as it doesn't clutter the data layer with presentation
junk.
First you need to create your own form class extending Form; that way you can
define a custom template using a `forTemplate()` method on your Form class.
:::php
class MyForm extends Form {
public function __construct($controller, $name) {
$fields = new FieldList(
EmailField::create("Email"),
PasswordField::create("Password")
);
$actions = new FieldList(FormAction::create("login")->setTitle("Log in"));
parent::__construct($controller, $name, $fields, $actions);
}
public function login(array $data, Form $form) {
// Do something with $data
Controller::curr()->redirectBack();
}
public function forTemplate() {
return $this->renderWith(array($this->class, 'Form'));
}
}
`MyForm->forTemplate()` tells the `[api:Form]` class to render with a template
of return value of `$this->class`, which in this case is *MyForm*. If the
template doesn't exist, then it falls back to using Form.ss.
*MyForm.ss* should then be placed into your *templates/Includes* directory for your project. Here is an example of
basic customisation, with two ways of presenting the field and its inline validation:
:::ss
<form $FormAttributes>
<% if $Message %>
<p id="{$FormName}_error" class="message $MessageType">$Message</p>
<% else %>
<p id="{$FormName}_error" class="message $MessageType" style="display: none"></p>
<% end_if %>
<fieldset>
<div id="Email" class="field email">
<label class="left" for="{$FormName}_Email">Email</label>
$Fields.dataFieldByName(Email)
<span id="{$FormName}_error" class="message $Fields.dataFieldByName(Email).MessageType">
$Fields.dataFieldByName(Email).Message
</span>
</div>
<div id="Email" class="field password">
<label class="left" for="{$FormName}_Password">Password</label>
<% with $Fields.dataFieldByName(Password) %>
$field
<% if $Message %>
<p id="{$FormName}_error" class="message $MessageType">$Message</p>
<% end_if %>
<% end_with %>
</div>
$Fields.dataFieldByName(SecurityID)
</fieldset>
<% if $Actions %>
<div class="Actions">
<% loop $Actions %>$Field<% end_loop %>
</div>
<% end_if %>
</form>
`$Fields.dataFieldByName(FirstName)` will return the form control contents of
`Field()` for the particular field object, in this case `EmailField->Field()` or
`PasswordField->Field()` which returns an `<input>` element with specific markup
for the type of field. Pass in the name of the field as the first parameter, as
done above, to render it into the template.
To find more methods, have a look at the `[api:Form]` class and
`[api:FieldList]` class as there is a lot of different methods of customising
the form templates. An example is that you could use `<% loop $Fields %>`
instead of specifying each field manually, as we've done above.
### Custom form field templates
The easiest way to customize form fields is adding CSS classes and additional attributes.
:::php
$field = TextField::create('MyText')
->addExtraClass('largeText');
->setAttribute('data-validation-regex', '[\d]*');
Will be rendered as:
:::html
<input type="text" name="MyText" class="text largeText" id="MyForm_MyCustomForm_MyText" data-validation-regex="[\d]*">
Each form field is rendered into a form via the
`[FormField->FieldHolder()](api:FormField)` method, which includes a container
`<div>` as well as a `<label>` element (if applicable).
You can also render each field without these structural elements through the
`[FormField->Field()](api:FormField)` method. To influence form rendering,
overriding these two methods is a good start.
In addition, most form fields are rendered through SilverStripe templates; for
example, `TextareaField` is rendered via
`framework/templates/forms/TextareaField.ss`.
These templates can be overridden globally by placing a template with the same
name in your `mysite` directory, or set on a form field instance via any of
these methods:
- FormField->setTemplate()
- FormField->setFieldHolderTemplate()
- FormField->setSmallFieldHolderTemplate()
<div class="hint" markdown='1'>
Caution: Not all FormFields consistently uses templates set by the above methods.
</div>
### Securing forms against Cross-Site Request Forgery (CSRF)
SilverStripe tries to protect users against *Cross-Site Request Forgery (CSRF)*
by adding a hidden *SecurityID* parameter to each form. See
[secure-development](/topics/security) for details.
In addition, you should limit forms to the intended HTTP verb (mostly `GET` or `POST`)
to further reduce attack exposure, by using `[api:Form->setStrictFormMethodCheck()]`.
:::php
$myForm->setFormMethod('POST');
$myForm->setStrictFormMethodCheck(true);
$myForm->setFormMethod('POST', true); // alternative short notation
### Remove existing fields
If you want to remove certain fields from your subclass:
:::php
class MyCustomForm extends MyForm {
public function __construct($controller, $name) {
parent::__construct($controller, $name);
// remove a normal field
$this->Fields()->removeByName('MyFieldName');
// remove a field from a tab
$this->Fields()->removeFieldFromTab('TabName', 'MyFieldName');
}
}
### Working with tabs
Adds a new text field called FavouriteColour next to the Content field in the CMS
:::php
$this->Fields()->addFieldToTab('Root.Content', new TextField('FavouriteColour'), 'Content');
## Form Validation
SilverStripe provides PHP form validation out of the box, but doesn't come with
any built-in JavaScript validation (the previously used `Validator.js` approach
has been deprecated).
### Required Fields
Validators are implemented as an argument to the `[api:Form]` constructor, and
are subclasses of the abstract `[api:Validator]` base class. The only
implementation that comes with SilverStripe is the `[api:RequiredFields]` class,
which ensures that fields are filled out when the form is submitted.
:::php
public function Form() {
$form = new Form($this, 'Form',
new FieldList(
new TextField('MyRequiredField'),
new TextField('MyOptionalField')
),
new FieldList(
new FormAction('submit', 'Submit form')
),
new RequiredFields(array('MyRequiredField'))
);
// Optional: Add a CSS class for custom styling
$form->dataFieldByName('MyRequiredField')->addExtraClass('required');
return $form;
}
### Form Field Validation
Form fields are responsible for validating the data they process, through the
`[api:FormField->validate()]` method. There are many fields for different
purposes (see ["form field types"](/reference/form-field-types) for a full list).
### Adding your own validation messages
In many cases, you want to add PHP validation that is more complex than
validating the format or existence of a single form field input. For example,
you might want to have dependent validation on a postcode which depends on the
country you've selected in a different field.
There are two ways to go about this: attach a custom error message to a specific
field, or a generic message to the whole form.
Example: Validate postcodes based on the selected country (on the controller).
:::php
class MyController extends Controller {
private static $allowed_actions = array('Form');
public function Form() {
return Form::create($this, 'Form',
new FieldList(
new NumericField('Postcode'),
new CountryDropdownField('Country')
),
new FieldList(
new FormAction('submit', 'Submit form')
),
new RequiredFields(array('Country'))
);
}
public function submit($data, $form) {
// At this point, RequiredFields->validate() will have been called already,
// so we can assume that the values exist.
// German postcodes need to be five digits
if($data['Country'] == 'de' && isset($data['Postcode']) && strlen($data['Postcode']) != 5) {
$form->addErrorMessage('Postcode', 'Need five digits for German postcodes', 'bad');
return $this->redirectBack();
}
// Global validation error (not specific to form field)
if($data['Country'] == 'IR' && isset($data['Postcode']) && $data['Postcode']) {
$form->sessionMessage("Ireland doesn't have postcodes!", 'bad');
return $this->redirectBack();
}
// continue normal processing...
}
}
### JavaScript Validation
Although there are no built-in JavaScript validation handlers in SilverStripe,
the `FormField` API is flexible enough to provide the information required in
order to plug in custom libraries.
#### HTML5 attributes
HTML5 specifies some built-in form validations
([source](http://www.w3.org/wiki/HTML5_form_additions)), which are evaluated by
modern browsers without any need for JavaScript. SilverStripe supports this by
allowing to set custom attributes on fields.
:::php
// Markup contains <input type="text" required />
TextField::create('MyText')->setAttribute('required', true);
// Markup contains <input type="url" pattern="https?://.+" />
TextField::create('MyText')
->setAttribute('type', 'url')
->setAttribute('pattern', 'https?://.+')
#### HTML5 metadata
In addition, HTML5 elements can contain custom data attributes with the `data-`
prefix. These are general-purpose attributes, but can be used to hook in your
own validation.
:::php
// Validate a specific date format (in PHP)
// Markup contains <input type="text" data-dateformat="dd.MM.yyyy" />
DateField::create('MyDate')->setConfig('dateformat', 'dd.MM.yyyy');
// Limit extensions on upload (in PHP)
// Markup contains <input type="file" data-allowed-extensions="jpg,jpeg,gif" />
$exts = array('jpg', 'jpeg', 'gif');
$fileField = FileField::create('MyFile');
$fileField->getValidator()->setAllowedExtensions($exts);
$fileField->setAttribute('data-allowed-extensions', implode(',', $exts));
Note that these examples don't have any effect on the client as such, but are
just a starting point for custom validation with JavaScript.
### Model Validation
An alternative (or additional) approach to validation is to place it directly on
the model. SilverStripe provides a `[api:DataObject->validate()]` method for
this purpose. Refer to the
["datamodel" topic](/topics/datamodel#validation-and-constraints) for more information.
### Validation in the CMS
Since you're not creating the forms for editing CMS records, SilverStripe
provides you with a `getCMSValidator()` method on your models to return a
`[api:Validator]` instance.
:::php
class Page extends SiteTree {
private static $db = array('MyRequiredField' => 'Text');
public function getCMSValidator() {
return new RequiredFields(array('MyRequiredField'));
}
}
### Subclassing Validator
To create your own validator, you need to subclass validator and define two methods:
* **javascript()** Should output a snippet of JavaScript that will get called
to perform javascript validation.
* **php($data)** Should return true if the given data is valid, and call
$this->validationError() if there were any errors.
## Related
* [Form Field Types](/reference/form-field-types)
* [MultiForm Module](http://silverstripe.org/multi-form-module)
* Model Validation with [api:DataObject->validate()]
## API Documentation
* `[api:Form]`
* `[api:FormField]`
* `[api:FieldList]`
* `[api:FormAction]`

View File

@ -1,34 +0,0 @@
# Topics
This section provides an overview on how things fit together, the "conceptual glue" between APIs and features.
It is where most documentation should live, and is the natural "second step" after finishing the tutorials.
* [Access Control and Page Security](access-control): Restricting access and setting up permissions on your website.
* [Authentication](authentication): Overview of the default member authentication system.
* [Caching](caching): Explains built-in caches for classes, config and templates. How to use your own caches.
* [Command line Usage](commandline): Calling controllers via the command line interface using `sake`
* [Configuration](configuration): Influence behaviour through PHP and YAML configuration
* [Controller](controller): The intermediate layer between your templates and the data model
* [Data Types](data-types): Types that properties on `DataObject` can have (e.g. `Text` or `Date`)
* [Datamodel](datamodel): How we use an "Object-relational model" to expose database information in a useful way
* [Debugging](debugging): Tracking down errors via logs, URL parameters and profiling
* [Directory Structure](directory-structure): What are core files, where do modules and my own project files go?
* [Emails](email): Configuring and sending emails
* [Environment management](environment-management): Sharing configuration details (e.g. database login, passwords) with multiple websites via a `_ss_environment.php` file
* [Error Handling](error-handling): Error messages and filesystem logs
* [Files and Images](files): File and Image management in the database and how to manipulate images
* [Forms & form validation](forms): Create your own form, add fields and create your own form template using the existing `Form` class
* [Internationalization (i18n)](i18n): Displaying templates and PHP code in different languages using i18n
* [Javascript](javascript): Best practices for developing with JavaScript in SilverStripe
* [Module Development](module-development): Creating a module (also known as "extension" or "plugin") to contain reusable functionality
* [Modules](modules): Introduction, how to download and install a module (e.g. with blog or forum functionality)
* [Page Type Templates](page-type-templates): How to build templates for all your different page types
* [Page Types](page-types): What is a "page type" and how do you create one?
* [Rich Text Editing](rich-text-editing): How to use and configure SilverStripes built in HTML Editor
* [Search](search): Searching for properties in the database as well as other documents
* [Security](security): How to develop secure SilverStripe applications with good code examples
* [Testing](testing): Functional and Unit Testing with PHPUnit and SilverStripe's testing framework
* [Theme Development](theme-development): Package templates, images and CSS to a reusable theme
* [Using Themes](themes): How to download and install themes
* [Versioning](versioning): Extension for SiteTree and other classes to store old versions and provide "staging"
* [Widgets](widgets): Small feature blocks which can be placed on a page by the CMS editor, also outlines how to create and add widgets

View File

@ -1,203 +0,0 @@
# Module Development
## Introduction
Creating a module is a good way to re-use abstract code and templates across
multiple projects. SilverStripe already has certain modules included, for
example "framework" and "cms". These two modules are the core functionality and
templates for any initial installation.
If you want to add generic functionality that isn't specific to your
project, like a forum, an ecommerce package or a blog you can do it like this:
1. Create another directory at the root level (same level as "framework"
and "cms"). This will contain all your module files.
2. The module directory must contain a `_config` sub-directory, or a
`_config.php` file to be recognised.
3. Inside your module directory, follow our
[directory structure guidelines](/topics/directory-structure#module_structure)
Once this is done, SilverStripe will automatically include any PHP classes and
templates from within your module.
## Tips
Try to keep your module as generic as possible - for example if you're making a
forum module, your members section shouldn't contain fields like 'Games You
Play' or 'Your LiveJournal Name' - if people want to add these fields they can
sub-class your class, or extend the fields on to it.
If you're using [api:Requirements] to include generic support files for your project
like CSS or Javascript, and want to override these files to be more specific in
your project, the following code is an example of how to do so using the init()
function on your module controller classes:
:::php
class Forum_Controller extends Page_Controller {
public function init() {
if(Director::fileExists(project() . "/css/forum.css")) {
Requirements::css(project() . "/css/forum.css");
} else {
Requirements::css("forum/css/forum.css");
}
parent::init();
}
}
This will use `<projectname>/css/forum.css` if it exists, otherwise it falls
back to using `forum/css/forum.css`.
## Conventions
### Configuration
SilverStripe has a comprehensive [Configuration](/topics/configuration) system
built on YAML which allows developers to set configuration values in core
classes.
If your module allows developers to customize specific values (for example API
key values) use the existing configuration system for your data.
:::php
// use this in your module code
$varible = Config::inst()->get('ModuleName', 'SomeValue');
Then developers can set that value in their own configuration file. As a module
author, you can set the default configuration values.
// yourmodule/_config/module.yml
---
Name: modulename
---
ModuleName:
SomeValue: 10
But by using the Config system, developers can alter the value for their
application without editing your code.
// mysite/_config/module_customizations.yml
---
Name: modulecustomizations
After: "#modulename"
---
ModuleName:
SomeValue: 10
If you want to make the configuration value user editable in the backend CMS,
provide an extension to [SiteConfig](/reference/siteconfig).
## Publication
If you wish to submit your module to our public directory, you take
responsibility for a certain level of code quality, adherence to conventions,
writing documentation, and releasing updates. See
[contributing](/misc/contributing). All modules should be published
on [addons.silverstripe.org](http://addons.silverstripe.org) to make them
discoverable by others.
### Composer and Packagist
SilverStripe uses [Composer](/installation/composer/) to manage module releases
and dependencies between modules. If you plan on releasing your module to the
public, ensure that you provide a `composer.json` file in the root of your
module containing the meta-data about your module.
For more information about what your `composer.json` file should include,
consult the [Composer Documentation](http://getcomposer.org/doc/01-basic-usage.md).
A basic usage of a module for 3.1 that requires the CMS would look similar to
this:
{
"name": "your-vendor-name/module-name",
"description": "One-liner describing your module",
"type": "silverstripe-module",
"homepage": "http://github.com/your-vendor-name/module-name",
"keywords": ["silverstripe", "some-tag", "some-other-tag"],
"license": "BSD-3-Clause",
"authors": [
{"name": "Your Name","email": "your@email.com"}
],
"support": {
"issues": "http://github.com/your-vendor-name/module-name/issues"
},
"require": {
"silverstripe/cms": "~3.1",
"silverstripe/framework": "~3.1"
},
"extra": {
"installer-name": "module-name",
"screenshots": [
"relative/path/screenshot1.png",
"http://myhost.com/screenshot2.png"
]
}
}
Once your module is released, submit it to [Packagist](https://packagist.org/)
to have the module accessible to developers. It'll automatically get picked
up by [addons.silverstripe.org](http://addons.silverstripe.org/).
### Versioning
Over time you may have to release new versions of your module to continue to
work with newer versions of SilverStripe. By using Composer, this is made easy
for developers by allowing them to specify what version they want to use. Each
version of your module should be a separate branch in your version control and
each branch should have a `composer.json` file explicitly defining what versions
of SilverStripe you support.
Say you have a module which supports SilverStripe 3.0.
A new release of this module takes advantage of new features
in SilverStripe 3.1. In this case, you would create a new branch
for the 3.0 compatible codebase of your module.
This allows you to continue fixing bugs on this older release branch.
As a convention, the `master` or `trunk` branch of your
module should always work with the `master` branch of SilverStripe.
Other branches should be created on your module as needed if they're
required to support specific SilverStripe releases.
You can have an overlap in supported versions,
e.g two branches in your module both support SilverStripe 3.1.
In this case, you should explain the differences in your `README.md` file.
Here's some common values for your `require` section
(see [getcomposer.org](http://getcomposer.org/doc/01-basic-usage.md#package-versions) for details):
* `3.0.*`: Version `3.0`, including `3.0.1`, `3.0.2` etc, excluding `3.1`
* `~3.0`: Version `3.0` or higher, including `3.0.1` and `3.1` etc, excluding `4.0`
* `~3.0,<3.2`: Version `3.0` or higher, up until `3.2`, which is excluded
* `~3.0,>3.0.4`: Version `3.0` or higher, starting with `3.0.4`
## Reference
### How To:
* [How to customize the CMS Menu](/howto/customize-cms-menu)
* [How to extend the CMS interface](/howto/extend-cms-interface)
### Reference:
Provide custom functionality for the developer via:
* [DataExtension](/reference/dataextension)
* [SiteConfig](/reference/siteconfig)
* [Page types](/topics/page-types)
Follow SilverStripe best practice:
* [Partial Caching](/reference/partial-caching)
* [Injector](/reference/injector)
## Useful Links
* [Introduction to Composer](http://getcomposer.org/doc/00-intro.md)
* [Modules](modules)
* [Directory structure guidelines](/topics/directory-structure#module_structure)
* [Debugging methods](/topics/debugging)
* [URL Variable Tools](/reference/urlvariabletools) - Lists a number of page options, rendering tools or special URL variables that you can use to debug your SilverStripe applications

View File

@ -1,108 +0,0 @@
# Modules
SilverStripe is designed to be a modular application system - even the CMS is simply a module that plugs into it.
A module is, quite simply, a collection of classes, templates, and other resources that is loaded into a top-level
directory. In a default SilverStripe download, even resources in 'framework' and 'mysite' are treated in exactly the
same as every other module.
SilverStripe's `[api:ManifestBuilder]` will find any class, css or template files anywhere under the site's main
directory. The `_config.php` file in the module directory as well as the [_config/*.yml files](/topics/configuration)
can be used to define director rules, add
extensions, etc. So, by unpacking a module into site's main directory and viewing the site with
?flush=1 on the end of the URL, all the module's new behaviour will be incorporated to your site:
* You can create subclasses of base classes such as SiteTree to extend behaviour.
* You can use Object::useCustomClass() to replace a built in class with a class of your own.
* You can use [an extension](api:DataExtension) to extend or alter the behaviour of a built-in class without replacing
it.
* You can provide additional director rules to define your own controller for particular URLs.
For more information on creating modules, see [module-development](/topics/module-development).
## Types of Modules
Because of the broad definition of modules, they can be created for a number of purposes:
* **Applications:** A module can define a standalone application that may work out of the box, or may get customisation
from your mysite folder. "cms" is an example of this.
* **CMS Add-ons:** A module can define an extension to the CMS, usually by defining special page types with their own
templates and behaviour. "blog", "ecommerce", "forum", and "gallery" are examples of this.
* **Widgets:** Small pieces of functionality such as showing the latest Comments or Flickr Photos. Since SilverStripe 3.0, they have been moved into a standalone module at [github.com/silverstripe/silverstripe-widgets](https://github.com/silverstripe/silverstripe-widgets).
* **Developer Tools:** A module can provide a number of classes or resource files that do nothing by themselves, but
instead make it easier for developers to build other applications.
## Finding Modules
* [Official module list on silverstripe.org](http://addons.silverstripe.org/)
* [Packagist.org "silverstripe" tag](https://packagist.org/search/?tags=silverstripe)
* [Github.com "silverstripe" search](https://github.com/search?q=silverstripe&ref=commandbar)
## Installation
Modules should exist in the root folder of your SilverStripe installation
(the directory containing the *framework* and *cms* subdirectories).
The following article explains the generic installation of a module. Individual modules have their own requirements such
as creating folders or configuring API keys. For information about installing or configuring a specific module see the
modules *README* file. Modules should adhere to the [directory-structure](/topics/directory-structure)
guidelines.
### From a Composer Package
Our preferred way to manage module dependencies is through the [Composer][http://getcomposer.org]
package manager. It enables you to install modules from specific versions, checking for
compatibilities between modules and even allowing to track development branches of them.
After [installing Composer](/installation/composer) itself,
you can run a simple command to install a module.
Each module has a unique identifier, consisting of a vendor prefix and name.
For example, the popular "blog" module has the identifier `silverstripe/blog`,
and would be installed with the following command executed in the root folder:
composer require silverstripe/blog:*@stable
This will fetch the latest compatible stable version. Every time you run
`composer update` afterwards, Composer will check for a new stable version.
To lock down to a specific version, branch or commit, read up on
[Composer "lock" files](http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file).
You can also add modules by editing the "require" section of the `composer.json` file.
To find modules and their identifiers, search for them on [packagist.org](http://packagist.org).
<div class="notice" markdown="1">
Older releases (<3.0.3, <2.4.9) don't come with a `composer.json` file in your root folder,
which is required for its operation. In this case, we recommend upgrading to a newer release.
</div>
### From an Archive Download
Alternatively, you can download the archive file from the
[modules page](http://www.silverstripe.org/modules)
and extract it to the root folder mentioned above.
Github also provides archive downloads which are generated automatically for every tag/version.
<div class="notice" markdown="1">
The main folder extracted from the archive
might contain the version number or additional "container" folders above the actual module
codebase. You need to make sure the folder name is the correct name of the module
(e.g. "blog/" rather than "silverstripe-blog/"). This folder should contain a `_config/` directory.
While the module might register and operate in other structures,
paths to static files such as CSS or JavaScript won't work.
</div>
<div class="warning" markdown="1">
Some modules might not work at all with this approach since they rely on the
Composer [autoloader](http://getcomposer.org/doc/01-basic-usage.md#autoloading)
or post-install hooks, so we recommend using Composer.
</div>
### Git Submodules and Subversion Externals
Git and Subversion provide their own facilities for managing dependent repositories.
This is essentially a variation of the "Archive Download" approach,
and comes with the same caveats.
## Related
* [Modules Development](/topics/module-development)

View File

@ -1,285 +0,0 @@
# Building templates for page types
Much of your work building a SilverStripe site will involve the creation of
templates for your own page types. SilverStripe has its own template language.
Its basic features like variables, blocks and loops are described in our ["templates" reference guide](/reference/templates).
In this guide, we'll show you specific uses for creating page layouts.
This assumes you are familiar with the concept of ["page types"](/topics/page-types).
To get a feel for what those templates look like, let's have a look at an abbreviated example. In your webroot, these templates are usually located in `themes/<your-theme>/templates`.
Replace the `<your-theme>` placeholder accordingly, most likely you're using a theme called "simple")
Most of the magic happens in `Page.ss` and `Layout/Page.ss`.
`themes/<your-theme>/templates/Page.ss`
:::ss
<html>
<head>
<% base_tag %>
<title>$SiteConfig.Title | $Title</title>
$MetaTags(false)
</head>
<body>
<div id="Container">
<header>
<h1>Bob's Chicken Shack</h1>
</header>
<navigation>
<% if $Menu(1) %>
<ul>
<% loop $Menu(1) %>
<li><a href="$Link" class="$LinkingMode">$MenuTitle</a></li>
<% end_loop %>
</ul>
<% end_if %>
</navigation>
<div class="typography">
$Layout
</div>
</div>
</body>
</html>
`themes/<your-theme>/templates/Layout/Page.ss`
<h2>$Title</h2>
$Content
$Form
### Template inheritance through $Layout
Our example shows two templates, both called `Page.ss`.
One is located in the `templates/` "root" folder, the other one in a `templates/Layout/` subfolder.
This "inner template" is used by the `$Layout` placeholder in the "root template",
and is inherited based on the underlying PHP classes (read more about template inheritance
on the ["page types" topic](/topics/page-types)).
"Layout" is a fixed naming convention,
you can't use the same pattern for other folder names.
### Page Content
:::ss
$Content
This variable in the `Layout` template contains the main content of the current page,
edited through the WYSIWIG editor in the CMS.
It returns the database content of the `SiteTree.Content` property.
Please note that this database content can be "staged",
meaning that draft content edited in the CMS can be different from published content
shown to your website visitors. In templates, you don't need to worry about this distinction.
The `$Content` variable contain the published content by default,
and only preview draft content if explicitly requested (e.g. by the "preview" feature in the CMS)
(see the ["versioning" topic](topics/versioning) for more details).
### Menu Loops
:::ss
<% loop $Menu(1) %>...<% end_loop %>
`$Menu(1)` is a built-in page control that defines the top-level menu.
You can also create a sub-menu using `$Menu(2)`, and so forth.
The `<% loop $Menu(1) %>...<% end_loop %>` block defines a repeating element.
It will change the "scope" of your template, which means that all of the template variables you use inside it will refer to a menu item. The template code will be repeated once per menu item, with the scope set to that menu item's page. In this case, a menu item refers to an instance
of the `Page` class, so you can access all properties defined on there, for example `$Title`.
Note that pages with the `ShowInMenus` property set to FALSE will be filtered out
(its a checkbox in the "Settings" panel of the CMS).
### Children Loops
:::ss
<% loop $Children %>...<% end_loop %>
Will loop over all children of the current page context.
Helpful to create page-specific subnavigations.
Most likely, you'll want to use `<% loop $Menu %>` for your main menus,
since its independent of the page context.
:::ss
<% loop $ChildrenOf(<my-page-url>) %>...<% end_loop %>
Will create a list of the children of the given page,
as identified by its `URLSegment` value. This can come in handy because its not dependent
on the context of the current page. For example, it would allow you to list all staff member pages
underneath a "staff" holder on any page, regardless if its on the top level or elsewhere.
:::ss
<% loop $allChildren %>...<% end_loop %>
This will show all children of a page even if the `ShowInMenus` property is set to FALSE.
### Access to Parent and Level Pages
:::ss
<% with $Level(1) %>
$Title
<% end_with %>
Will return a page in the current path, at the level specified by the numbers.
It is based on the current page context, looking back through its parent pages.
For example, imagine you're on the "bob marley" page,
which is three levels in: "about us > staff > bob marley".
* `$Level(1).Title` would return "about us"
* `$Level(2).Title` would return "staff"
* `$Level(3).Title` would return "bob marley"
To simply retrieve the parent page of the current context (if existing), use the `$Parent` variable.
### Access to a specific Page
:::ss
<% with $Page(my-page) %>...<% end_with %>`
"Page" will return a single page from the site tree, looking it up by URL. You can use it in the `<% loop %>` format.
Can't be called using `$Page(my-page).Title`.
### Title and Menu Title
The CMS provides two fields to label a page: "Title" and "Menu Title".
"Title" is the title in its full length, while "Menu Title" can be
a shorter version suitable for size-constrained menus.
If "Menu Title" is left blank by the CMS author, it'll just default to "Title".
### Links and Linking Modes
:::ss
$LinkingMode
Each menu item we loop over knows its location on the website, so can generate a link to it.
This happens through the `[api:SiteTree->Link()]` method behind the scenes.
We're not using the direct database property `SiteTree.URLSegment` here
because pages can be nested, so the link needs to be generated on the fly.
In the template syntax, there's no distinction between a method and a property though.
The link is relative by default (see `<% base_tag %>`),
you can get an absolute one including the domain through [$AbsoluteLink](api:SiteTree->AbsoluteLink())`.
In addition, each menu item gets some context information relative
to the page you're currently viewing, contained in the `$LinkingMode` placeholder.
By setting a HTML class to this value, you can distinguish the styling of
the currently selected menu item. It can have the following values:
* `link`: You are neither on this page nor in this section.
* `current`: You are currently on this page.
* `section`: The current page is a child of this menu item, so the current "section"
More common uses:
* `$LinkOrCurrent`: Determines if the item is the current page. Returns "link" or "current" strings.
* `$LinkOrSection`: Determines if the item is in the current section, so in the path towards the current page. Useful for menus which you only want to show a second level menu when you are on that page or a child of it. Returns "link" or "section" strings.
* `InSection(page-url)`: This if block will pass if we're currently on the page-url page or one of its children.
Example: Only show the menu item linked if its the current one:
:::ss
<% if $LinkOrCurrent = current %>
$Title
<% else %>
<a href="$Link">$Title</a>
<% end_if %>
### Breadcrumbs
Breadcrumbs are the path of parent pages which needs to be taken
to reach the current page, and can be a great navigation aid for website users.
While you can achieve breadcrumbs through the `<% Level(<level>) %>` control already,
there's a nicer shortcut: The `$Breadcrumbs` control.
It uses its own template defined in `BreadcrumbsTemplate.ss`.
Simply place a file with the same name in your `themes/<your-theme>/templates`
folder to customize its output. Here's the default template:
:::ss
<% if $Pages %>
<% loop $Pages %>
<% if $Last %>$Title.XML<% else %><a href="$Link">$MenuTitle.XML</a> &raquo;<% end_if %>
<% end_loop %>
<% end_if %>
For more customization options like limiting the amount of breadcrumbs,
take a look at `[api:SiteTree->Breadcrumbs()]`.
### SiteConfig: Global settings
:::ss
$SiteConfig.Title
The ["SiteConfig"](/reference/siteconfig) object allows content authors
to modify global data in the CMS, rather than PHP code.
By default, this includes a website title and tagline
(as opposed to the title of a specific page).
It can be extended to hold other data, for example a logo image
which can be uploaded through the CMS.
The object is available to all page templates through the `$SiteConfig` placeholder.
### Meta Tags
The `$MetaTags` placeholder in a template returns a segment of HTML appropriate for putting into the `<head>` tag. It
will set up title, keywords and description meta-tags, based on the CMS content and is editable in the 'Meta-data' tab
on a per-page basis. If you dont want to include the title-tag `<title>` (for custom templating), use
`$MetaTags(false)`.
By default `$MetaTags` renders:
:::ss
<title>Title of the Page</title>
<meta name="generator" http-equiv="generator" content="SilverStripe 3.0" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
#### URLSegment
This returns the part of the URL of the page you're currently on.
Shouldn't be used for linking to a page, since the link
is a composite value based on all parent pages as well (through the `$Link` variable).
#### ClassName
Returns the class of the underlying `Page` record.
This can be handy to add to your `<body>` tag to influence
CSS styles and JavaScript behaviour based on the page type used:
:::ss
<body class="$ClassName">
In case you want to include parent PHP classes in this list as well,
use the `$CSSClasses` placeholder instead.
#### BaseHref
Returns the base URL for the current site.
This is used to populate the `<base>` tag by default.
Can be handy to prefix custom links (not generated through `SiteTree->Link()`),
to ensure they work correctly when the webroot is hosted in a subfolder
rather than its own domain (a common development setup).
### Forms
:::ss
$Form
Very often, a page will contain some content and a form of some kind. For example, the log-in page has a log-in form. If you are on such a page, the `$Form` variable will contain the HTML content of the form. Placing it just below `$Content` is a good default. Behind the scenes,
it maps to the `Page_Controller->Form()` method. You can add more forms by implementing
new methods there (see ["forms" topic](/topics/forms) for details).
### More Advanced Controls
Template variables and controls are just PHP properties and methods
on the underlying controllers and model classes.
We've just shown you the most common once, in practice
you can use any public API on those classes, and [extend](/reference/dataextension) them
with your own. To get an overview on what's available to you,
we recommend that you dive into the API docs for the following classes:
* `[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

View File

@ -1,123 +0,0 @@
# Page Types
## Introduction
Page Types are the basic building blocks of any SilverStripe website. A page type can define:
* Templates being used to display content
* Form fields available to edit content in the CMS
* Behaviour specific to a page type. For example a contact form on a Contact Us page type, sending an email when the form is submitted
All the pages on the base installation are of the page type called "Page". See
[tutorial:2-extending-a-basic-site](/tutorials/2-extending-a-basic-site) for a good introduction to page-types.
## Class and Template Inheritance
Each page type on your website is a sub-class of the `SiteTree` class. Usually, youll define a class called `Page`
and use this template to lay out the basic design elements that dont change.
![](_images/pagetype-inheritance.png)
Each page type is represented by two classes: a data object and a controller. In the diagrams above and below, the data
objects are black and the controllers are blue. The page controllers are only used when the page type is actually
visited on the website. In our example above, the search form would become a method on the Page_Controller class.
Any methods put on the data object will be available wherever we use this page. For example, we put any customizations
we want to do to the CMS for this page type in here.
![](_images/controllers-and-dataobjects.png)
We put the `Page` class into a file called `Page.php` inside `mysite/code`.
As a convention, we also put the `Page_Controller` class in the same file.
Why do we sub-class `Page` for everything? The easiest way to explain this is to use the example of a search form. If we
create a search form on the `Page` class, then any other sub-class can also use it in their templates. This saves us
re-defining commonly used forms or controls in every class we use.
## Templates
Page type templates work much the same as other [templates](/reference/templates) in SilverStripe
(see ). There's some specialized controls and placeholders, as well as built-in inheritance.
This is explained in a more in-depth topic at [Page Type Templates](/topics/page-type-templates).
## Adding Database Fields
Adding database fields is a simple process. You define them in an array of the static variable `$db`, this array is
added on the object class. For example, Page or StaffPage. Every time you run dev/build to recompile the manifest, it
checks if any new entries are added to the `$db` array and adds any fields to the database that are missing.
For example, you may want an additional field on a `StaffPage` class which extends `Page`, called `Author`. `Author` is a
standard text field, and can be [casted](/topics/datamodel) as a variable character object in php (`VARCHAR` in SQL). In the
following example, our `Author` field is casted as a variable character object with maximum characters of 50. This is
especially useful if you know how long your source data needs to be.
:::php
class StaffPage extends Page {
private static $db = array(
'Author' => 'Varchar(50)'
);
}
class StaffPage_Controller extends Page_Controller {
}
See [datamodel](/topics/datamodel) for a more detailed explanation on adding database fields, and how the SilverStripe data
model works.
## Adding Form Fields and Tabs
See [form](/topics/forms) and [tutorial:2-extending-a-basic-site](/tutorials/2-extending-a-basic-site).
Note: To modify fields in the "Settings" tab, you need to use `updateSettingsFields()` instead.
## Removing inherited form fields and tabs
### removeFieldFromTab()
Overloading `getCMSFields()` you can call `removeFieldFromTab()` on a `[api:FieldList]` object. For example, if you don't
want the MenuTitle field to show on your page, which is inherited from `[api:SiteTree]`.
:::php
class StaffPage extends Page {
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->removeFieldFromTab('Root.Content', 'MenuTitle');
return $fields;
}
}
class StaffPage_Controller extends Page_Controller {
}
### removeByName()
`removeByName()` for normal form fields is useful for breaking inheritance where you know a field in your form isn't
required on a certain page-type.
:::php
class MyForm extends Form {
public function __construct($controller, $name) {
// add a default FieldList of form fields
$member = singleton('Member');
$fields = $member->formFields();
// We don't want the Country field from our default set of fields, so we remove it.
$fields->removeByName('Country');
$actions = new FieldList(
new FormAction('submit', 'Submit')
);
parent::__construct($controller, $name, $fields, $actions);
}
}
This will also work if you want to remove a whole tab e.g. $fields->removeByName('Metadata'); will remove the whole
Metadata tab.
For more information on forms, see [form](/topics/forms), [tutorial:2-extending-a-basic-site](/tutorials/2-extending-a-basic-site)
and [tutorial:3-forms](/tutorials/3-forms).

View File

@ -1,42 +0,0 @@
# Search
## Searching for Pages (and Files)
Fulltext search for page content (and other attributes like "Title" or "MetaTags") can be easily added to SilverStripe.
See [Tutorial: Site Search](/tutorials/4-site-search) for details.
## Searching for DataObjects
The `[api:SearchContext]` class provides a good base implementation that you can hook into your own controllers.
A working implementation of searchable DataObjects can be seen in the `[ModelAdmin](/reference/modeladmin)` class.
[SearchContext](/reference/searchcontext) goes into more detail about setting up a default search form for `[api:DataObject]`s.
### Fulltext search on DataObjects
The `[api:MySQLDatabase]` class now defaults to creating tables using the InnoDB storage engine. As Fulltext search in MySQL
requires the MyISAM storage engine, any DataObject you wish to use with Fulltext search must be changed to use MyISAM storage
engine.
You can do so by adding this static variable to your class definition:
:::php
private static $create_table_options = array(
'MySQLDatabase' => 'ENGINE=MyISAM'
);
## Searching for Documents
SilverStripe does not have a built-in method to search through file content (e.g. in PDF or DOC format).
You can either extract any textual file content into the `[File](api:File)->Content` property, or use a
dedicated search service like the [sphinx module](http://silverstripe.org/sphinx-module).
## Related
* [ModelAdmin](/reference/modeladmin)
* [RestfulServer module](https://github.com/silverstripe/silverstripe-restfulserver)
* [Tutorial: Site Search](/tutorials/4-site-search)
* [SearchContext](/reference/searchcontext)
* [genericviews module](http://silverstripe.org/generic-views-module)
* [sphinx module](http://silverstripe.org/sphinx-module)
* [lucene module](http://silverstripe.org/lucene-module)

View File

@ -1,69 +0,0 @@
# Creating a functional tests
Functional tests test your controllers. The core of these are the same as unit tests:
* Create a subclass of `[api:SapphireTest]` in the `mysite/tests` or `(module)/tests` folder.
* Define static $fixture_file to point to a database YAML file.
* Create methods that start with "test" to create your tests.
* Assertions are used to work out if a test passed or failed.
The code of the tests is a little different. Instead of examining the behaviour of objects, we example the results of
URLs. Here is an example from the subsites module:
:::php
class SubsiteAdminTest extends SapphireTest {
private static $fixture_file = 'subsites/tests/SubsiteTest.yml';
/**
* Return a session that has a user logged in as an administrator
*/
public function adminLoggedInSession() {
return Injector::inst()->create('Session', array(
'loggedInAs' => $this->idFromFixture('Member', 'admin')
));
}
/**
* Test generation of the view
*/
public function testBasicView() {
// Open the admin area logged in as admin
$response1 = Director::test('admin/subsites/', null, $this->adminLoggedInSession());
// Confirm that this URL gets you the entire page, with the edit form loaded
$response2 = Director::test('admin/subsites/show/1', null, $this->adminLoggedInSession());
$this->assertTrue(strpos($response2->getBody(), 'id="Root_Configuration"') !== false);
$this->assertTrue(strpos($response2->getBody(), '<head') !== false);
// Confirm that this URL gets you just the form content, with the edit form loaded
$response3 = Director::test('admin/subsites/show/1', array('ajax' => 1), $this->adminLoggedInSession());
$this->assertTrue(strpos($response3->getBody(), 'id="Root_Configuration"') !== false);
$this->assertTrue(strpos($response3->getBody(), '<form') === false);
$this->assertTrue(strpos($response3->getBody(), '<head') === false);
}
}
We are using a new static method here: **Director::test($url, $postVars, $sessionObj)**
Director::test() lets us execute a URL and see what happens. It bypasses HTTP, instead relying on the cleanly
encapsulated execution model of `[api:Controller]`.
It takes 3 arguments:
* $url: The URL to execute
* $postVars: Post variables to pass to the URL
* $sessionObj: A Session object representing the current session.
And it returns an `[api:HTTPResponse]` object, which will give you the response headers (including redirection), status code,
and body.
We can use string processing on the body of the response to then see if it fits with our expectations.
If you're testing for natural language responses like error messages, make sure to use [i18n](/topics/i18n) translations through
the *_t()* method to avoid tests failing when i18n is enabled.
Note that for a more highlevel testing approach, SilverStripe also supports
[behaviour-driven testing through Behat](https://github.com/silverstripe-labs/silverstripe-behat-extension). It interacts
directly with your website or CMS interface by remote controlling an actual browser, driven by natural language assertions.

View File

@ -1,64 +0,0 @@
# Creating a SilverStripe Test
A test is created by extending one of two classes, SapphireTest and FunctionalTest.
You would subclass `[api:SapphireTest]` to test your application logic,
for example testing the behaviour of one of your `[DataObjects](api:DataObject)`,
whereas `[api:FunctionalTest]` is extended when you want to test your application's functionality,
such as testing the results of GET and POST requests,
and validating the content of a page. FunctionalTest is a subclass of SapphireTest.
## Creating a test from SapphireTest
Here is an example of a test which extends SapphireTest:
:::php
<?php
class SiteTreeTest extends SapphireTest {
// Define the fixture file to use for this test class
protected static $fixture_file = 'SiteTreeTest.yml';
/**
* Test generation of the URLSegment values.
* - Turns things into lowercase-hyphen-format
* - Generates from Title by default, unless URLSegment is explicitly set
* - Resolves duplicates by appending a number
*/
public function testURLGeneration() {
$expectedURLs = array(
'home' => 'home',
'staff' => 'my-staff',
'about' => 'about-us',
'staffduplicate' => 'my-staff-2'
);
foreach($expectedURLs as $fixture => $urlSegment) {
$obj = $this->objFromFixture('Page', $fixture);
$this->assertEquals($urlSegment, $obj->URLSegment);
}
}
}
Firstly we define a static member `$fixture_file`, this should point to a file that represents the data we want to test,
represented in YAML. When our test is run, the data from this file will be loaded into a test database for our test to use.
This property can be an array of strings pointing to many .yml files, but for our test we are just using a string on its
own. For more detail on fixtures, see [this page](fixtures).
The second part of our class is the `testURLGeneration` method. This method is our test. You can asign many tests, but
again for our purposes there is just the one. When the test is executed, methods prefixed with the word `test` will be
run. The test database is rebuilt every time one of these methods is run.
Inside our test method is the `objFromFixture` method that will generate an object for us based on data from our fixture
file. To identify to the object, we provide a class name and an identifier. The identifier is specified in the YAML file
but not saved in the database anywhere, `objFromFixture` looks the `[api:DataObject]` up in memory rather than using the
database. This means that you can use it to test the functions responsible for looking up content in the database.
The final part of our test is an assertion command, `assertEquals`. An assertion command allows us to test for something
in our test methods (in this case we are testing if two values are equal). A test method can have more than one assertion
command, and if any one of these assertions fail, so will the test method.
For more information on PHPUnit's assertions see the [PHPUnit manual](http://www.phpunit.de/manual/current/en/api.html#api.assert).
The `[api:SapphireTest]` class comes with additional assertions which are more specific to Sapphire, for example the
`assertEmailSent` method, which simulates sending emails through the `Email->send()`
API without actually using a mail server. For more details on this see the [testing emails](testing-email) guide.

View File

@ -1,363 +0,0 @@
# Fixtures
## Overview
You will often find the need to test your functionality with some consistent
data. If we are testing our code with the same data each time, we can trust our
tests to yield reliable results.
In Silverstripe we define this data via 'fixtures' (so called because of their
fixed nature). The `[api:SapphireTest]` class takes care of populating a test
database with data from these fixtures - all we have to do is define them, and
we have a few ways in which we can do this.
## YAML Fixtures
YAML is a markup language which is deliberately simple and easy to read, so it
is ideal for fixture generation.
Say we have the following two DataObjects:
:::php
class Player extends DataObject {
private static $db = array (
'Name' => 'Varchar(255)'
);
private static $has_one = array(
'Team' => 'Team'
);
}
class Team extends DataObject {
private static $db = array (
'Name' => 'Varchar(255)',
'Origin' => 'Varchar(255)'
);
private static $has_many = array(
'Players' => 'Player'
);
}
We can represent multiple instances of them in `YAML` as follows:
:::yml
Player:
john:
Name: John
Team: =>Team.hurricanes
joe:
Name: Joe
Team: =>Team.crusaders
jack:
Name: Jack
Team: =>Team.crusaders
Team:
hurricanes:
Name: The Hurricanes
Origin: Wellington
crusaders:
Name: The Crusaders
Origin: Bay of Plenty
Our `YAML` is broken up into three levels, signified by the indentation of each
line. In the first level of indentation, `Player` and `Team`, represent the
class names of the objects we want to be created for the test.
The second level, `john`/`joe`/`jack` & `hurricanes`/`crusaders`, are
identifiers. These are what you pass as the second argument of
`SapphireTest::objFromFixture()`. Each identifier you specify represents a new
object.
The third and final level represents each individual object's fields.
A field can either be provided with raw data (such as the names for our
Players), or we can define a relationship, as seen by the fields prefixed with
`=>`.
Each one of our Players has a relationship to a Team, this is shown with the
`Team` field for each `Player` being set to `=>Team.` followed by a team name.
Take the player John for example, his team is the Hurricanes which is
represented by `=>Team.hurricanes`.
This is tells the system that we want to set up a relationship for the `Player`
object `john` with the `Team` object `hurricanes`.
It will populate the `Player` object's `TeamID` with the ID of `hurricanes`,
just like how a relationship is always set up.
<div class="hint" markdown='1'>
Note that we use the name of the relationship (Team), and not the name of the
database field (TeamID).
</div>
This style of relationship declaration can be used for both a `has-one` and a
`many-many` relationship. For `many-many` relationships, we specify a comma
separated list of values.
For example we could just as easily write the above as:
:::yml
Player:
john:
Name: John
joe:
Name: Joe
jack:
Name: Jack
Team:
hurricanes:
Name: The Hurricanes
Origin: Wellington
Players: =>Player.john
crusaders:
Name: The Crusaders
Origin: Bay of Plenty
Players: =>Player.joe,=>Player.jack
A crucial thing to note is that **the YAML file specifies DataObjects, not
database records**.
The database is populated by instantiating DataObject objects and setting the
fields declared in the YML, then calling write() on those objects. This means
that any `onBeforeWrite()` or default value logic will be executed as part of
the test. The reasoning behind this is to allow us to test the `onBeforeWrite`
functionality of our objects.
You can see this kind of testing in action in the `testURLGeneration()` test
from the example in [Creating a SilverStripe Test](creating-a-silverstripe-test).
### Defining many_many_extraFields
`many_many` relations can have additional database fields attached to the
relationship. For example we may want to declare the role each player has in the
team.
:::php
class Player extends DataObject {
private static $db = array (
'Name' => 'Varchar(255)'
);
private static $belongs_many_many = array(
'Teams' => 'Team'
);
}
class Team extends DataObject {
private static $db = array (
'Name' => 'Varchar(255)'
);
private static $many_many = array(
'Players' => 'Player'
);
private static $many_many_extraFields = array(
"Players" => array(
"Role" => "Varchar"
)
);
}
To provide the value for the many_many_extraField use the YAML list syntax.
:::yml
Player:
john:
Name: John
joe:
Name: Joe
jack:
Name: Jack
Team:
hurricanes:
Name: The Hurricanes
Players:
- =>Player.john:
Role: Captain
crusaders:
Name: The Crusaders
Players:
- =>Player.joe:
Role: Captain
- =>Player.jack:
Role: Winger
## Test Class Definition
### Manual Object Creation
Sometimes statically defined fixtures don't suffice. This could be because of
the complexity of the tested model, or because the YAML format doesn't allow you
to modify all of a model's state.
One common example here is publishing pages (page fixtures aren't published by
default).
You can always resort to creating objects manually in the test setup phase.
Since the test database is cleared on every test method, you'll get a fresh set
of test instances every time.
:::php
class SiteTreeTest extends SapphireTest {
function setUp() {
parent::setUp();
for($i=0; $i<100; $i++) {
$page = new Page(array('Title' => "Page $i"));
$page->write();
$page->publish('Stage', 'Live');
}
}
}
## Fixture Factories
### Why Factories?
While manually defined fixtures provide full flexibility, they offer very little
in terms of structure and convention. Alternatively, you can use the
`[api:FixtureFactory]` class, which allows you to set default values, callbacks
on object creation, and dynamic/lazy value setting.
<div class="hint" markdown='1'>
SapphireTest uses FixtureFactory under the hood when it is provided with YAML
based fixtures.
</div>
The idea is that rather than instantiating objects directly, we'll have a
factory class for them. This factory can have so called "blueprints" defined on
it, which tells the factory how to instantiate an object of a specific type.
Blueprints need a name, which is usually set to the class it creates.
### Usage
Since blueprints are auto-created for all available DataObject subclasses,
you only need to instantiate a factory to start using it.
:::php
$factory = Injector::inst()->create('FixtureFactory');
$obj = $factory->createObject('MyClass', 'myobj1');
It is important to remember that fixtures are referenced by arbitrary
identifiers ('myobj1'). These are internally mapped to their database identifiers.
:::php
$databaseId = $factory->getId('MyClass', 'myobj1');
In order to create an object with certain properties, just add a second argument:
:::php
$obj = $factory->createObject('MyClass', 'myobj1', array('MyProperty' => 'My Value'));
#### Default Properties
Blueprints can be overwritten in order to customize their behaviour,
for example with default properties in case none are passed into `createObject()`.
:::php
$factory->define('MyObject', array(
'MyProperty' => 'My Default Value'
));
#### Dependent Properties
Values can be set on demand through anonymous functions, which can either generate random defaults,
or create composite values based on other fixture data.
:::php
$factory->define('Member', array(
'Email' => function($obj, $data, $fixtures) {
if(isset($data['FirstName']) {
$obj->Email = strtolower($data['FirstName']) . '@example.org';
}
},
'Score' => function($obj, $data, $fixtures) {
$obj->Score = rand(0,10);
}
));
#### Relations
Model relations can be expressed through the same notation as in the YAML fixture format
described earlier, through the `=>` prefix on data values.
:::php
$obj = $factory->createObject('MyObject', 'myobj1', array(
'MyHasManyRelation' => '=>MyOtherObject.obj1,=>MyOtherObject.obj2'
));
#### Callbacks
Sometimes new model instances need to be modified in ways which can't be expressed
in their properties, for example to publish a page, which requires a method call.
:::php
$blueprint = Injector::inst()->create('FixtureBlueprint', 'Member');
$blueprint->addCallback('afterCreate', function($obj, $identifier, $data, $fixtures) {
$obj->publish('Stage', 'Live');
});
$page = $factory->define('Page', $blueprint);
Available callbacks:
* `beforeCreate($identifier, $data, $fixtures)`
* `afterCreate($obj, $identifier, $data, $fixtures)`
### Multiple Blueprints
Data of the same type can have variations, for example forum members vs.
CMS admins could both inherit from the `Member` class, but have completely
different properties. This is where named blueprints come in.
By default, blueprint names equal the class names they manage.
:::php
$memberBlueprint = Injector::inst()->create('FixtureBlueprint', 'Member', 'Member');
$adminBlueprint = Injector::inst()->create('FixtureBlueprint', 'AdminMember', 'Member');
$adminBlueprint->addCallback('afterCreate', function($obj, $identifier, $data, $fixtures) {
if(isset($fixtures['Group']['admin'])) {
$adminGroup = Group::get()->byId($fixtures['Group']['admin']);
$obj->Groups()->add($adminGroup);
}
});
$member = $factory->createObject('Member'); // not in admin group
$admin = $factory->createObject('AdminMember'); // in admin group
### Full Test Example
:::php
class MyObjectTest extends SapphireTest {
protected $factory;
function __construct() {
parent::__construct();
$factory = Injector::inst()->create('FixtureFactory');
// Defines a "blueprint" for new objects
$factory->define('MyObject', array(
'MyProperty' => 'My Default Value'
));
$this->factory = $factory;
}
function testSomething() {
$MyObjectObj = $this->factory->createObject(
'MyObject',
array('MyOtherProperty' => 'My Custom Value')
);
// $myPageObj->MyProperty = My Default Value
// $myPageObj->MyOtherProperty = My Custom Value
}
}

View File

@ -1,46 +0,0 @@
# Glossary
**Assertion:** A predicate statement that must be true when a test runs.
**Behat:** A behaviour-driven testing library used with SilverStripe as a higher-level
alternative to the `FunctionalTest` API, see [http://behat.org](http://behat.org).
**Test Case:** The atomic class type in most unit test frameworks. New unit tests are created by inheriting from the
base test case.
**Test Suite:** Also known as a 'test group', a composite of test cases, used to collect individual unit tests into
packages, allowing all tests to be run at once.
**Fixture:** Usually refers to the runtime context of a unit test - the environment and data prerequisites that must be
in place in order to run the test and expect a particular outcome. Most unit test frameworks provide methods that can be
used to create fixtures for the duration of a test - `setUp` - and clean them up after the test is done - `tearDown'.
**Refactoring:** A behavior preserving transformation of code. If you change the code, while keeping the actual
functionality the same, it is refactoring. If you change the behavior or add new functionality it's not.
**Smell:** A code smell is a symptom of a problem. Usually refers to code that is structured in a way that will lead to
problems with maintenance or understanding.
**Spike:** A limited and throwaway sketch of code or experiment to get a feel for how long it will take to implement a
certain feature, or a possible direction for how that feature might work.
**Test Double:** Also known as a 'Substitute'. A general term for a dummy object that replaces a real object with the
same interface. Substituting objects is useful when a real object is difficult or impossible to incorporate into a unit
test.
**Fake Object**: A substitute object that simply replaces a real object with the same interface, and returns a
pre-determined (usually fixed) value from each method.
**Mock Object:** A substitute object that mimics the same behavior as a real object (some people think of mocks as
"crash test dummy" objects). Mocks differ from other kinds of substitute objects in that they must understand the
context of each call to them, setting expectations of which, and what order, methods will be invoked and what parameters
will be passed.
**Test-Driven Development (TDD):** A style of programming where tests for a new feature are constructed before any code
is written. Code to implement the feature is then written with the aim of making the tests pass. Testing is used to
understand the problem space and discover suitable APIs for performing specific actions.
**Behavior Driven Development (BDD):** An extension of the test-driven programming style, where tests are used primarily
for describing the specification of how code should perform. In practice, there's little or no technical difference - it
all comes down to language. In BDD, the usual terminology is changed to reflect this change of focus, so *Specification*
is used in place of *Test Case*, and *should* is used in place of *expect* and *assert*.

View File

@ -1,62 +0,0 @@
# Testing Email
SilverStripe's test system has built-in support for testing emails sent using the `[api:Email]` class.
## How it works
For this to work, you need to send emails using the `Email` class,
which is generally the way that we recommend you send emails in your SilverStripe application.
Here is a simple example of how you might do this:
:::php
$e = new Email();
$e->To = "someone@example.com";
$e->Subject = "Hi there";
$e->Body = "I just really wanted to email you and say hi.";
$e->send();
Normally, the `send()` method would send an email using PHP's `mail()` function.
However, if you are running a `[api:SapphireTest]` test, then it holds off actually sending the email,
and instead lets you assert that an email was sent using this method.
:::php
$this->assertEmailSent("someone@example.com", null, "/th.*e$/");
The arguments are `$to`, `$from`, `$subject`, `$body`, and can take one of the three following types:
* A string: match exactly that string
* `null/false`: match anything
* A PERL regular expression (starting with '/'): match that regular expression
## How to use it
Given all of that, there is not a lot that you have to do in order to test emailing functionality in your application.
Whenever we include e-mailing functionality in our application,
we simply use `$this->assertEmailSent()` to check our mail has been passed to PHP `mail` in our tests.
That's it!
## What isn't tested
It's important to realise that this email testing doesn't actually test everything that there is to do with email.
The focus of this email testing system is testing that your application is triggering emails correctly.
It doesn't test your email infrastructure outside of the webserver. For example:
* It won't test that email is correctly configured on your webserver
* It won't test whether your emails are going to be lost in someone's spam filter
* It won't test bounce-handling or any other auxiliary services of email
## How it's built
For those of you who want to dig a little deeper, here's a quick run-through of how the system has been built.
As well as explaining how we built the email test,
this is a good design pattern for making other "tricky external systems" testable:
1. The `Email::send()` method makes uses of a static object, `Email::$mailer`, to do the dirty work of calling
mail(). The default mailer is an object of type `Mailer`, which performs a normal send.
2. `Email::set_mailer()` can be called to load in a new mailer object.
3. `SapphireTest::setUp()` method calls `Email::set_mailer(new TestMailer())` to replace the default mailer with a `TestMailer` object. This replacement mailer doesn't actually do anything when it is asked to send an email; it just
records the details of the email in an internal field that can be searched with `TestMailer::findEmails()`.
4. `SapphireTest::assertEmailSent()` calls `TestMailer::findEmails()` to see if a mail-send was requested by the
application.

View File

@ -1,61 +0,0 @@
# Unit Test Troubleshooting
Part of the [SilverStripe Testing Guide](testing-guide).
## I can't run my new test class
If you've just added a test class, but you can't see it via the web interface, chances are, you haven't flushed your
manifest cache - append `?flush=1` to the end of your URL querystring.
## Class 'PHPUnit_Framework_MockObject_Generator' not found
This is due to an upgrade in PHPUnit 3.5 which PEAR doesn't handle correctly.<br>
It can be fixed by running the following commands:
pear install -f phpunit/DbUnit
pear install -f phpunit/PHPUnit_MockObject
pear install -f phpunit/PHPUnit_Selenium
## My tests fail seemingly random when comparing database IDs
When defining fixtures in the YML format, you only assign aliases
for them, not direct database IDs. Even if you insert only one record
on a clean database, it is not guaranteed to produce ID=1 on every run.
So to make your tests more robust, use the aliases rather than hardcoded IDs.
Also, some databases don't return records in a consistent sort order
unless you explicitly tell them to. If you don't want to test sort order
but rather just the returned collection,
:::php
$myPage = $this->objFromFixture('Page', 'mypage');
$myOtherPage = $this->objFromFixture('Page', 'myotherpage');
$pages = Page::get();
// Bad: Assumptions about IDs and their order
$this->assertEquals(array(1,2), $pages->column('ID'));
// Good: Uses actually created IDs, independent of their order
$this->assertContains($myPage->ID, $pages->column('ID'));
$this->assertContains($myOtherPage->ID, $pages->column('ID'));
## My fixtures are getting complicated, how do I inspect their database state?
Fixtures are great because they're easy to define through YML,
but sometimes can be a bit of a blackbox when it comes to the actual
database state they create. These are temporary databases, which are
destructed directly after the test run - which is intentional,
but not very helpful if you want to verify that your fixtures have been created correctly.
SilverStripe comes with a URL action called `dev/tests/startsession`.
When called through a web browser, it prompts for a fixture file
which it creates a new database for, and sets it as the current database
in this browser session until you call `dev/tests/endsession`.
For more advanced users, you can also have a look in the `[api:YamlFixture]`
class to see what's going on behind the scenes.
## My database server is cluttered with `tmpdb...` databases
This is a common problem due to aborted test runs,
which don't clean up after themselves correctly
(mostly because of a fatal PHP error in the tests).
The easiest way to get rid of them is a call to `dev/tests/cleanupdb`.

View File

@ -1,93 +0,0 @@
# Why Unit Test?
*Note: This is part of the [SilverStripe Testing Guide](/topics/testing/).*
So at this point, you might be thinking, *"that's way too complicated, I don't have time to write unit tests on top of
all the things I'm already doing"*. Fair enough. But, maybe you're already doing things that are close to unit testing
without realizing it. Everyone tests all the time, in various ways. Even if you're just refreshing a URL in a browser to
review the context of your changes, you're testing!
First, ask yourself how much time you're already spending debugging your code. Are you inserting `echo`, `print_r`,
and `die` statements into different parts of your program and watching the details dumping out to screen? **Yes, you
know you are.** So how much time do you spend doing this? How much of your development cycle is dependent on dumping out
the contents of variables to confirm your assumptions about what they contain?
From this position, it may seem that unit testing may take longer and have uncertain outcomes simply because it involves
adding more code. You'd be right, in the sense that we should be striving to write as little code as possible on
projects. The more code there is, the more room there is for bugs, the harder it is to maintain. There's absolutely no
doubt about that. But if you're dumping the contents of variables out to the screen, you are already making assertions
about your code. All unit testing does is separate these assertions into separate runnable blocks of code, rather than
have them scattered inline with your actual program logic.
The practical and immediate advantages of unit testing are twofold. Firstly, they mean you don't have to mix your
debugging and analysis code in with your actual program code (with the need to delete, or comment it out once you're
done). Secondly, they give you a way to capture the questions you ask about your code while you're writing it, and the
ability to run those questions over and over again, with no overhead or interference from other parts of the system.
Unit testing becomes particularly useful when exploring boundary conditions or edge case behavior of your code. You can
write assertions that verify examples of how your methods will be called, and verify that they always return the right
results each time. If you make changes that have the potential to break these expected results, running the unit tests
over and over again will give you immediate feedback of any regressions.
Unit tests also function as specifications. They are a sure way to describe an API and how it works by simply running
the code and demonstrating what parameters each method call expects and each method call returns. You could think of it
as live API documentation that provides real-time information about how the code works.
Unit test assertions are best understood as **pass/fail** statements about the behavior of your code. Ideally, you want
every assertion to pass, and this is usually up by the visual metaphor of green/red signals. When things are all green,
it's all good. Red indicates failure, and provides a direct warning that you need to fix or change your code.
## Getting Started
Everyone has a different set of ideas about what makes good code, and particular preferences towards a certain style of
logic. At the same time, frameworks and programming languages provide clear conventions and design idioms that guide
code towards a certain common style.
If all this ranting and raving about the importance of testing hasn't made got you thinking that you want to write tests
then we haven't done our job well enough! But the key question still remains - *"where do I start?"*.
To turn the key in the lock and answer this question, we need to look at how automated testing fits into the different
aspects of the SilverStripe platform. There are some significant differences in goals and focus between different layers
of the system and interactions between the core, and various supporting modules.
### SilverStripe Core
In open source core development, we are focussing on a large and (for the most part) stable system with existing well
defined behavior. Our overarching goal is that we do not want to break or change this existing behavior, but at the same
time we want to extend and improve it.
Testing the SilverStripe framework should focus on [characterization](http://en.wikipedia.org/wiki/Characterization_Test).
We should be writing tests that illustrate the way that the API works, feeding commonly used methods with a range of
inputs and states and verifying that these methods respond with clear and predictable results.
Especially important is documenting and straighten out edge case behavior, by pushing various objects into corners and
twisting them into situations that we know are likely to manifest with the framework in the large.
### SilverStripe Modules
Modules usually encapsulate a smaller, and well defined subset of behavior or special features added on top of the core
platform. A well constructed module will contain a reference suite of unit tests that documents and verifies all the
basic aspects of the module design. See also: [modules](/topics/modules).
### Project Modules
Testing focus on client projects will not be quite so straightforward. Every project involves different personalities,
goals, and priorities, and most of the time, there is simply not enough time or resources to exhaustively predicate
every granular aspect of an application.
On application projects, the best option is to keep tests lean and agile. Most useful is a focus on experimentation and
prototyping, using the testing framework to explore solution spaces and bounce new code up into a state where we can be
happy that it works the way we want it to.
## Rules of Thumb
**Be aware of breaking existing behavior.** Run your full suite of tests every time you do a commit.
**Not everything is permanent.** If a test is no longer relevant, delete it from the repository.
## See Also
* [Getting to Grips with SilverStripe
Testing](http://www.slideshare.net/maetl/getting-to-grips-with-silverstripe-testing)

View File

@ -1,283 +0,0 @@
# Developing Themes
## Introduction
[Tutorial 1](/tutorials/1-building-a-basic-site#templates) shows you how to create page templates. This guide will help
you create your own SilverStripe website theme.
Developing your own theme in SilverStripe is a piece of cake thanks to a very straight forward and clean templating
language.
## What is a Theme?
A theme is a set of HTML/CSS/Javascript and images that can be used to provide a skin for a site. A theme does not
include any PHP: instead it should be separate from the code which allows the portability of a design. After all, this
is an MVC framework!
## Getting started - Folder Structure
To start your theme you first need to create the basic folder structure for the theme. Check out the image below for the
layout of your folders. First you need to create a folder in the themes directory called the name of your theme (we're
using "simple"). Please note that underscores in the theme name are reserved to denote "sub-themes" (e.g.
"blackcandy_blog").
![themes:basicfilestructure.gif](_images/basicfilestructure.gif)
Inside your theme, you need the css, images and templates folders. Each of these folders contain parts of your theme and
keeping a good folder structure is super important. Now we have this structure we can put our CSS in the css folder,
Images in the images folder and all our HTML in the templates folder. This keeps our workspace clean and easy to use.
After you have created the templates folder you need to create 2 more folders within - Includes and Layout (Note the
uppercase initial letters). These are 2 different types of templates you will use in your theme - Includes contain
snippets of HTML that you want to break out and share between templates (for example the Header can be an include,
Footer, Navigation etc) whereas Layout templates are the base page templates. So you can have several includes in a
Layout template.
## Getting started - Core Files
### HTML Templates
Once you have created your folders you need to start to fill it out with a couple 'Core' files. First and most
importantly is we need a HTML template for our design. Read the [Tutorial 1](/tutorials/1-building-a-basic-site#templates)
and the HTML pages for more in-depth discussion about the HTML templates and how they work. At the very least
we need a Page.ss file (note the .ss extenstion - Don't worry its just HTML and any text editor will still read it). So
go ahead and create 2 Page.ss files. One in templates, the other in Layout.
![themes:basicfiles.gif](_images/basicfiles.gif)
Whats with the 2 Page.ss files? Well we have 2 so when the user visits a page they get redirected to the top level
Page.ss then, depending on what page they are on, we can have a custom template for that page in the Layout folder. If
you dont get it now, you will hopefully pick it up over the rest of this.
So you have 2 blank Page.ss files. What are we going to do with them? How bout we put some HTML in them so we can see
our theme in action. The code for mine is below.
** yourtheme/templates/Page.ss **
:::ss
<!DOCTYPE html>
<html lang="en">
<head>
<% base_tag %>
$MetaTags(false)
<title>Bob's Chicken Shack | $Title</title>
</head>
<body>
<div id="Container">
<div id="Header">
<h1>Bob's Chicken Shack</h1>
</div>
<div id="Navigation">
<% if $Menu(1) %>
<ul>
<% loop $Menu(1) %>
<li><a href="$Link" title="Go to the $Title page" class="$LinkingMode">$MenuTitle</a></li>
<% end_loop %>
</ul>
<% end_if %>
</div>
<div id="Layout">
$Layout
</div>
<div id="Footer">
<p>Copyright $Now.Year - Bob's Chicken Shack.</p>
</div>
</div>
</body>
</html>
** yourtheme/templates/Layout/Page.ss **
:::ss
<h1>$Title</h1>
$Content
$Form
All you have to do now is tell your site to use your new theme. This is defined through
the YAML config, for example in `mysite/_config/config.yml`.
:::yml
SSViewer:
theme: 'mythemename'
Go to http://localhost?flush=1 and check it out. You should be using your new theme! Not really that awesome or amazing is
it? Next we need to add some CSS Magic!
See [Templates](/reference/themes) for more information about templates.
### CSS Files
By standard SilverStripe uses 3 CSS Files for your site -
* **layout.css** contains the layout and design of the site
* **typography.css** contains the styling for the text/fonts/links (used in both front and back ends)
* **form.css** styling for forms.
You can add more stylesheets using the template tag `<% require themedCSS("filename") %>`, which will load filename.css from
your css directory.
Note: If you're using a default install of Silverstripe and notice that you're getting layout.css, typography.css and
forms.css included without asking for them, they may be being called on lines 21-23 in mysite/code/Page.php. Remove
these three Requirements::themedCSS lines, and you will be free to add your own styles.
## Dividing your site the correct way!
Most Web page designers 10 years ago used a table-based layout to achieve a consistent look. Now, (Thankfully) there's a
different way to achieve the same look.
Using CSS and tags (including `DIV`s) reduces markup code, speeds up page downloads, separates content from
its visual presentation, and brings your code closer to web standards compliance--all while making your website more
appealing to search engine spiders.
For layout we tend to use `DIV` tags as the `DIV` tag defines a division/section in a document.
Let's have a look at part of a Page.ss for the main layout elements defining a 2 column layout.
:::ss
<div id="Container">
<div id="Header">
<!-- Header -->
</div>
<div id="Navigation">
<!-- The Main Site Nav -->
</div>
<div id="Layout">
<!-- The whole site content has to sit inside here! Anything you want to sub template (eg each page will be different, needs to be contained in $Layout. This calls the file /Layout/Page.ss or anyother sub page template -->
$Layout
</div>
<div id="Footer">
</div>
</div>
As you can see we normally wrap the site in a container. For this we use the ID 'Container'. Then we divide the main
template into sections.
:::ss
<div id="Header"><!-- markup goes here --></div>
We have the Header section which includes things like any banner images/ mastheads/ logos or any stuff that belongs at
the top of the page, This might vary on the design of the page
:::ss
<div id="Navigation"><!-- markup goes here --></div>
Next is a division for the main navigation. This may contain something like:
:::ss
<div id="Navigation">
<% if $Menu(1) %>
<ul>
<% loop $Menu(1) %>
<li><a href="$Link" title="Go to the $Title page" class="$LinkingMode">$MenuTitle</a></li>
<% end_loop %>
</ul>
<% end_if %>
</div>
This is the standard for creating the main Navigation. As you can see it outputs the Menu 1 in a unordered list.
Before stepping into a loop it's good practise to check if it exists first. This is not only
important in manipulating SilverStripe templates, but in any programming language!
:::ss
<% if $MyFunction %>
<% loop $MyFunction %>
$Title
<% end_loop %>
<% end_if %>
Last and probably least is the Footer division. Here is where you put all the Footer related stuff for your website.
Maybe even a nice link saying Website Powered by SilverStripe to show your support.
:::ss
<div id="Footer">
<!-- markup goes here -->
</div>
## Resources
A bunch of resources feel free to use to make your template awesome
* [http://kuler.adobe.com](http://kuler.adobe.com) - Kuler is a great color scheming tool
* [http://blog.html.it/layoutgala/](http://blog.html.it/layoutgala/) - 40 super cool CSS layouts for you to use
* [http://designmeltdown.com](http://designmeltdown.com) - Great gallery of websites. Browse through and get inspired.
* [http://validator.w3.org/](http://validator.w3.org/) - Your template must pass 'or get near' validation.
* [http://famfamfam.com/lab/icons/](http://famfamfam.com/lab/icons/) - free, beautiful icons.
* [http://cssremix.com](http://cssremix.com) - Another CSS site gallery for inspiration.
* [http://www.maxdesign.com.au/presentation/process/](http://www.maxdesign.com.au/presentation/process/) - a good process for creating a design
## Reference
### Overriding
The templating system will search for the appropriate files in the following order:
1. mysite (or other name given to site folder)
2. themes
3. module (eg blog)
So if, for example, you had a typography.css file for a module in the module folder (eg blog/css/), in the theme module
directory (eg themes/simple/css/), and in your site folder (eg mysite/css/), the system would use the file
mysite/css/typography.css
<div class="notice" markdown='1'>
Note: This only applies for CSS and template files. PHP files **do not** get overridden!
</div>
### Requirements
The `[api:Requirements::themedCSS()]` function will
do the search specified above. This avoids the need to type a full path to the css file, and also provides better
ambiguity for themes.
### Subthemes
If you have a theme called mytheme_forum, it is considered to be a 'subtheme' of the mytheme theme. This lets module
developers release extensions to popular themes that will let their module work with that theme.
A subtheme overrides the files in the module. So for instance, if you have the forum setup instead of editing the files
within the forum/ module you would create a themes/yourtheme_forum/ folder.
### ThemeDir
If you want to refer to an image or other file asset in a .ss template, use $ThemeDir. For example:
:::ss
<img src="$ThemeDir/images/log.gif" />
If your image is in a subtheme, you can refer to that with an argument to ThemeDir. For example, if your image was in
mytheme_forum, you could use the following code:
:::ss
<img src="$ThemeDir(forum)/images/log.gif" />
## Conventions, standards and guidelines
Following some set standards and conventions makes life easy for you and I.
Some conventions for SilverStripe websites, which we suggest following. Take a look at each:
* [Coding-Conventions](/misc/coding-conventions)
* [Typography](/reference/typography)
We also suggest following various [industry standards](http://www.w3.org/) and conventions.

View File

@ -1,43 +0,0 @@
# Themes
## Introduction
Themes can be used to kick start your SilverStripe projects, and generally make you look good.
## Downloading
Head to the [ Themes ](http://www.silverstripe.org/themes) area of the website to check out the wide range of themes
the community has built. Each theme has a page with links you can use to preview and download it. The theme is provided
as a .tar.gz file.
## Installing
1. Unpack the contents of the zip file into the `themes` directory in your SilverStripe installation.
2. Change the site to the theme. You can do this either by:
- Altering the `SSViewer.theme` setting in your `[config.yml](/topics/configuration)`
- changing the theme in the Site Configuration panel in the CMS
3. Visit your homepage with ?flush=all appended to the URL. `http://localhost?flush=all`
## Developing your own theme
See [Developing Themes](theme-development) to get an idea of how themes actually work and how you can develop your own.
## Submitting your theme to SilverStripe
If you want to submit your theme to the SilverStripe directory then check
* You should ensure your templates are well structured, modular and commented so it's easy for other people to
customise them.
* Templates should not contain text inside images and all images provided must be open source and not break any copyright or license laws.
This includes any icons your template uses in the frontend or the backend CMS.
* A theme does not include any PHP files. Only CSS, HTML, Images and Javascript.
Your theme file must be in a .tar.gz format. A useful tool for this is - [7 Zip](http://www.7-zip.org/). Using 7Zip you
must select the your_theme folder and Add to archive, select TAR and create. Then after you have the TAR file right
click it -> Add to Archive (again) -> Then use the archive format GZIP.
## Links
* [Themes Listing on silverstripe.org](http://silverstripe.org/themes)
* [Themes Forum on silverstripe.org](http://www.silverstripe.org/themes-2/)
* [Themes repository on github.com](http://github.com/silverstripe-themes)

View File

@ -1,204 +0,0 @@
# Versioning of Database Content
## Overview
Database content in SilverStripe can be "staged" before its publication,
as well as track all changes through the lifetime of a database record.
It is most commonly applied to pages in the CMS (the `SiteTree` class).
This means that draft content edited in the CMS can be different from published content
shown to your website visitors.
The versioning happens automatically on read and write.
If you are using the SilverStripe ORM to perform these operations,
you don't need to alter your existing calls.
Versioning in SilverStripe is handled through the `[api:Versioned]` class.
It's a `[api:DataExtension]`, which allow it to be applied to any `[api:DataObject]` subclass.
## Configuration
Adding versioned to your `DataObject` subclass works the same as any other extension.
It accepts two or more arguments denoting the different "stages",
which map to different database tables. Add the following to your [configuration file](/topics/configuration):
:::yml
MyRecord:
extensions:
- Versioned("Stage","Live")
Note: The extension is automatically applied to `SiteTree` class.
## Database Structure
Depending on how many stages you configured, two or more new tables will be created for your records.
Note that the "Stage" naming has a special meaning here, it will leave the original
table name unchanged, rather than adding a suffix.
* `MyRecord` table: Contains staged data
* `MyRecord_Live` table: Contains live data
* `MyRecord_versions` table: Contains a version history (new record created on each save)
Similarly, any subclass you create on top of a versioned base
will trigger the creation of additional tables, which are automatically joined as required:
* `MyRecordSubclass` table: Contains only staged data for subclass columns
* `MyRecordSubclass_Live` table: Contains only live data for subclass columns
* `MyRecordSubclass_versions` table: Contains only version history for subclass columns
## Usage
### Reading Versions
By default, all records are retrieved from the "Draft" stage (so the `MyRecord` table in our example).
You can explicitly request a certain stage through various getters on the `Versioned` class.
:::php
// Fetching multiple records
$stageRecords = Versioned::get_by_stage('MyRecord', 'Stage');
$liveRecords = Versioned::get_by_stage('MyRecord', 'Live');
// Fetching a single record
$stageRecord = Versioned::get_by_stage('MyRecord', 'Stage')->byID(99);
$liveRecord = Versioned::get_by_stage('MyRecord', 'Live')->byID(99);
### Historical Versions
The above commands will just retrieve the latest version of its respective stage for you,
but not older versions stored in the `<class>_versions` tables.
:::php
$historicalRecord = Versioned::get_version('MyRecord', <record-id>, <version-id>);
Caution: The record is retrieved as a `DataObject`, but saving back modifications
via `write()` will create a new version, 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]` objects,
which expose the same database information as a `DataObject`,
but also include information about when and how a record was published.
:::php
$record = MyRecord::get()->byID(99); // stage doesn't matter here
$versions = $record->allVersions();
echo $versions->First()->Version; // instance of Versioned_Version
### Writing Versions and Changing Stages
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 `<class>_versions` table.
To avoid this, use `[writeWithoutVersion()](api:Versioned->writeWithoutVersion())` instead.
To move a saved version from one stage to another,
call `[writeToStage(<stage>)](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(<from-stage>, <to-stage>)`.
:::php
$record = Versioned::get_by_stage('MyRecord', 'Stage')->byID(99);
$record->MyField = 'changed';
// will update `MyRecord` table (assuming Versioned::current_stage() == 'Stage'),
// and write a row to `MyRecord_versions`.
$record->write();
// will copy the saved record information to the `MyRecord_Live` table
$record->publish('Stage', 'Live');
Similarly, an "unpublish" operation does the reverse, and removes a record
from a specific stage.
:::php
$record = MyRecord::get()->byID(99); // stage doesn't matter here
// will remove the row from the `MyRecord_Live` table
$record->deleteFromStage('Live');
### Forcing the Current Stage
The current stage is stored as global state on the object.
It is usually modified by controllers, e.g. when a preview is initialized.
But it can also be set and reset temporarily to force a specific operation
to run on a certain stage.
:::php
$origMode = Versioned::get_reading_mode(); // save current mode
$obj = MyRecord::getComplexObjectRetrieval(); // returns 'Live' records
Versioned::set_reading_mode('Stage'); // temporarily overwrite mode
$obj = MyRecord::getComplexObjectRetrieval(); // returns 'Stage' records
Versioned::set_reading_mode($origMode); // reset current mode
### 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 smaller modifications of the generated `DataList` objects.
Example: Get the first 10 live records, filtered by creation date:
:::php
$records = Versioned::get_by_stage('MyRecord', 'Live')->limit(10)->sort('Created', 'ASC');
### Permissions
The `Versioned` extension doesn't provide any permissions on its own,
but you can have a look at the `SiteTree` class for implementation samples,
specifically `canPublish()` and `canDeleteFromStage()`.
### Page Specific Operations
Since the `Versioned` extension is primarily used for page objects,
the underlying `SiteTree` class has some additional helpers.
See the ["sitetree" reference](/reference/sitetree) for details.
### Templates Variables
In templates, you don't need to worry about this distinction.
The `$Content` variable contain the published content by default,
and only preview draft content if explicitly requested (e.g. by the "preview" feature in the CMS).
If you want to force a specific stage, we recommend the `Controller->init()` method for this purpose.
### Controllers
The current stage for each request is determined by `VersionedRequestFilter` before
any controllers initialize, through `Versioned::choose_site_stage()`.
It checks for a `Stage` GET parameter, so you can force
a draft stage by appending `?stage=Stage` to your request. The setting is "sticky"
in the PHP session, so any subsequent requests will also be in draft stage.
Important: The `choose_site_stage()` call only deals with setting the default stage,
and doesn't check if the user is authenticated to view it. As with any other controller logic,
please use `DataObject->canView()` to determine permissions, and avoid exposing unpublished
content to your users.
:::php
class MyController extends Controller {
private static $allowed_actions = array('showpage');
public function showpage($request) {
$page = Page::get()->byID($request->param('ID'));
if(!$page->canView()) return $this->httpError(401);
// continue with authenticated logic...
}
}
The `ContentController` class responsible for page display already has this built in,
so your own `canView()` checks are only necessary in controllers extending directly
from the `Controller` class.
## Recipes
It can be useful to add the variable `$SilverStripeNavigator` somewhere into the template, since it allows you to put a mini "admin" bar on the page which isn't visible to non editors. It shows the current stage and provides a convenient CMS link and version changing link.
Keep in mind that `$SilverStripeNavigator` is only available on ContentController, so useful when the Versioned extension is applied to SiteTree (default), and not when it's applied to DataObject.
### Trapping the publication event
Sometimes, you'll want to do something whenever a particular kind of page is published. This example sends an email
whenever a blog entry has been published.
:::php
class Page extends SiteTree {
// ...
public function onAfterPublish() {
mail("sam@silverstripe.com", "Blog published", "The blog has been published");
}
}

View File

@ -1,3 +0,0 @@
# Widgets
[Widgets](http://silverstripe.org/widgets) are small pieces of functionality such as showing the latest Comments or Flickr Photos. Since SilverStripe 3.0, they have been moved into a standalone module at [github.com/silverstripe/silverstripe-widgets](https://github.com/silverstripe/silverstripe-widgets).

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

View File

@ -1,23 +0,0 @@
# Written Tutorials
* [Tutorial 1: Building a basic site](1-building-a-basic-site): An introduction to building a site with
SilverStripe.
* [Tutorial 2: Extending a basic site](2-extending-a-basic-site): A tutorial that builds on "Building a basic
site".
* [Tutorial 3: Forms](3-forms): An introduction to forms in SilverStripe.
* [Tutorial 4: Site Search](4-site-search): Learn how to add search to your site.
* [Tutorial 5: Dataobject Relationship Management](5-dataobject-relationship-management): Learn how to create
simple data relationships.
# Video tutorials
* [Installing on Linux](http://silverstripe.org/assets/screencasts/Tutorial-InstallingLinux-DM08.swf)
* [Installing on Mac OSX (using MAMP)](http://silverstripe.org/assets/screencasts/Tutorial-InstallingMAMP-SW08.swf)
* [Installing a module (e.g. a blog)](http://silverstripe.org/assets/screencasts/Tutorial-InstallingBlogModule-DM08.swf)
* [Customising the CMS (adding new fields)](http://silverstripe.org/assets/screencasts/Tutorial-ChangingFields-DM08.swf)
# Help: If you get stuck
* [Common Problems](/installation/common-problems): Review some existing solutions to common problems.
* [SilverStripe Forums](http://www.silverstripe.com/silverstripe-forum/): Head over to the forums and ask the community
for help.